diff --git a/.gitignore b/.gitignore index 5b0ed67..319790e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ Thumbs.db # Temporary and editor files *.swp -*~ \ No newline at end of file +*~ +# Locally supplied external exhibits; never vendor them into NGN. +/test-fixtures/local/ diff --git a/.labyricorn/devlog/post-step-4-completion/contents.lr b/.labyricorn/devlog/post-step-4-completion/contents.lr index 2d967f5..3810930 100644 --- a/.labyricorn/devlog/post-step-4-completion/contents.lr +++ b/.labyricorn/devlog/post-step-4-completion/contents.lr @@ -25,4 +25,3 @@ body: | - Comprehensive test fixtures and test suites under `test‑fixtures/` and `tests/`. All modified files (`AGENTS.md`, `README.md`) were updated to reflect the latest project state. The commit was authored as part of the Step 4 milestone and is tagged accordingly. ---- diff --git a/AGENTS.md b/AGENTS.md index 3174e45..8680d9e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,10 +49,16 @@ scoped `AGENTS.md` files before making changes. ## Authoritative project documents, in priority order: +This repository is the canonical home of the shared Contract Specification and +Authoring Guide, and owns the NGN Implementation Plan. Documents in +`docs/reference/` describe external implementations, not NGN architecture. +Reference exhibits under `test-fixtures/` are test/reference fixtures, not product +code. Keep exhibit-specific names and paths in test configuration or documentation. + - XZBT Exhibit Contract Specification v5.2 - XZBT-NGN Exhibit Engine Implementation Plan v5.2 - XZBT Exhibit Authoring Guide v5.2 - Step 3.7 reference exhibit report - Do not infer XZBT-NGN behavior from SciFi-XZBT source code. -- SciFi-XZBT is an external exhibit implementation, not the NGN architecture. \ No newline at end of file +- SciFi-XZBT is an external exhibit implementation, not the NGN architecture. diff --git a/README.md b/README.md index f93f1e3..c6a8e4a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,41 @@ # XZBT-NGN Exhibit Engine -A minimal, single-exhibit engineering host for XZBT Exhibit Contract 5.2. -It discovers controls from `describe`, tracks the exhibit's reported state, -and exposes set/invoke operations, events, sessions, and protocol errors. +A single-exhibit host for XZBT Exhibit Contract 5.2. It negotiates sessions, +discovers descriptor-driven controls, inspects reported state, sends set/invoke +operations, and displays capabilities, events, revisions, and protocol errors. + +Step 5 implementation and verification are recorded in +[the completion report](STEP5-MVP-COMPLETION-REPORT.md). The SciFi-XZBT argument +descriptors are corrected and both text invocations pass through the operator UI. +Step 5 is complete: 27 automated tests and all three browser reference checks +pass. Haunted House reports conformant values, and publishing validation passes. +Step 6 is planned but has not begun. ## Run locally -Requires Node.js 22 or later. No npm dependencies or build step are needed. +Requires Node.js 22 or later. No npm dependencies or build step. ```powershell npm start ``` Open `http://127.0.0.1:4173`. Enter a same-origin exhibit path and select -**Load exhibit**. The host negotiates a real session, calls `describe` and -`state.get`, then renders the discovered catalog. **Reconnect** establishes -a new session without reloading the exhibit; **Refresh state** requests a -new authoritative snapshot. **Disconnect** removes the exhibit frame. +**Load exhibit**. The host sends `hello`, `describe`, and `state.get` and renders +the discovered controls. Enter another path and select **Load exhibit** to switch. -Included test fixture paths: +- **Reconnect** negotiates a new session with the current frame, preserving the + exhibit instance. After **Disconnect**, it reloads the remembered URL; the new + exhibit instance starts with its own initial state. +- **Disconnect** removes the frame and clears session, catalog, state and events. +- **Refresh state** reads a new authoritative snapshot. +- Controls are grouped by the exhibit's category (or Other when omitted). + Reported values and draft inputs are distinct; select **Set** or **Invoke** + to submit. Rediscovery resets drafts. +- Expand **Capabilities**, **State and metadata**, or **Protocol diagnostics** + details to inspect engineering information. The latest diagnostic is also + shown near the connection panel. **Clear log** dismisses historical diagnostics. + +Included reference fixture paths: ```text /test-fixtures/reference-exhibits/aquarium/index.html @@ -26,12 +43,13 @@ Included test fixture paths: /test-fixtures/reference-exhibits/haunted-house/index.html ``` -These copied fixtures have documented compatibility corrections; see -[fixture provenance](test-fixtures/PROVENANCE.md). They are not NGN product -logic. Additional trusted HTML exhibits can be placed in a directory under -`public/` and loaded by their same-origin URL. The server exposes only -`public/`, `src/`, and `test-fixtures/`, and binds to `127.0.0.1`. -Set the `PORT` environment variable to change the default port. +These are locally corrected test/reference fixtures, not NGN product code or +pristine upstream conformance evidence. See [provenance](test-fixtures/PROVENANCE.md). +Place additional trusted HTML exhibits and their relative assets under `public/` +and enter their served URL. A filesystem path or a different server origin cannot +be attached directly. The server exposes only `public/`, `src/`, and +`test-fixtures/`, binds to `127.0.0.1`, and denies paths escaping those directories. +For a port conflict, set `$env:PORT='4175'` before `npm start` and use that origin. ## Verification @@ -41,240 +59,53 @@ python devlog_editor.py --validate git diff --check ``` -With the server running, open -`http://127.0.0.1:4173/test-fixtures/host-verification.html` and select -**Run verification** for repeatable real-browser transport tests against all -three exhibits. Manual admin UI and native exhibit gesture checks are recorded -in [the Step 4 report](STEP4-MVP-REPORT.md). +Run the browser suite at `/test-fixtures/host-verification.html`; its timestamped +results appear on the page and in the browser console. The normal operator UI +checks and external SciFi-XZBT preparation are documented in +[Step 5 verification](test-fixtures/STEP5-VERIFICATION.md). Saved browser evidence +is under `test-fixtures/evidence/`. No source edits or dedicated harness are +needed for ordinary operation. -## Implementation boundaries +## Canonical documentation -- `src/host.js`: session/request correlation, catalog, state cache, events, - synchronization, and control operations. -- `src/validation.js`: descriptor, value, and clarified argument validation. -- `src/transport/post-message.js`: same-origin frame transport only. -- `src/ui.js` and `public/`: descriptor-driven engineering interface. -- `server/serve.js`: loopback static server with explicit served directories. -- `tests/`: focused automated host and server tests. -- `test-fixtures/`: copied reference exhibits and real-browser verification. +This repository is the canonical home for the shared +[XZBT Exhibit Contract Specification 5.2](docs/contract/XZBT-Exhibit-Contract-Specification-v5.2.md) +and [XZBT Exhibit Authoring Guide 5.2](docs/authoring/XZBT-Exhibit-Authoring-Guide-v5.2-Step3.7.md). +The [NGN Implementation Plan 5.2](docs/architecture/XZBT-NGN-Exhibit-Engine-Implementation-Plan-v5.2.md) +also belongs here. Its historical phase numbering describes the longer roadmap; +the current Step 5 completion pass does not authorize those later features. +SciFi-XZBT is an external exhibit implementation, not the NGN architecture. +`docs/reference/` contains external implementation context, not host requirements. -The state display is a cache of exhibit reports. Events update reported values; -editable fields are drafts and are submitted only when Set/Invoke is selected. -Draft fields start from the first snapshot and do not overwrite themselves on -each incoming event. Registry/capability rediscovery rebuilds the controls. -Logs retain the latest 200 entries in memory. The interface intentionally has -no persistence, accounts, MIDI, scenarios, webhooks, packaging, or orchestration. +## Implementation and MVP boundary -## Template and publishing records +- `src/host.js`: negotiation, requests, discovery, state cache, events and recovery. +- `src/connection.js`: URL validation and frame load/disconnect/reconnect lifecycle. +- `src/validation.js`: descriptor, state value and argument validation. +- `src/transport/post-message.js`: same-origin frame transport. +- `src/ui.js`, `public/`: generic operator interface. +- `server/serve.js`: loopback static server. +- `tests/`, `test-fixtures/`: automated checks, reference fixtures and browser evidence. -A starter template and baseline structure for projects compatible with -[Labyricorn](https://www.labyricorn.com). +The MVP supports one exhibit, Contract 5.2 discovery, generic target rendering, +state inspection, set/invoke, capabilities, events, session/revision/sequence +tracking, reconnect and synchronization recovery. It has in-memory state only; +logs retain 200 entries. Idle polling is not enabled. The last connection URL is +remembered only until the page is reloaded. -This template provides the required `.labyricorn/` publishing records, assistant -guidance files (`AGENTS.md`), devlog structure, and the standalone -`devlog_editor.py` management utility for local validation and GUI editing. +Not implemented: multi-surface presentation, casting/display endpoints, MIDI, +MCP/webhooks, scenarios/recording, telemetry acquisition, multi-exhibit +orchestration, persisted operator configurations, remote deployment, accounts, +databases, cloud services, plugins, commercial licensing or production branding. ---- +## Publishing records -## Overview - -Projects published on Labyricorn maintain their own exhibition pages and -development logs directly inside their Git repositories under the `.labyricorn/` -directory. The Labyricorn site importer periodically synchronizes these records -as read-only content. - -This template includes: - -- **`.labyricorn/` publishing structure**: Validated Lektor-format records for - project identity and development logs. -- **`devlog_editor.py`**: Standalone GUI and CLI tool for managing devlog entries, - validating publishing contracts, and safely publishing changes. -- **`AGENTS.md` instructions**: Scoped assistant instructions at repository root - and publishing directories to ensure AI assistants adhere to the publishing - contract. -- **`.gitignore`**: Configured exclusions for Python environments, caches, and build - artifacts. - ---- - -## Directory Structure - -```text -. -├── .gitignore -├── .labyricorn/ -│ ├── AGENTS.md # Assistant instructions for publishing content -│ ├── README.md # Publishing structure documentation -│ ├── project/ -│ │ ├── AGENTS.md # Instructions for the project exhibition record -│ │ ├── contents.lr # Project identity and exhibition narrative -│ │ └── # Project logo (PNG, JPEG, WebP) -│ └── devlog/ -│ ├── AGENTS.md # Instructions for devlog records -│ ├── contents.lr # Devlog index record -│ └── / # (Optional) Devlog entry directories -│ ├── contents.lr # Devlog entry record -│ └── # Entry attachments (PNG, JPEG, WebP) -├── AGENTS.md # Assistant instructions for the repository -├── devlog_editor.py # Standalone devlog editor & validator -├── LICENSE # License file -└── README.md # Project documentation -``` - ---- - -## Getting Started - -### 1. Configure the Project Identity - -Edit `.labyricorn/project/contents.lr` to set your project details: - -```ini -_model: project ---- -schema_version: 1 ---- -project_id: my-project-id ---- -title: My Project Title ---- -summary: - -A concise summary of the project for listings and cards. ---- -status: active ---- -started: 2026-08-23 ---- -author: Author Name ---- -repository_url: https://git.labyricorn.com/Owner/my-project ---- -default_branch: main ---- -tags: python, web, tool ---- -body: - -An exhibition narrative describing the project background, goals, and features. -``` - -#### Project Fields: -- `project_id`: Stable lowercase hyphenated slug (e.g. `my-project`). -- `title`: Display title for the project. -- `summary`: Short multi-line summary. -- `status`: One of `active`, `released`, `maintained`, or `archived`. -- `started`: Start date in `YYYY-MM-DD` format. -- `author`: Author or organization name. -- `repository_url`: Public credential-free HTTPS Git URL. -- `default_branch`: Target branch name (e.g. `main`). -- `logo`: (Optional) Local filename of an image inside `.labyricorn/project/`. -- `tags`: Comma-separated technology or topic labels. -- `body`: Markdown narrative of the project. - -### 2. Configure the Devlog Index - -Edit `.labyricorn/devlog/contents.lr` to customize the devlog title and summary: - -```ini -_model: devlog ---- -schema_version: 1 ---- -title: My Project development log ---- -summary: Milestones and development updates for My Project. -``` - -### 3. Add Devlog Entries - -To add an entry manually, create a directory under `.labyricorn/devlog//` -containing `contents.lr`: - -```ini -_model: devlog-entry ---- -schema_version: 1 ---- -title: Initial Project Milestone ---- -date: 2026-08-23 ---- -author: Author Name ---- -summary: - -Overview of the milestone accomplishments. ---- -tags: release, setup ---- -source_commit: <40-character-commit-hash> ---- -body: - -Detailed Markdown description of the milestone, technical decisions, and progress. -``` - -Alternatively, use `devlog_editor.py` to create and edit entries interactively. - ---- - -## Devlog Editor - -The included `devlog_editor.py` script is a standalone utility with zero external -dependencies (uses Python standard library and Tkinter). - -### CLI Validation - -Validate all project and devlog records without opening a GUI: - -```bash -python devlog_editor.py --validate -``` - -### Graphical Editor - -Launch the Tkinter management interface to view repository status, draft entries, -and publish updates: - -```bash -python devlog_editor.py -``` - -Features of the GUI editor: -- **Repository status**: Displays current branch, remote, and ahead/behind counts. -- **Entry manager**: Create, edit, and delete devlog entries with automatic slug and date handling. -- **Publishing safety**: Stages and commits only `.labyricorn/` publishing files, leaving application source files untouched. - ---- - -## Publishing Rules & Constraints - -The Labyricorn site importer enforces strict validation rules on imported repositories: - -1. **Native Lektor Records**: Records must be named `contents.lr` and declare `schema_version: 1`. -2. **No Raw HTML**: HTML tags (` diff --git a/public/style.css b/public/style.css index 2bff3cd..68d687e 100644 --- a/public/style.css +++ b/public/style.css @@ -13,3 +13,14 @@ pre { white-space: pre-wrap; overflow-wrap: anywhere; background: #111d28; paddi .controls { display: flex; align-items: end; flex-wrap: wrap; gap: 12px; } .controls label { min-width: 150px; } .notice { color: #ffcd7a; } details { margin: 8px 0; } summary { cursor: pointer; } output { display: block; margin: 8px 0; } button:focus-visible, input:focus-visible, select:focus-visible { outline: 2px solid #7cceff; outline-offset: 2px; } +#status { font-weight: 650; padding: 8px 12px; border-left: 4px solid #ffcd7a; } +#status[data-state=connected] { border-color: #80d6ad; } +#status[data-state=error], #latest-error { color: #ffcd7a; } +#loaded-url { overflow-wrap: anywhere; font-size: 13px; } +#identity { font-weight: 600; } +#capabilities { display: flex; flex-wrap: wrap; gap: 8px 24px; padding-left: 20px; } +.target-group { margin-top: 24px; } +.target-group > h3 { background: #233444; padding: 8px 12px; } +.target { padding: 16px 12px; } +.target code { overflow-wrap: anywhere; } +@media (max-width: 600px) { header, main { padding: 12px; } section { padding: 12px; } .connection-row input { flex-basis: 100%; } } diff --git a/src/connection.js b/src/connection.js new file mode 100644 index 0000000..984fbbf --- /dev/null +++ b/src/connection.js @@ -0,0 +1,49 @@ +import { ProtocolError } from './validation.js'; + +export function exhibitURL(value, base) { + if (!value.trim()) throw new Error('Enter an exhibit URL or served path.'); + const url = new URL(value.trim(), base); + if (!['http:', 'https:'].includes(url.protocol) || url.origin !== new URL(base).origin || url.username || url.password) { + throw new Error('Enter a same-origin HTTP exhibit URL or served path.'); + } + return url.href; +} + +// Frame lifecycle only; protocol negotiation remains in ExhibitHost. +export class ExhibitConnection { + constructor({ host, createFrame, transport, base, changed = () => {}, loadTimeoutMs = 15000 }) { + Object.assign(this, { host, createFrame, transport, base, changed, loadTimeoutMs }); + this.frame = null; this.url = ''; this.loading = false; this.generation = 0; + } + disconnect() { + this.generation++; clearTimeout(this.timer); this.loading = false; + this.frame?.remove(); this.frame = null; this.host.disconnect(); this.changed(); + } + load(value) { + const url = exhibitURL(value, this.base); // Invalid input preserves the active exhibit. + this.disconnect(); this.url = url; this.loading = true; + const generation = this.generation; + const frame = this.createFrame(); this.frame = frame; + frame.addEventListener('load', () => { + if (generation !== this.generation) return; + clearTimeout(this.timer); this.loading = false; this.attach(); + }); + this.timer = setTimeout(() => { + if (generation !== this.generation) return; + this.disconnect(); + this.host.report(new ProtocolError('LOAD_TIMEOUT', 'Exhibit did not finish loading. Check the served path and reconnect.')); + }, this.loadTimeoutMs); + frame.src = url; this.changed(); + } + async attach() { + if (!this.frame || this.loading) return; + const generation = this.generation; + try { await this.host.connect(this.transport(this.frame)); } + catch (error) { if (generation === this.generation && !error.code) this.host.report(error); } + this.changed(); + } + reconnect() { + if (this.frame && !this.loading) return this.attach(); + if (this.url) this.load(this.url); + } +} diff --git a/src/ui.js b/src/ui.js index 676e2ac..dcb15b2 100644 --- a/src/ui.js +++ b/src/ui.js @@ -1,12 +1,21 @@ import { ExhibitHost } from './host.js'; import { argumentSchema } from './validation.js'; import { postMessageTransport } from './transport/post-message.js'; +import { ExhibitConnection } from './connection.js'; const host = new ExhibitHost(); const byId = id => document.getElementById(id); const json = value => JSON.stringify(value, null, 2); const rows = new Map(); -let frame = null, renderedCatalog = null; +let renderedCatalog = null; +const connection = new ExhibitConnection({ host, base: location.href, transport: postMessageTransport, changed: render, + createFrame() { + const frame = document.createElement('iframe'); frame.title = 'Connected exhibit'; + // Append only after src is assigned to avoid negotiating with the blank document. + queueMicrotask(() => { if (connection.frame === frame) byId('frame-container').append(frame); }); + return frame; + } +}); function element(tag, text, parent) { const node = document.createElement(tag); if (text !== undefined) node.textContent = text; parent?.append(node); return node; @@ -38,8 +47,15 @@ function editor(spec, label, parent) { } function buildCatalog() { rows.clear(); byId('catalog').replaceChildren(); + if (!host.catalog.length) element('p', 'Connect an exhibit to discover its controls.', byId('catalog')); + const groups = new Map(); for (const target of host.catalog) { - const section = element('article', undefined, byId('catalog')); section.className = 'target'; + const category = target.category || 'Other'; + if (!groups.has(category)) { + const group = element('div', undefined, byId('catalog')); group.className = 'target-group'; + element('h3', category, group); groups.set(category, group); + } + const section = element('article', undefined, groups.get(category)); section.className = 'target'; element('h3', target.label ?? target.id, section); element('code', target.id, section); element('p', `${target.kind} · ${target.readable ? 'readable' : 'not readable'} · ${target.writable ? 'writable' : 'not writable'}`, section); const current = element('output', '', section); @@ -85,16 +101,27 @@ function buildCatalog() { } } function render() { - byId('status').textContent = `${host.status} · ${host.sync}`; + byId('status').textContent = connection.loading ? 'Loading exhibit…' : `${host.status} · ${host.sync}`; + byId('status').dataset.state = host.status; + byId('identity').textContent = host.exhibit + ? `${host.exhibit.product} · ${host.exhibit.version ?? ''} · Contract ${host.contract.major}.${host.contract.minor}` + : 'No exhibit connected. Enter a served path to begin.'; + byId('loaded-url').textContent = connection.url ? `Exhibit location: ${connection.url}` : ''; byId('metadata').textContent = json({ sessionId: host.sessionId, contract: host.contract, exhibit: host.exhibit, registryRevision: host.registryRevision, stateRevision: host.stateRevision, sequence: host.sequence }); - byId('capabilities').textContent = json(host.capabilities); + byId('capabilities').replaceChildren(); + if (!host.capabilities.length) element('p', host.sessionId ? 'No capabilities declared.' : 'Capabilities appear after discovery.', byId('capabilities')); + for (const capability of host.capabilities) element('li', `${capability.id}: ${capability.state}`, byId('capabilities')); byId('state').textContent = json(Object.fromEntries(host.values)); byId('events').textContent = json(host.eventLog); byId('errors').textContent = json(host.logs); + const latest = host.logs.findLast(entry => entry.code !== 'SESSION'); + byId('latest-error').textContent = latest ? `Latest diagnostic (${latest.time}): ${latest.code}: ${latest.message}` : ''; + byId('latest-error').hidden = !latest; byId('target-count').textContent = `(${host.catalog.length})`; byId('refresh').disabled = host.status !== 'connected'; - byId('reconnect').disabled = !frame; + byId('reconnect').disabled = !connection.url || connection.loading || host.status === 'negotiating'; + byId('disconnect').disabled = !connection.frame; if (host.catalog !== renderedCatalog) { renderedCatalog = host.catalog; buildCatalog(); } for (const row of rows.values()) { row.current.textContent = host.values.has(row.target.id) ? `Reported value: ${json(host.values.get(row.target.id))}` : 'No persistent value reported.'; @@ -106,22 +133,14 @@ function render() { if (row.submit) row.submit.disabled = row.busy || host.status !== 'connected' || !!row.schemaError || unavailable.length > 0; } } -async function attach() { - if (!frame) return; - try { await host.connect(postMessageTransport(frame)); } catch (error) { if (!error.code) host.report(error); } -} byId('connection').addEventListener('submit', event => { event.preventDefault(); try { - const url = new URL(byId('exhibit-url').value, location.href); - if (url.origin !== location.origin || !['http:', 'https:'].includes(url.protocol)) throw new Error('Enter a same-origin HTTP exhibit URL.'); - host.disconnect(); frame?.remove(); - frame = document.createElement('iframe'); frame.title = 'Connected exhibit'; - frame.addEventListener('load', attach); frame.src = url.href; - byId('frame-container').append(frame); render(); + connection.load(byId('exhibit-url').value); } catch (error) { host.report(error); } }); -byId('reconnect').addEventListener('click', attach); -byId('disconnect').addEventListener('click', () => { host.disconnect(); frame?.remove(); frame = null; render(); }); +byId('reconnect').addEventListener('click', () => connection.reconnect()); +byId('disconnect').addEventListener('click', () => connection.disconnect()); byId('refresh').addEventListener('click', () => host.refresh().catch(error => host.report(error))); +byId('clear-errors').addEventListener('click', () => { host.logs = []; render(); }); host.subscribe(render); render(); diff --git a/src/validation.js b/src/validation.js index cd48042..407750c 100644 --- a/src/validation.js +++ b/src/validation.js @@ -92,6 +92,8 @@ export function validateCatalog(message) { && typeof t.readable === 'boolean' && typeof t.writable === 'boolean' && Array.isArray(t.requires) && t.requires.every(id => typeof id === 'string'), 'Invalid target descriptor.'); ids.add(t.id); + check(t.restorable === undefined || typeof t.restorable === 'boolean', 'Invalid target restorable metadata.'); + check(t.category === undefined || typeof t.category === 'string', 'Invalid target category metadata.'); if (t.kind === 'range') check(Number.isFinite(t.min) && Number.isFinite(t.max) && t.min <= t.max && Number.isFinite(t.step) && t.step > 0, 'Invalid range descriptor.'); if (t.kind === 'selection') check(Array.isArray(t.options) diff --git a/test-fixtures/PROVENANCE.md b/test-fixtures/PROVENANCE.md index 07195e7..f082203 100644 --- a/test-fixtures/PROVENANCE.md +++ b/test-fixtures/PROVENANCE.md @@ -20,6 +20,11 @@ Local compatibility corrections: - Planetarium's magnitude reader and native dispatch still used the old camelCase ID. They now match its existing canonical `sky.magnitude-limit` descriptor, fixing a null snapshot value and failed native writes. +- Final Step 5 closure: Haunted House's haunt-level reader now clamps to 0–1 + and rounds to integer hundredths, matching its unchanged 0.01 descriptor step. + The internal drifting simulation and Manifest behavior remain continuous; + snapshots and mutation events share this normalized reader. Previously the + reader rounded to thousandths and could report off-step values. NGN has no fixture-specific target logic. These corrections apply only to the local fixture copies. No upstream fixes or independent conformance diff --git a/test-fixtures/STEP5-VERIFICATION.md b/test-fixtures/STEP5-VERIFICATION.md new file mode 100644 index 0000000..24a10d3 --- /dev/null +++ b/test-fixtures/STEP5-VERIFICATION.md @@ -0,0 +1,147 @@ +# Step 5 repeatable verification + +Latest rerun (2026-09-14): the corrected SciFi text targets pass through the normal +UI; NGN tests pass 25/25. The browser gate currently fails at 2/3 on Haunted House's +haunt-level step, and publishing validation still fails. See completion report Q. + +Run from the NGN repository root with Node.js 22+. Use `npm start` and the normal +UI at `http://127.0.0.1:4173/`. No npm install, build, source edit, external mount, +or test harness is required for operator use. Use the same origin/port for every +exhibit. `npm test` runs the automated tests. + +## External SciFi-XZBT preparation + +SciFi-XZBT is supplied separately. The run recorded here used the actual working +copy at `G:/.vibe/SciFi-XZBT`, whose HEAD was +`179b9db715aaa92999c5bfef451b6877f30079fe` with local changes and untracked contract +files. This is NOT evidence for that commit alone or for a released dist build. +The original SHA-256 manifest `evidence/step5-scifi-files.json` identifies the +initial 12 files tested. The authorized follow-up corrected the external adapter's +two argument descriptors and matching validator. Its fresh copy is identified by +`evidence/step5-scifi-arguments-files.json`; all 12 source/copy hashes match. + +For this source layout, copy the runtime entry point and its relative assets +without modifying them. Use a fresh destination name when repeating with a newer +copy, to avoid mixing versions: + +```powershell +$exhibitSource = 'G:/.vibe/SciFi-XZBT' +$exhibitDestination = 'test-fixtures/local/scifi-arguments' +New-Item -ItemType Directory -Path $exhibitDestination +Copy-Item -LiteralPath (Join-Path $exhibitSource 'index.html') -Destination $exhibitDestination +Copy-Item -LiteralPath (Join-Path $exhibitSource 'js') -Destination $exhibitDestination -Recurse +Copy-Item -LiteralPath (Join-Path $exhibitSource 'css') -Destination $exhibitDestination -Recurse +``` + +`test-fixtures/local/` is ignored by Git. Do not copy the external repository's +credentials, .git, or development tooling into a served directory. A future +self-contained distributable can instead be placed under `public/` unchanged. +The tested source copy uses only index.html, js/ and css/ for this workflow. + +Enter `/test-fixtures/local/scifi-arguments/index.html` in the **normal NGN UI**, then +select **Load exhibit**. Expected identity: SciFi-XZBT 5.2.0, Contract 5.2, +61 discovered targets. Inspect Session and exhibit metadata and Current +persistent state. Controls below refer to labels discovered from this copy; +they are test instructions, not product configuration. + +1. Set Master Volume to 0.42; verify reported value and state.changed. +2. Set Master Mute true; verify reported value. +3. Set Observation Viewport Active true, then false; verify reported values + and native exhibit behavior within its single frame. +4. Select Active Audio Preset / Interprise-G: Main Engineering; verify + preset.selected and related changes share one resulting revision. +5. Invoke Comm Badge Chime; verify action.executed (acceptance, not audio fidelity). +6. Invoke Demat Cycle Impulse while in Starfleet; expect CAPABILITY_UNAVAILABLE. +7. Select Active Sci-Fi Universe / Whataverse / Tardix, then invoke Demat Cycle + Impulse; verify action.executed in that context. +8. Inspect Speak Text Line and Display Ticker Message descriptors. A conformant + version declares an `arguments` array and NGN shows its text field. Enter text + and invoke; verify acceptance/event. **The corrected copy passes this check:** + both fields appear and action.executed contains the exact submitted args.text. + Speech declares maxLength 2000, ticker 512, both required strings. Correct the + external source before copying; do not add a host workaround or patch only the + served copy. The original copy's legacy args.text failure remains in old evidence. +9. Inspect Incoming events: source, correlationId, monotonically increasing + sequence and stateRevision. Reconnect while attached: new session, cleared + event log/sequence baseline, same exhibit state and revision. +10. Disconnect: no frame/catalog/cache remains. Reconnect: URL reloads, a new + instance and session are discovered. Initial state/revision may differ from + the removed instance. No source changes required. + +Classify failures before changing NGN: host defect, external exhibit contract +defect, conformant but awkward behavior, or environment limitation. Sound +acceptance/events are covered; audible output and optional AI downloads are not +certified. Text-argument failure blocks Step 5 acceptance. + +## Reference regression through the normal UI + +Use these paths, changing only the URL field: + +| Exhibit | URL | Representative operations | +| --- | --- | --- | +| Aquarium | /test-fixtures/reference-exhibits/aquarium/index.html | Population 16, Paused true, Species Blue Tang, Feed | +| Planetarium | /test-fixtures/reference-exhibits/planetarium/index.html | Azimuth 0.75, Sky frozen true, Constellation Cygnus, Advance time with Include minutes checked and minutes 30 | +| Haunted House | /test-fixtures/reference-exhibits/haunted-house/index.html | Haunt level 0.5, Held true, Room Library, Manifest | + +Inspect reports and events, reconnect and confirm a new session. Disconnect and +reload the remembered URL. Aquarium and Planetarium pass the representative +flows. Haunted House emits Manifest's action event, but during the effect a +snapshot can contain a haunt-level value off its declared 0.01 step; NGN reports +INVALID_MESSAGE and preserves its previous cache. This is a pre-existing fixture +limitation, not a new host regression. No fixture repair was made in Step 5. + +The existing transport suite at `/test-fixtures/host-verification.html` remains a +separate regression check: select Run verification, expect 3/3, and save the +visible timestamped text or browser console output. The latest rerun produced +2/3, failing Haunted House's step validation. Its first impulse with no capability +requirements is Manifest, and the fixture's continuous drift can also produce +off-step snapshots. The earlier statement that this suite avoids Manifest was +incorrect. Do not reinterpret a historical 3/3 as the current result or as complete +fixture conformance certification. + +## Failure and cancellation checks + +- Start with no URL: useful disconnected controls/empty catalog. +- Enter an off-origin URL: reject and preserve the current exhibit. +- Enter a missing local URL: document-load timeout (15 seconds in the checked + browser), or hello timeout (5 seconds if an error page finishes loading). + Enter a valid path to recover. +- Disconnect during loading or switch rapidly: old frame load callbacks must + not attach to the new exhibit. Focused automated lifecycle tests cover this. +- Refresh state reads current values. Logs can be cleared; prior diagnostics + are timestamped and do not imply the current connection is failed. + +## Evidence and repeatability limits + +`evidence/step5-reference-suite.txt` is the actual timestamped browser suite output. +`step5-scifi-session.json` contains the actual discovered catalog, session metadata, +events and failures. The three reference JSON files contain DOM-rendered runtime +metadata/events; Haunted House also includes its errors. `step5-operator.jpg` +shows the operator panel. Evidence was captured in local Chromium on 2026-09-14. + +A fresh `npm start` succeeded on port 4175 because an existing service occupied +4173; the final browser transport suite ran on that fresh process. The normal UI +ran on 4173. No separate pristine checkout was created; tests/startup use the +current source tree without build products. SciFi repeatability requires the +external files described above. Repository publishing validation has an existing +malformed trailing record block in the Post Step 4 Completion devlog; see report. + +Follow-up evidence is separate from those historical artifacts: +`step5-scifi-arguments-ui.json` captures the corrected descriptors, text fields, +and successful events; `step5-scifi-arguments-files.json` identifies the tested +working copy; `step5-acceptance-rerun-browser.txt` records the current 2/3 failure. +`step5-scifi-arguments-ui-final.json` records the repeated checks after line-ending +cleanup, including a draft reset on speech capability rediscovery and successful +ticker resubmission with the exact intended text. +The follow-up started a fresh server on 4173 and performed the requested narrow +SciFi checks, rather than repeating every earlier operator case above. + +The optional external validator regression check is: + +```powershell +node test-fixtures/check-scifi-arguments.cjs G:/.vibe/SciFi-XZBT/js/contract-adapter.js +``` + +It checks descriptor arrays, required/type/unknown-key/length rejection before +execution, maximum-length acceptance, and unchanged argumentless behavior. It +supplements the real operator UI checks; it does not replace them. diff --git a/test-fixtures/check-scifi-arguments.cjs b/test-fixtures/check-scifi-arguments.cjs new file mode 100644 index 0000000..21710b4 --- /dev/null +++ b/test-fixtures/check-scifi-arguments.cjs @@ -0,0 +1,38 @@ +// Optional external-exhibit regression check; not NGN product configuration. +// Usage: node test-fixtures/check-scifi-arguments.cjs +const fs = require('node:fs'); +const vm = require('node:vm'); +const assert = require('node:assert/strict'); + +async function main() { + const context = { window: {}, console }; + vm.createContext(context); + vm.runInContext(fs.readFileSync(process.argv[2], 'utf8'), context); + const calls = []; + const adapter = new context.window.XZBTContractAdapter({ bindings: { invokers: { + 'speech.say': args => calls.push(args), + 'display.ticker': args => calls.push(args), + 'speech.generate-announcement': args => calls.push(args) + } } }); + for (const [id, maxLength] of [['speech.say', 2000], ['display.ticker', 512]]) { + const descriptor = adapter.targets.get(id); + assert.equal(Object.hasOwn(descriptor, 'args'), false); + assert.deepEqual(JSON.parse(JSON.stringify(descriptor.arguments)), [ + { name: 'text', type: 'string', required: true, maxLength } + ]); + for (const args of [{}, { text: null }, { text: 42 }, { text: 'x', extra: true }, { text: 'x'.repeat(maxLength + 1) }]) { + const before = calls.length; + const result = await adapter.invokeAction(id, args); + assert.equal(result.code, 'INVALID_VALUE'); + assert.equal(calls.length, before, 'invalid arguments must not execute'); + } + const text = 'x'.repeat(maxLength); + assert.equal((await adapter.invokeAction(id, { text })).ok, true); + assert.equal(calls.at(-1).text, text); + console.log(`PASS ${id}: arguments metadata, required/type/unknown/length rejection, exact length accepted`); + } + assert.equal((await adapter.invokeAction('speech.generate-announcement', {})).ok, true); + assert.equal((await adapter.invokeAction('speech.generate-announcement', { text: 'x' })).code, 'INVALID_VALUE'); + console.log('PASS argumentless impulse: empty args accepted, unknown key rejected'); +} +main().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/test-fixtures/evidence/step5-acceptance-rerun-browser.txt b/test-fixtures/evidence/step5-acceptance-rerun-browser.txt new file mode 100644 index 0000000..adb1a20 --- /dev/null +++ b/test-fixtures/evidence/step5-acceptance-rerun-browser.txt @@ -0,0 +1,7 @@ +Started: 2026-09-14T19:41:45.445Z +PASS aquarium: 12 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +PASS planetarium: 14 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +FAIL haunted-house: room.haunt-level: Value must follow step 0.01. +2/3 real exhibits passed. +Finished: 2026-09-14T19:41:45.575Z + diff --git a/test-fixtures/evidence/step5-aquarium.json b/test-fixtures/evidence/step5-aquarium.json new file mode 100644 index 0000000..4ad1feb --- /dev/null +++ b/test-fixtures/evidence/step5-aquarium.json @@ -0,0 +1,66 @@ +{ + "events": [ + { + "correlationId": "ngn-2-4", + "sequence": 1, + "sessionId": "sess-1a192", + "source": "host", + "stateRevision": 1, + "target": "tank.population", + "timestamp": 1789398588359, + "type": "state.changed", + "value": 16, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-6", + "sequence": 2, + "sessionId": "sess-1a192", + "source": "host", + "stateRevision": 2, + "target": "tank.paused", + "timestamp": 1789398588908, + "type": "state.changed", + "value": true, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-8", + "sequence": 3, + "sessionId": "sess-1a192", + "source": "host", + "stateRevision": 3, + "target": "tank.species", + "timestamp": 1789398589178, + "type": "selection.changed", + "value": "blue-tang", + "xzbt": "5.2" + }, + { + "args": {}, + "correlationId": "ngn-2-10", + "sequence": 4, + "sessionId": "sess-1a192", + "source": "host", + "target": "action.feed", + "timestamp": 1789398589436, + "type": "action.executed", + "xzbt": "5.2" + } + ], + "metadata": { + "contract": { + "major": 5, + "minor": 2 + }, + "exhibit": { + "build": "reference-exhibit", + "product": "Aquarium", + "version": "1.0.0" + }, + "registryRevision": 1, + "sequence": 4, + "sessionId": "sess-1a192", + "stateRevision": 3 + } +} \ No newline at end of file diff --git a/test-fixtures/evidence/step5-closure-browser-suite.txt b/test-fixtures/evidence/step5-closure-browser-suite.txt new file mode 100644 index 0000000..1324d83 --- /dev/null +++ b/test-fixtures/evidence/step5-closure-browser-suite.txt @@ -0,0 +1,7 @@ +Started: 2026-09-14T19:55:22.193Z +PASS aquarium: 12 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +PASS planetarium: 14 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +PASS haunted-house: 13 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +3/3 real exhibits passed. +Finished: 2026-09-14T19:55:22.342Z + diff --git a/test-fixtures/evidence/step5-closure-checks.json b/test-fixtures/evidence/step5-closure-checks.json new file mode 100644 index 0000000..181b2bd --- /dev/null +++ b/test-fixtures/evidence/step5-closure-checks.json @@ -0,0 +1,25 @@ +{ + "capturedAt": "2026-09-14T19:56:36.172Z", + "checks": [ + { + "status": "fulfilled", + "value": { + "chunk_id": "82977d", + "wall_time_seconds": 1.3615515999999999, + "exit_code": 0, + "original_token_count": 585, + "output": "\n> xzbt-ngn@0.1.0 test\n> node --test\n\n✔ connection accepts served paths and same-origin URLs, rejects unsupported locations (1.7406ms)\n✔ switch and disconnect cancel old frame loads; invalid URL preserves active frame (1.1493ms)\n✔ reconnect negotiates existing frame and reloads remembered URL after disconnect (0.4236ms)\n✔ stalled document load ends with recoverable error (20.0644ms)\n✔ optional descriptor metadata is permissive when omitted and typed when supplied (1.4027ms)\n✔ Haunted House reports bounded hundredths while preserving continuous drift (28.2601ms)\n✔ Haunted House snapshots and mutation events stay valid through Manifest, drift and Calm (11.0154ms)\n✔ negotiates, discovers and reads real session fields (4.696ms)\n✔ sets all persistent kinds, invokes arguments, preserves no-op revisions (1.6233ms)\n✔ invalid values and required/unknown arguments never reach transport (1.4478ms)\n✔ argument type/enum/bounds/step/length rules and unsupported future types (0.5322ms)\n✔ surfaces exhibit errors and invalid session requires reconnect (0.9883ms)\n✔ unsupported version and timeout are explicit failures (11.4367ms)\n✔ native events update cache; shared revision is not a gap (0.8535ms)\n✔ sequence and revision gaps resync and remain visible (26.0243ms)\n✔ capability and registry changes debounce discovery; normal state does not (52.8678ms)\n✔ events arriving during snapshot are replayed without lost updates (0.6791ms)\n✔ old session traffic does not mutate new cache (0.4186ms)\n✔ malformed persistent events trigger recovery (25.6604ms)\n✔ postMessage verifies origin and source and cleans up (0.4321ms)\n✔ local server serves host and denies repository/private paths (41.9438ms)\n✔ invalid reported types do not contaminate the cache (26.2967ms)\n✔ invalid snapshot values preserve previous cache and mark uncertainty (0.6139ms)\n✔ snapshot revision cannot roll back a previously observed transaction (0.322ms)\n✔ registry rediscovery adds descriptors and removes obsolete cached targets (25.7427ms)\n✔ unsupported future impulse arguments do not hide the catalog (0.7149ms)\n✔ capability requirements block unusable services without deleting targets (0.4759ms)\nℹ tests 27\nℹ suites 0\nℹ pass 27\nℹ fail 0\nℹ cancelled 0\nℹ skipped 0\nℹ todo 0\nℹ duration_ms 388.0334\n" + } + }, + { + "status": "fulfilled", + "value": { + "chunk_id": "9c0253", + "wall_time_seconds": 31.5457821, + "exit_code": 0, + "original_token_count": 8, + "output": "devlog-editor: devlog is valid\r\n" + } + } + ] +} diff --git a/test-fixtures/evidence/step5-closure-haunted-house.json b/test-fixtures/evidence/step5-closure-haunted-house.json new file mode 100644 index 0000000..1ad1afd --- /dev/null +++ b/test-fixtures/evidence/step5-closure-haunted-house.json @@ -0,0 +1,17 @@ +{ + "capturedAt": "2026-09-14T19:57:02.871Z", + "checks": [ + { + "phase": "held Manifest", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/reference-exhibits/haunted-house/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: Haunted House · 1.0.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/reference-exhibits/haunted-house/index.html\"\n - iframe:\n - banner:\n - heading \"Haunted House\" [level=1]\n - generic: Fifth room — live haunt\n - generic: host attached\n - main:\n - generic \"Haunted house room view\"\n - complementary:\n - heading \"Presence\" [level=2]\n - generic: Room\n - combobox \"Room\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - generic: Spirit\n - combobox \"Spirit\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - generic: Haunt level\n - generic: 100%\n - slider \"Haunt level 100%\"\n - heading \"Atmosphere\" [level=2]\n - generic: Fog density\n - generic: 25%\n - slider \"Fog density 25%\"\n - generic: Candlelight\n - generic: 55%\n - slider \"Candlelight 55%\"\n - text: \"Audio engine:\"\n - strong: available\n - button \"Audio on\" [pressed]\n - generic: Simulation\n - button \"Resume\" [pressed]\n - heading \"Actions\" [level=2]\n - button \"Moan\"\n - button \"Slam door\"\n - button \"Manifest\"\n - button \"Calm\"\n - contentinfo:\n - generic: House\n - generic: The apparition manifests fully.\n - heading \"Exhibit controls (13)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"room\" [level=3]\n - article:\n - heading \"Room\" [level=3]\n - code: room.current\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"library\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - button \"Set\"\n - article:\n - heading \"Spirit\" [level=3]\n - code: room.spirit\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"restless\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - button \"Set\"\n - article:\n - heading \"Haunt level\" [level=3]\n - code: room.haunt-level\n - paragraph: range · readable · writable\n - status: \"Reported value: 1\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.5\"\n - button \"Set\"\n - article:\n - heading \"Fog density\" [level=3]\n - code: room.fog-density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.25\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.25\"\n - button \"Set\"\n - article:\n - heading \"Candlelight\" [level=3]\n - code: room.candlelight\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Audio enabled\" [level=3]\n - code: room.audio-enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - article:\n - heading \"Held\" [level=3]\n - code: room.paused\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"action\" [level=3]\n - article:\n - heading \"Moan\" [level=3]\n - code: action.moan\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Slam door\" [level=3]\n - code: action.slam-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Manifest\" [level=3]\n - code: action.manifest\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Calm\" [level=3]\n - code: action.calm\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"telemetry\" [level=3]\n - article:\n - heading \"Audio engine state\" [level=3]\n - code: telemetry.audio-state\n - paragraph: state · readable · not writable\n - status: \"Reported value: \\\"available\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - article:\n - heading \"Room damping\" [level=3]\n - code: telemetry.room-damp\n - paragraph: state · readable · not writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\"\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-12844\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"Haunted House\\\", \\\"version\\\": \\\"1.0.0\\\", \\\"build\\\": \\\"reference-exhibit\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 3, \\\"sequence\\\": 4 }\"\n - generic \"Current persistent state\" [active]\n - paragraph: Last reported exhibit values. This host view may be uncertain while resynchronizing.\n - generic: \"{ \\\"room.current\\\": \\\"library\\\", \\\"room.spirit\\\": \\\"restless\\\", \\\"room.haunt-level\\\": 1, \\\"room.fog-density\\\": 0.25, \\\"room.candlelight\\\": 0.55, \\\"room.audio-enabled\\\": true, \\\"room.paused\\\": true, \\\"telemetry.audio-state\\\": \\\"available\\\", \\\"telemetry.room-damp\\\": 0.6 }\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[ { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 1, \\\"timestamp\\\": 1789415741975, \\\"stateRevision\\\": 1, \\\"target\\\": \\\"room.paused\\\", \\\"value\\\": true, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-4\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 2, \\\"timestamp\\\": 1789415742259, \\\"stateRevision\\\": 2, \\\"target\\\": \\\"room.haunt-level\\\", \\\"value\\\": 0.5, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-6\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"selection.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 3, \\\"timestamp\\\": 1789415742544, \\\"stateRevision\\\": 3, \\\"target\\\": \\\"room.current\\\", \\\"value\\\": \\\"library\\\", \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-8\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 4, \\\"timestamp\\\": 1789415793706, \\\"target\\\": \\\"action.manifest\\\", \\\"args\\\": {}, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-10\\\" } ]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:55:08.301Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-12844\\\" } } ]\"" + }, + { + "phase": "drifting after Manifest", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/reference-exhibits/haunted-house/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: Haunted House · 1.0.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/reference-exhibits/haunted-house/index.html\"\n - iframe:\n - banner:\n - heading \"Haunted House\" [level=1]\n - generic: Fifth room — live haunt\n - generic: host attached\n - main:\n - generic \"Haunted house room view\"\n - complementary:\n - heading \"Presence\" [level=2]\n - generic: Room\n - combobox \"Room\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - generic: Spirit\n - combobox \"Spirit\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - generic: Haunt level\n - generic: 100%\n - slider \"Haunt level 100%\"\n - heading \"Atmosphere\" [level=2]\n - generic: Fog density\n - generic: 25%\n - slider \"Fog density 25%\"\n - generic: Candlelight\n - generic: 55%\n - slider \"Candlelight 55%\"\n - text: \"Audio engine:\"\n - strong: available\n - button \"Audio on\" [pressed]\n - generic: Simulation\n - button \"Hold\"\n - heading \"Actions\" [level=2]\n - button \"Moan\"\n - button \"Slam door\"\n - button \"Manifest\"\n - button \"Calm\"\n - contentinfo:\n - generic: House\n - generic: The house is quiet — awaiting instruction.\n - heading \"Exhibit controls (13)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"room\" [level=3]\n - article:\n - heading \"Room\" [level=3]\n - code: room.current\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"library\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - button \"Set\"\n - article:\n - heading \"Spirit\" [level=3]\n - code: room.spirit\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"restless\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - button \"Set\"\n - article:\n - heading \"Haunt level\" [level=3]\n - code: room.haunt-level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.26\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.5\"\n - button \"Set\"\n - article:\n - heading \"Fog density\" [level=3]\n - code: room.fog-density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.25\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.25\"\n - button \"Set\"\n - article:\n - heading \"Candlelight\" [level=3]\n - code: room.candlelight\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Audio enabled\" [level=3]\n - code: room.audio-enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - article:\n - heading \"Held\" [level=3]\n - code: room.paused\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - heading \"action\" [level=3]\n - article:\n - heading \"Moan\" [level=3]\n - code: action.moan\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Slam door\" [level=3]\n - code: action.slam-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Manifest\" [level=3]\n - code: action.manifest\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Calm\" [level=3]\n - code: action.calm\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"telemetry\" [level=3]\n - article:\n - heading \"Audio engine state\" [level=3]\n - code: telemetry.audio-state\n - paragraph: state · readable · not writable\n - status: \"Reported value: \\\"available\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - article:\n - heading \"Room damping\" [level=3]\n - code: telemetry.room-damp\n - paragraph: state · readable · not writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\" [active]\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-12844\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"Haunted House\\\", \\\"version\\\": \\\"1.0.0\\\", \\\"build\\\": \\\"reference-exhibit\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 4, \\\"sequence\\\": 5 }\"\n - generic \"Current persistent state\"\n - paragraph: Last reported exhibit values. This host view may be uncertain while resynchronizing.\n - generic: \"{ \\\"room.current\\\": \\\"library\\\", \\\"room.spirit\\\": \\\"restless\\\", \\\"room.haunt-level\\\": 0.26, \\\"room.fog-density\\\": 0.25, \\\"room.candlelight\\\": 0.55, \\\"room.audio-enabled\\\": true, \\\"room.paused\\\": false, \\\"telemetry.audio-state\\\": \\\"available\\\", \\\"telemetry.room-damp\\\": 0.6 }\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[ { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 1, \\\"timestamp\\\": 1789415741975, \\\"stateRevision\\\": 1, \\\"target\\\": \\\"room.paused\\\", \\\"value\\\": true, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-4\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 2, \\\"timestamp\\\": 1789415742259, \\\"stateRevision\\\": 2, \\\"target\\\": \\\"room.haunt-level\\\", \\\"value\\\": 0.5, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-6\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"selection.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 3, \\\"timestamp\\\": 1789415742544, \\\"stateRevision\\\": 3, \\\"target\\\": \\\"room.current\\\", \\\"value\\\": \\\"library\\\", \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-8\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 4, \\\"timestamp\\\": 1789415793706, \\\"target\\\": \\\"action.manifest\\\", \\\"args\\\": {}, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-10\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-12844\\\", \\\"sequence\\\": 5, \\\"timestamp\\\": 1789415795623, \\\"stateRevision\\\": 4, \\\"target\\\": \\\"room.paused\\\", \\\"value\\\": false, \\\"source\\\": \\\"host\\\", \\\"correlationId\\\": \\\"ngn-2-13\\\" } ]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:55:08.301Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-12844\\\" } } ]\"" + }, + { + "phase": "reconnected", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/reference-exhibits/haunted-house/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: Haunted House · 1.0.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/reference-exhibits/haunted-house/index.html\"\n - iframe:\n - banner:\n - heading \"Haunted House\" [level=1]\n - generic: Fifth room — live haunt\n - generic: host attached\n - main:\n - generic \"Haunted house room view\"\n - complementary:\n - heading \"Presence\" [level=2]\n - generic: Room\n - combobox \"Room\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - generic: Spirit\n - combobox \"Spirit\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - generic: Haunt level\n - generic: 100%\n - slider \"Haunt level 100%\"\n - heading \"Atmosphere\" [level=2]\n - generic: Fog density\n - generic: 25%\n - slider \"Fog density 25%\"\n - generic: Candlelight\n - generic: 55%\n - slider \"Candlelight 55%\"\n - text: \"Audio engine:\"\n - strong: available\n - button \"Audio on\" [pressed]\n - generic: Simulation\n - button \"Hold\"\n - heading \"Actions\" [level=2]\n - button \"Moan\"\n - button \"Slam door\"\n - button \"Manifest\"\n - button \"Calm\"\n - contentinfo:\n - generic: House\n - generic: The house is quiet — awaiting instruction.\n - heading \"Exhibit controls (13)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"room\" [level=3]\n - article:\n - heading \"Room\" [level=3]\n - code: room.current\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"library\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Foyer\"\n - option \"Library\" [selected]\n - option \"Cellar\"\n - option \"Attic\"\n - option \"Nursery\"\n - button \"Set\"\n - article:\n - heading \"Spirit\" [level=3]\n - code: room.spirit\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"restless\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Restless\" [selected]\n - option \"Vengeful\"\n - option \"Mourning\"\n - option \"Mischievous\"\n - button \"Set\"\n - article:\n - heading \"Haunt level\" [level=3]\n - code: room.haunt-level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.26\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.26\"\n - button \"Set\"\n - article:\n - heading \"Fog density\" [level=3]\n - code: room.fog-density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.25\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.25\"\n - button \"Set\"\n - article:\n - heading \"Candlelight\" [level=3]\n - code: room.candlelight\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Audio enabled\" [level=3]\n - code: room.audio-enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - article:\n - heading \"Held\" [level=3]\n - code: room.paused\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - heading \"action\" [level=3]\n - article:\n - heading \"Moan\" [level=3]\n - code: action.moan\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Slam door\" [level=3]\n - code: action.slam-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Manifest\" [level=3]\n - code: action.manifest\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Calm\" [level=3]\n - code: action.calm\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"telemetry\" [level=3]\n - article:\n - heading \"Audio engine state\" [level=3]\n - code: telemetry.audio-state\n - paragraph: state · readable · not writable\n - status: \"Reported value: \\\"available\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - article:\n - heading \"Room damping\" [level=3]\n - code: telemetry.room-damp\n - paragraph: state · readable · not writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\"\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-29bfc\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"Haunted House\\\", \\\"version\\\": \\\"1.0.0\\\", \\\"build\\\": \\\"reference-exhibit\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 4, \\\"sequence\\\": null }\"\n - generic \"Current persistent state\"\n - paragraph: Last reported exhibit values. This host view may be uncertain while resynchronizing.\n - generic: \"{ \\\"room.current\\\": \\\"library\\\", \\\"room.spirit\\\": \\\"restless\\\", \\\"room.haunt-level\\\": 0.26, \\\"room.fog-density\\\": 0.25, \\\"room.candlelight\\\": 0.55, \\\"room.audio-enabled\\\": true, \\\"room.paused\\\": false, \\\"telemetry.audio-state\\\": \\\"available\\\", \\\"telemetry.room-damp\\\": 0.6 }\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:55:08.301Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-12844\\\" } }, { \\\"time\\\": \\\"2026-09-14T19:56:46.979Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-29bfc\\\" } } ]\"" + } + ] +} diff --git a/test-fixtures/evidence/step5-closure-scifi.json b/test-fixtures/evidence/step5-closure-scifi.json new file mode 100644 index 0000000..6e90d26 --- /dev/null +++ b/test-fixtures/evidence/step5-closure-scifi.json @@ -0,0 +1,4 @@ +{ + "capturedAt": "2026-09-14T19:57:29.131Z", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/local/scifi-arguments/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: SciFi-XZBT · 5.2.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/local/scifi-arguments/index.html\"\n - iframe:\n - banner:\n - generic \"Click to Change Sci-Fi Universe\":\n - generic: STARFLIGHT\n - generic: ▼\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - button \"MUTE\"\n - generic: 75%\n - generic: STARFLIGHT SOUND MATRIX v2.9\n - main:\n - complementary:\n - generic: STARSHIP PRESETS\n - 'button \"Interprise-G: Main Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Main Engineering STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Engineering\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Crew Quarters STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Crew Quarters\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Class-N Worp Core STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Class-N Worp Core\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Defiance: Tactical Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Defiance: Tactical Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Deep Space 8: Ops Center STARFLIGHT COMMAND\"':\n - generic: \"Deep Space 8: Ops Center\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Bridge (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Bridge (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Engineering (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Engineering (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NS-01: Command Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise NS-01: Command Bridge\"\n - generic: STARFLIGHT COMMAND\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - generic: Warm, low-frequency 50Hz hull tone with gentle ventilation and soft LCARD computer chirps.\n - generic: AUDIO ENGINE\n - generic: STANDBY\n - generic: CORE FREQ\n - generic: 58.0 Hz\n - button \"▶ ENGAGE\":\n - generic: ▶\n - generic: ENGAGE\n - button \"WARP JUMP\"\n - button \"RED ALERT\"\n - button \"YELLOW ALERT\"\n - button \"WATCH EXPERIENCE\"\n - generic: Full-screen procedural visuals, soundscape, telemetry, events, and optional local AI announcements.\n - 'generic \"Toggle starship viewport window frame (ON: framed starship lounge / OFF: pure deep space)\"':\n - generic: VIEWPORT\n - checkbox \"Toggle Observation Viewport Frame\" [checked]\n - generic: \"ON\"\n - 'generic \"Toggle vertical structural window pillars / mullions (OFF: unobstructed panoramic view / ON: structural posts)\"':\n - generic: PILLARS\n - checkbox \"Toggle Window Pillars\"\n - generic: \"OFF\"\n - generic \"Controls how frequently and intensely Observation generates transient visual activity\":\n - generic: ACTIVITY\n - slider \"Observation activity level\"\n - generic: 60%\n - generic \"Keep OBSERVATION profile/status information visible. Turn off to fade it out over 30 seconds.\":\n - generic: HUD HOLD\n - checkbox \"Keep Observation information visible\" [checked]\n - generic: \"ON\"\n - generic: MAIN MASTER VOLUME\n - generic: 75%\n - generic: \"-2.5 dB\"\n - button \"MUTE\"\n - button \"25%\"\n - button \"50%\"\n - button \"75%\"\n - button \"MAX\"\n - button \"−\"\n - slider \"Main Volume\"\n - button \"+\"\n - generic: FNC // FUTURISTIC NOICE CANCELLATION\n - generic: ACTIVE\n - button \"FNC ENABLED\" [pressed]\n - generic: FNC LEVEL\n - slider \"Futuristic Noice Cancellation level\"\n - generic: 65%\n - generic: Comfort suppression active — Life Support 83%, Hull 49%, Worp Core 34% reduction.\n - generic: ACOUSTIC FREQUENCY SPECTRUM\n - generic: PROCEDURAL HARMONIC ANALYSIS\n - generic: \"SPECTRUM BIN: 32 CH // REAL-TIME FFT\"\n - generic: PROCEDURAL SOUND ENGINE MIXER\n - generic: LIVE PARAMETER MODULATION\n - generic: 1. HULL DRONE\n - generic: 65%\n - generic: LEVEL\n - slider\n - generic: BASE TONE\n - generic: 50 Hz\n - slider\n - generic: DAMPING CUTOFF\n - generic: 105 Hz\n - slider\n - generic: 2. WORP CORE\n - generic: 35%\n - generic: LEVEL\n - slider\n - generic: PULSE RATE (BPM)\n - generic: 48 BPM\n - slider\n - generic: REACTOR PITCH\n - generic: 58 Hz\n - slider\n - generic: 3. LIFE SUPPORT\n - generic: 55%\n - generic: LEVEL\n - slider\n - generic: AIRFLOW AIR FILTER\n - generic: 1600 Hz\n - slider\n - generic: 4. TELEMETRY\n - generic: 45%\n - generic: LEVEL\n - slider\n - generic: BACKGROUND CHIRP DENSITY\n - generic: 65%\n - slider\n - complementary:\n - generic: INTERMIX CASCADE\n - generic: INTERMIX CHAMBER // ACTIVE\n - generic: GENERATIVE EXPERIENCE\n - generic: LOCAL WEBLLM + KOKORO TTS\n - generic: LOCAL GENERATIVE SYSTEM\n - generic: STANDBY\n - text: LANGUAGE MODEL\n - text: Qwen3-1.7B-q4f16_1-MLC\n - text: Automatically prepared when the experience starts. No model selection required.\n - text: WEBLLM\n - generic: \"OFF\"\n - text: KOKORO\n - generic: \"OFF\"\n - button \"PREPARE AI\"\n - button \"GENERATE NOW\" [disabled]\n - checkbox \"AUTO UPDATES\" [checked]\n - text: AUTO UPDATES\n - checkbox \"SPEAK\" [checked]\n - text: SPEAK\n - combobox \"Kokoro voice\":\n - option \"Kokoro voice loads on preparation\" [selected]\n - generic: VOICE AUTOMATION\n - generic: 84%\n - slider \"Robotic voice effect intensity\"\n - generic: AUTO CADENCE\n - generic: 90-180 SEC\n - slider\n - generic: AI and neural voice models load only when needed and are cached by the browser when supported.\n - generic: GENERATIVE CHANNEL STANDBY\n - generic: TV PRESENTATION\n - generic: CAST-FRIENDLY 16:9\n - checkbox \"LOCK EXPERIENCE TO 16:9\" [checked]\n - text: LOCK EXPERIENCE TO 16:9\n - generic: RENDER DENSITY\n - generic: 1080P TARGET\n - combobox:\n - option \"720P PERFORMANCE\"\n - option \"1080P STANDARD\" [selected]\n - option \"1440P HIGH\"\n - option \"2160P ULTRA\"\n - generic: The scene keeps a canonical 16:9 layout on phones, PCs, and cast displays. Chrome still negotiates the final Cast stream resolution.\n - generic: SLEEP TIMER\n - generic: TIMER INACTIVE (CONTINUOUS)\n - button \"15M\"\n - button \"30M\"\n - button \"60M\"\n - button \"OFF\"\n - generic: LCARD KEYPAD\n - button \"COM BADGE CHIRP\"\n - button \"DOOR PNEUMATIC SWISH\"\n - button \"BOSUN'S PIPE WHISTLE\"\n - button \"MEDBAY ECG MONITOR\"\n - button \"CARDASSIAN BULKHEAD DOOR\"\n - button \"TURBOCHUTE WHOOSH\"\n - button \"FABRICATOR SHIMMER\"\n - button \"TRANSLOCATOR CYCLE\"\n - button \"LCARD SINGLE CHIRP\"\n - contentinfo:\n - generic: \"SYSTEM STATUS: NORMAL\"\n - link \"VIBE ENGINEERED BY LABYRICORN STUDIOS\":\n - /url: https://www.labyricorn.com/\n - generic: \"SPACE: PLAY/PAUSE | M: MUTE | ↑/↓: VOLUME\"\n - heading \"Exhibit controls (61)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"audio\" [level=3]\n - article:\n - heading \"Master Volume\" [level=3]\n - code: mix.master\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.75\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.75\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Level\" [level=3]\n - code: mix.hull.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Frequency\" [level=3]\n - code: mix.hull.frequency\n - paragraph: range · readable · writable\n - status: \"Reported value: 50\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"50\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Cutoff\" [level=3]\n - code: mix.hull.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 105\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"105\"\n - button \"Set\"\n - article:\n - heading \"Drive / Core Level\" [level=3]\n - code: mix.drive.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.35\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.35\"\n - button \"Set\"\n - article:\n - heading \"Drive Pulse Rate\" [level=3]\n - code: mix.drive.pulse-rate\n - paragraph: range · readable · writable\n - status: \"Reported value: 48\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"48\"\n - button \"Set\"\n - article:\n - heading \"Drive Carrier Frequency\" [level=3]\n - code: mix.drive.carrier\n - paragraph: range · readable · writable\n - status: \"Reported value: 58\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"58\"\n - button \"Set\"\n - article:\n - heading \"Environmental Airflow Level\" [level=3]\n - code: mix.environment.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Environmental Filter Cutoff\" [level=3]\n - code: mix.environment.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 1600\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"1600\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Level\" [level=3]\n - code: mix.telemetry.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.45\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.45\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Density\" [level=3]\n - code: mix.telemetry.density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Level\" [level=3]\n - code: fnc.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Active\" [level=3]\n - code: fnc.enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"speech\" [level=3]\n - article:\n - heading \"Speech Robotic Modulation\" [level=3]\n - code: speech.robot-amount\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.84\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.84\"\n - button \"Set\"\n - article:\n - heading \"Speak Text Line\" [level=3]\n - code: speech.say\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - article:\n - heading \"Generate Ambient AI Announcement\" [level=3]\n - code: speech.generate-announcement\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"observation\" [level=3]\n - article:\n - heading \"Observation Visual Activity\" [level=3]\n - code: view.activity\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.6\"\n - button \"Set\"\n - article:\n - heading \"Observation Viewport Active\" [level=3]\n - code: view.observation\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - article:\n - heading \"Starship Bezel Frame Framing\" [level=3]\n - code: view.viewport-frame\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"transport\" [level=3]\n - article:\n - heading \"Audio Engine Online\" [level=3]\n - code: transport.playing\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - article:\n - heading \"Master Mute\" [level=3]\n - code: transport.muted\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - heading \"alert\" [level=3]\n - article:\n - heading \"Active Tactical Alert Status\" [level=3]\n - code: alert.active\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"none\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Condition Green (Normal)\" [selected]\n - option \"Yellow Alert\"\n - option \"Red Alert\"\n - button \"Set\"\n - heading \"universe\" [level=3]\n - article:\n - heading \"Active Sci-Fi Universe\" [level=3]\n - code: universe.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"starfleet\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Starfleet / Galaxy\" [selected]\n - option \"Whataverse / Tardix\"\n - option \"Industrial / Weyland\"\n - option \"Bioships / Leviathan\"\n - option \"Retro Future / 1960s\"\n - option \"Colonial Military\"\n - option \"Deep Space Generation\"\n - option \"Frontier & Salvage\"\n - option \"Orbital Stations & Docks\"\n - option \"Whimsical Fleet\"\n - button \"Set\"\n - heading \"preset\" [level=3]\n - article:\n - heading \"Active Audio Preset\" [level=3]\n - code: preset.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"tng-bridge\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - 'option \"Interprise-G: Main Bridge\" [selected]'\n - 'option \"Interprise-G: Main Engineering\"'\n - 'option \"Interprise-G: Crew Quarters\"'\n - 'option \"USS Wayfarer: Bridge\"'\n - 'option \"USS Wayfarer: Class-N Worp Core\"'\n - 'option \"USS Defiance: Tactical Bridge\"'\n - 'option \"Deep Space 8: Ops Center\"'\n - 'option \"Interprise NCC-1701.5: Bridge (TOS)\"'\n - 'option \"Interprise NCC-1701.5: Engineering (TOS)\"'\n - 'option \"Interprise NS-01: Command Bridge\"'\n - option \"1962 Classic Console (1st / 2nd Doctors)\"\n - option \"Edwardian Secondary Console (4th Doctor)\"\n - option \"The Coral Living TARDIX (9th / 10th Doctors)\"\n - option \"The Bronze Workshop (11th Doctor)\"\n - option \"The Cool Machine (12th Doctor)\"\n - option \"The Humming Crystal Console (13th Doctor)\"\n - option \"The Endless White (14th / 15th Doctors)\"\n - 'option \"USCSS Nostramo: Ore Refinery Bridge\"'\n - 'option \"Serenety: Firewing-Class Cargo Hold\"'\n - 'option \"Red Dwarve: Main Drive Corridor\"'\n - 'option \"Starbug Z: Cockpit Environment\"'\n - 'option \"The Rasa: Dark Matter Bridge\"'\n - 'option \"Rocinanta: Combat Ops & Epsilon Drive\"'\n - 'option \"Hawk Translocator: Command Module\"'\n - 'option \"Valley Foundry: Agro-Dome Forest Hub\"'\n - 'option \"Meya: Leviathon Central Nexus\"'\n - 'option \"Talym: Gunship Neural Bridge\"'\n - 'option \"The Lexxi: Primary Organ Bridge\"'\n - 'option \"Vorlan Cruiser: Sentient Core\"'\n - 'option \"Wraithe Hive Ship: Throne Chamber\"'\n - 'option \"Species 8675309: Fluid Bioship\"'\n - 'option \"Jupiter Duece: Upper Deck Astrogator\"'\n - 'option \"The Liberation: Zen Flight Bridge\"'\n - 'option \"USS Cygnis: Victorian Engine Hall\"'\n - 'option \"USS Palomina: Deep Research Pod\"'\n - 'option \"Discovery Zero: Habitation Centrifuge\"'\n - 'option \"Dim Star: Bomb Bay & Quarters\"'\n - 'option \"Gunstarr: Tactical Combat Cockpit\"'\n - 'option \"The Seeker: 25th Century Flagship\"'\n - 'option \"USS Solaco: Conestega Hangar Deck\"'\n - 'option \"Battlestar Galactica: Combat Information Center (CIC)\"'\n - 'option \"Pale Star: Minbara/Vorlan Hybrid Bridge\"'\n - 'option \"EAS Agamemnon II: Omega Destroyer Bridge\"'\n - 'option \"Andromeda Ascending: Command Deck\"'\n - 'option \"Excaliber: Triumph-Class Heavy Combat Core\"'\n - 'option \"Colonial Adder Mk II: Cockpit Atmosphere\"'\n - 'option \"The Avalin: Interstellar Cruise Concourse\"'\n - 'option \"The Nightflier: Telepathic Corridor\"'\n - 'option \"USS Ascention: Generation Ship Promenade\"'\n - 'option \"Ark Zero: Evacuation Ark Bridge\"'\n - 'option \"Icarus I: Solar Shield Core\"'\n - 'option \"Event Boundary: Gravity Singularity Core\"'\n - 'option \"Lewis & Clarke: Rescue Cutter Bridge\"'\n - 'option \"The Betsy: Salvage Freighter Mess\"'\n - 'option \"BeBoppa: Living Quarters & Hangar\"'\n - 'option \"Outlaw Sun: Grapple Bridge\"'\n - 'option \"Scorpius: Wanderer Salvage Vessel\"'\n - 'option \"The Raider: Havoc Shuttle Bridge\"'\n - 'option \"The Milan: M-Ship Cockpit Lounge\"'\n - 'option \"Babylon 4: Core Control & Zocolo\"'\n - 'option \"Moonbase Beta: Main Mission Control\"'\n - 'option \"Sebastopol Station: Habitation Deck\"'\n - 'option \"Tyco Station: Asteroid Construction Bay\"'\n - 'option \"Ceres Minor Station: Sub-Crustal Tunnels\"'\n - 'option \"Gatehouse Station: Quarantine Transfer Deck\"'\n - 'option \"USS Norval: Command Bridge\"'\n - 'option \"NSEA Protractor: Beryllium Orb Engine Room\"'\n - 'option \"Heart of Silver: Unlikelihood Bridge\"'\n - 'option \"HMS Camden Key: Flight Deck & Mess\"'\n - 'option \"Spaceorb One: Mega-Warship Bridge\"'\n - button \"Set\"\n - heading \"display\" [level=3]\n - article:\n - heading \"Display Ticker Message (Transient)\" [level=3]\n - code: display.ticker\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - heading \"event\" [level=3]\n - article:\n - heading \"Red Alert Impulse\" [level=3]\n - code: event.alert-red\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Warp Jump Impulse\" [level=3]\n - code: event.warp-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Cycle Impulse\" [level=3]\n - code: event.demat\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Time Vortex Impulse\" [level=3]\n - code: event.vortex\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cloister Bell Impulse\" [level=3]\n - code: event.cloister\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Flak Barrage Impulse\" [level=3]\n - code: event.flak-barrage\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"FTL Jump Impulse\" [level=3]\n - code: event.ftl-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Singularity Engage Impulse\" [level=3]\n - code: event.singularity\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Flare Roar Impulse\" [level=3]\n - code: event.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Afterburner Ignite Impulse\" [level=3]\n - code: event.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Ludicrous Speed Impulse\" [level=3]\n - code: event.ludicrous-speed\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Improbability Drive Flip\" [level=3]\n - code: event.improbability-flip\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"sfx\" [level=3]\n - article:\n - heading \"Comm Badge Chime\" [level=3]\n - code: sfx.comm-badge\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Pneumatic Door Swish\" [level=3]\n - code: sfx.door-swish\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Bosun Pipe Call\" [level=3]\n - code: sfx.bosun-whistle\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Medical Vital Monitor\" [level=3]\n - code: sfx.medical-monitor\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Heavy Bulkhead Door\" [level=3]\n - code: sfx.cardassian-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Turbochute Whoosh\" [level=3]\n - code: sfx.turbolift\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Fabricator Materialize\" [level=3]\n - code: sfx.replicator\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Translocator Beam\" [level=3]\n - code: sfx.transporter\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Single Chirp\" [level=3]\n - code: sfx.console-chirp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Sonic Acoustic Screwdriver\" [level=3]\n - code: sfx.sonic\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Mechanical Lever Clunk\" [level=3]\n - code: sfx.lever-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Tardix Wood & Lock Door\" [level=3]\n - code: sfx.tardix-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Switch Rotor\" [level=3]\n - code: sfx.demat-switch\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Telepathic Circuit Chime\" [level=3]\n - code: sfx.telepathic-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Handler Valve Thud\" [level=3]\n - code: sfx.air-handler\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Mag-Boot Solenoid Latch\" [level=3]\n - code: sfx.mag-boot\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Comlock Electronic Tone\" [level=3]\n - code: sfx.comlock\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Radiation Wind\" [level=3]\n - code: sfx.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cassette Magnetic Transport Clunk\" [level=3]\n - code: sfx.tape-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Thruster Burner Pulse\" [level=3]\n - code: sfx.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Radiation Geiger Counter\" [level=3]\n - code: sfx.geiger\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Valve & Dock Clamp\" [level=3]\n - code: sfx.dock-clamp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Dradis Combat Sonar Ping\" [level=3]\n - code: sfx.dradis-ping\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"HAL Logic Melodic Chime\" [level=3]\n - code: sfx.hal-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\"\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-319js95v\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"SciFi-XZBT\\\", \\\"version\\\": \\\"5.2.0\\\", \\\"build\\\": \\\"production\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 0, \\\"sequence\\\": 3 }\"\n - generic \"Current persistent state\"\n - paragraph: Last reported exhibit values. This host view may be uncertain while resynchronizing.\n - generic: \"{ \\\"mix.master\\\": 0.75, \\\"mix.hull.level\\\": 0.65, \\\"mix.hull.frequency\\\": 50, \\\"mix.hull.cutoff\\\": 105, \\\"mix.drive.level\\\": 0.35, \\\"mix.drive.pulse-rate\\\": 48, \\\"mix.drive.carrier\\\": 58, \\\"mix.environment.level\\\": 0.55, \\\"mix.environment.cutoff\\\": 1600, \\\"mix.telemetry.level\\\": 0.45, \\\"mix.telemetry.density\\\": 0.65, \\\"speech.robot-amount\\\": 0.84, \\\"fnc.level\\\": 0.65, \\\"view.activity\\\": 0.6, \\\"transport.playing\\\": false, \\\"transport.muted\\\": false, \\\"fnc.enabled\\\": true, \\\"view.observation\\\": false, \\\"view.viewport-frame\\\": true, \\\"alert.active\\\": \\\"none\\\", \\\"universe.selected\\\": \\\"starfleet\\\", \\\"preset.selected\\\": \\\"tng-bridge\\\" }\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[ { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-319js95v\\\", \\\"sequence\\\": 1, \\\"timestamp\\\": 1789415838707, \\\"target\\\": \\\"display.ticker\\\", \\\"args\\\": { \\\"text\\\": \\\"Step 5 final closure ticker check.\\\" }, \\\"correlationId\\\": \\\"ngn-5-23\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-319js95v\\\", \\\"sequence\\\": 2, \\\"timestamp\\\": 1789415843565, \\\"target\\\": \\\"speech.say\\\", \\\"args\\\": { \\\"text\\\": \\\"Step 5 final closure speech check.\\\" }, \\\"correlationId\\\": \\\"ngn-5-25\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"capability.changed\\\", \\\"sessionId\\\": \\\"sess-319js95v\\\", \\\"sequence\\\": 3, \\\"timestamp\\\": 1789415843614, \\\"capability\\\": \\\"speech\\\", \\\"state\\\": \\\"ready\\\", \\\"source\\\": \\\"system\\\" } ]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:55:08.301Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-12844\\\" } }, { \\\"time\\\": \\\"2026-09-14T19:56:46.979Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-29bfc\\\" } }, { \\\"time\\\": \\\"2026-09-14T19:57:03.069Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-319js95v\\\" } } ]\"" +} diff --git a/test-fixtures/evidence/step5-haunted-house.json b/test-fixtures/evidence/step5-haunted-house.json new file mode 100644 index 0000000..3bd08a4 --- /dev/null +++ b/test-fixtures/evidence/step5-haunted-house.json @@ -0,0 +1,128 @@ +{ + "events": [ + { + "correlationId": "ngn-11-35", + "sequence": 1, + "sessionId": "sess-1ce04", + "source": "host", + "stateRevision": 1, + "target": "room.haunt-level", + "timestamp": 1789398645709, + "type": "state.changed", + "value": 0.5, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-11-37", + "sequence": 2, + "sessionId": "sess-1ce04", + "source": "host", + "stateRevision": 2, + "target": "room.paused", + "timestamp": 1789398646251, + "type": "state.changed", + "value": true, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-11-39", + "sequence": 3, + "sessionId": "sess-1ce04", + "source": "host", + "stateRevision": 3, + "target": "room.current", + "timestamp": 1789398646528, + "type": "selection.changed", + "value": "library", + "xzbt": "5.2" + }, + { + "args": {}, + "correlationId": "ngn-11-41", + "sequence": 4, + "sessionId": "sess-1ce04", + "source": "host", + "target": "action.manifest", + "timestamp": 1789398646792, + "type": "action.executed", + "xzbt": "5.2" + } + ], + "logs": [ + { + "code": "SESSION", + "detail": { + "sessionId": "sess-1a192" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:09:20.864Z" + }, + { + "code": "SESSION", + "detail": { + "sessionId": "sess-21bcb" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:09:49.720Z" + }, + { + "code": "SESSION", + "detail": { + "sessionId": "sess-14131" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:09:50.283Z" + }, + { + "code": "SESSION", + "detail": { + "sessionId": "sess-194b7" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:10:00.111Z" + }, + { + "code": "SESSION", + "detail": { + "sessionId": "sess-22e" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:10:24.934Z" + }, + { + "code": "SESSION", + "detail": { + "sessionId": "sess-1ce04" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:10:25.249Z" + }, + { + "code": "INVALID_MESSAGE", + "detail": null, + "message": "room.haunt-level: Value must follow step 0.01.", + "time": "2026-09-14T15:10:46.252Z" + }, + { + "code": "INVALID_MESSAGE", + "detail": null, + "message": "room.haunt-level: Value must follow step 0.01.", + "time": "2026-09-14T15:10:46.530Z" + } + ], + "metadata": { + "contract": { + "major": 5, + "minor": 2 + }, + "exhibit": { + "build": "reference-exhibit", + "product": "Haunted House", + "version": "1.0.0" + }, + "registryRevision": 1, + "sequence": 4, + "sessionId": "sess-1ce04", + "stateRevision": 3 + } +} \ No newline at end of file diff --git a/test-fixtures/evidence/step5-operator.jpg b/test-fixtures/evidence/step5-operator.jpg new file mode 100644 index 0000000..fe74212 Binary files /dev/null and b/test-fixtures/evidence/step5-operator.jpg differ diff --git a/test-fixtures/evidence/step5-planetarium.json b/test-fixtures/evidence/step5-planetarium.json new file mode 100644 index 0000000..473a7df --- /dev/null +++ b/test-fixtures/evidence/step5-planetarium.json @@ -0,0 +1,68 @@ +{ + "events": [ + { + "correlationId": "ngn-8-21", + "sequence": 1, + "sessionId": "sess-194b7", + "source": "host", + "stateRevision": 1, + "target": "sky.azimuth", + "timestamp": 1789398623275, + "type": "state.changed", + "value": 0.75, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-8-23", + "sequence": 2, + "sessionId": "sess-194b7", + "source": "host", + "stateRevision": 2, + "target": "sky.paused", + "timestamp": 1789398623816, + "type": "state.changed", + "value": true, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-8-25", + "sequence": 3, + "sessionId": "sess-194b7", + "source": "host", + "stateRevision": 3, + "target": "sky.constellation", + "timestamp": 1789398624094, + "type": "selection.changed", + "value": "cygnus", + "xzbt": "5.2" + }, + { + "args": { + "minutes": 30 + }, + "correlationId": "ngn-8-27", + "sequence": 4, + "sessionId": "sess-194b7", + "source": "host", + "target": "action.advance-time", + "timestamp": 1789398624643, + "type": "action.executed", + "xzbt": "5.2" + } + ], + "metadata": { + "contract": { + "major": 5, + "minor": 2 + }, + "exhibit": { + "build": "reference-exhibit", + "product": "Planetarium", + "version": "1.0.0" + }, + "registryRevision": 1, + "sequence": 4, + "sessionId": "sess-194b7", + "stateRevision": 3 + } +} \ No newline at end of file diff --git a/test-fixtures/evidence/step5-reference-suite.txt b/test-fixtures/evidence/step5-reference-suite.txt new file mode 100644 index 0000000..d0f1799 --- /dev/null +++ b/test-fixtures/evidence/step5-reference-suite.txt @@ -0,0 +1,6 @@ +Started: 2026-09-14T15:11:53.141Z +PASS aquarium: 12 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +PASS planetarium: 14 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +PASS haunted-house: 13 targets; negotiation, discovery, state/range/selection, invoke, errors, events, no-op, reconnect, session invalidation/recovery. +3/3 real exhibits passed. +Finished: 2026-09-14T15:11:53.273Z diff --git a/test-fixtures/evidence/step5-scifi-arguments-files.json b/test-fixtures/evidence/step5-scifi-arguments-files.json new file mode 100644 index 0000000..7c082d3 --- /dev/null +++ b/test-fixtures/evidence/step5-scifi-arguments-files.json @@ -0,0 +1,68 @@ +{ + "capturedAt": "2026-09-14T19:45:18.3670019Z", + "source": "G:\\.vibe\\SciFi-XZBT", + "sourceCommit": "179b9db715aaa92999c5bfef451b6877f30079fe", + "destination": "G:\\.vibe\\XZBT-NGN\\test-fixtures\\local\\scifi-arguments", + "files": [ + { + "path": "index.html", + "sourceSha256": "9A851185BB5CB78AD034E25709253222BB35CE061716F611750CB758A55BC44C", + "copySha256": "9A851185BB5CB78AD034E25709253222BB35CE061716F611750CB758A55BC44C" + }, + { + "path": "css\\style.css", + "sourceSha256": "93032672388E757EFB6A52C5059344C378A7CED9F738D2C907EF4D969BB87CEE", + "copySha256": "93032672388E757EFB6A52C5059344C378A7CED9F738D2C907EF4D969BB87CEE" + }, + { + "path": "js\\app.js", + "sourceSha256": "499280FE19BAF4D4429AD915958C2BB54247018CDAC1195EB17E906DDC6370FE", + "copySha256": "499280FE19BAF4D4429AD915958C2BB54247018CDAC1195EB17E906DDC6370FE" + }, + { + "path": "js\\audio.js", + "sourceSha256": "0E8C4D8C09B7C53F0C2F402C259AACE95656AA4181709CB9F4D8F30F0D451A64", + "copySha256": "0E8C4D8C09B7C53F0C2F402C259AACE95656AA4181709CB9F4D8F30F0D451A64" + }, + { + "path": "js\\config.js", + "sourceSha256": "8ED99BDABD688C5D738DF6515BADCED90CD2EC73C81CAFC07376E62BFCF15CE5", + "copySha256": "8ED99BDABD688C5D738DF6515BADCED90CD2EC73C81CAFC07376E62BFCF15CE5" + }, + { + "path": "js\\contract-adapter.js", + "sourceSha256": "71C74BECA55046D7899653C0C8FA420A169BB6A68BAA43E1D02443AA6AAE7747", + "copySha256": "71C74BECA55046D7899653C0C8FA420A169BB6A68BAA43E1D02443AA6AAE7747" + }, + { + "path": "js\\control-bus.js", + "sourceSha256": "47160558AF54F8F7E6408B6E2E52E726568EBA02058C4518CC221B86AC8F2234", + "copySha256": "47160558AF54F8F7E6408B6E2E52E726568EBA02058C4518CC221B86AC8F2234" + }, + { + "path": "js\\core-animations.js", + "sourceSha256": "0CA430E307090F2D391FD92C074B00B7193259DD82FC656612DBF7397D945E7D", + "copySha256": "0CA430E307090F2D391FD92C074B00B7193259DD82FC656612DBF7397D945E7D" + }, + { + "path": "js\\generative-experience.js", + "sourceSha256": "371FF099E9529DA9D93F20B943F46A40466248AAEF91955D11BF005DC29817DB", + "copySha256": "371FF099E9529DA9D93F20B943F46A40466248AAEF91955D11BF005DC29817DB" + }, + { + "path": "js\\observation-bezels.js", + "sourceSha256": "18B853477F66C39CA6BCC7FE08C9C2BF868E2A909D3E18C38681D589205108FA", + "copySha256": "18B853477F66C39CA6BCC7FE08C9C2BF868E2A909D3E18C38681D589205108FA" + }, + { + "path": "js\\observation-engine.js", + "sourceSha256": "A5FEE90BDE61F733D28FD9F5EA58A81BCA911F9B3D0BBE41AE65CB06C5188376", + "copySha256": "A5FEE90BDE61F733D28FD9F5EA58A81BCA911F9B3D0BBE41AE65CB06C5188376" + }, + { + "path": "js\\visualizer.js", + "sourceSha256": "ED33A96E62674D0F7F30DDDC9FE83E94CA488BFCA45F32585E7AE51F0B35B89A", + "copySha256": "ED33A96E62674D0F7F30DDDC9FE83E94CA488BFCA45F32585E7AE51F0B35B89A" + } + ] +} diff --git a/test-fixtures/evidence/step5-scifi-arguments-ui-final.json b/test-fixtures/evidence/step5-scifi-arguments-ui-final.json new file mode 100644 index 0000000..079672f --- /dev/null +++ b/test-fixtures/evidence/step5-scifi-arguments-ui-final.json @@ -0,0 +1,5 @@ +{ + "capturedAt": "2026-09-14T19:45:50.154Z", + "url": "http://127.0.0.1:4173/", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/local/scifi-arguments/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: SciFi-XZBT · 5.2.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/local/scifi-arguments/index.html\"\n - iframe:\n - banner:\n - generic \"Click to Change Sci-Fi Universe\":\n - generic: STARFLIGHT\n - generic: ▼\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - button \"MUTE\"\n - generic: 75%\n - generic: STARFLIGHT SOUND MATRIX v2.9\n - main:\n - complementary:\n - generic: STARSHIP PRESETS\n - 'button \"Interprise-G: Main Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Main Engineering STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Engineering\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Crew Quarters STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Crew Quarters\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Class-N Worp Core STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Class-N Worp Core\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Defiance: Tactical Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Defiance: Tactical Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Deep Space 8: Ops Center STARFLIGHT COMMAND\"':\n - generic: \"Deep Space 8: Ops Center\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Bridge (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Bridge (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Engineering (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Engineering (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NS-01: Command Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise NS-01: Command Bridge\"\n - generic: STARFLIGHT COMMAND\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - generic: Warm, low-frequency 50Hz hull tone with gentle ventilation and soft LCARD computer chirps.\n - generic: AUDIO ENGINE\n - generic: ONLINE\n - generic: CORE FREQ\n - generic: 58.0 Hz\n - button \"■ FULL STOP\":\n - generic: ■\n - generic: FULL STOP\n - button \"WARP JUMP\"\n - button \"RED ALERT\"\n - button \"YELLOW ALERT\"\n - button \"WATCH EXPERIENCE\"\n - generic: Full-screen procedural visuals, soundscape, telemetry, events, and optional local AI announcements.\n - 'generic \"Toggle starship viewport window frame (ON: framed starship lounge / OFF: pure deep space)\"':\n - generic: VIEWPORT\n - checkbox \"Toggle Observation Viewport Frame\" [checked]\n - generic: \"ON\"\n - 'generic \"Toggle vertical structural window pillars / mullions (OFF: unobstructed panoramic view / ON: structural posts)\"':\n - generic: PILLARS\n - checkbox \"Toggle Window Pillars\"\n - generic: \"OFF\"\n - generic \"Controls how frequently and intensely Observation generates transient visual activity\":\n - generic: ACTIVITY\n - slider \"Observation activity level\"\n - generic: 60%\n - generic \"Keep OBSERVATION profile/status information visible. Turn off to fade it out over 30 seconds.\":\n - generic: HUD HOLD\n - checkbox \"Keep Observation information visible\" [checked]\n - generic: \"ON\"\n - generic: MAIN MASTER VOLUME\n - generic: 75%\n - generic: \"-2.5 dB\"\n - button \"MUTE\"\n - button \"25%\"\n - button \"50%\"\n - button \"75%\"\n - button \"MAX\"\n - button \"−\"\n - slider \"Main Volume\"\n - button \"+\"\n - generic: FNC // FUTURISTIC NOICE CANCELLATION\n - generic: ACTIVE\n - button \"FNC ENABLED\" [pressed]\n - generic: FNC LEVEL\n - slider \"Futuristic Noice Cancellation level\"\n - generic: 65%\n - generic: Comfort suppression active — Life Support 83%, Hull 49%, Worp Core 34% reduction.\n - generic: ACOUSTIC FREQUENCY SPECTRUM\n - generic: PROCEDURAL HARMONIC ANALYSIS\n - generic: \"SPECTRUM BIN: 32 CH // REAL-TIME FFT\"\n - generic: PROCEDURAL SOUND ENGINE MIXER\n - generic: LIVE PARAMETER MODULATION\n - generic: 1. HULL DRONE\n - generic: 65%\n - generic: LEVEL\n - slider\n - generic: BASE TONE\n - generic: 50 Hz\n - slider\n - generic: DAMPING CUTOFF\n - generic: 105 Hz\n - slider\n - generic: 2. WORP CORE\n - generic: 35%\n - generic: LEVEL\n - slider\n - generic: PULSE RATE (BPM)\n - generic: 48 BPM\n - slider\n - generic: REACTOR PITCH\n - generic: 58 Hz\n - slider\n - generic: 3. LIFE SUPPORT\n - generic: 55%\n - generic: LEVEL\n - slider\n - generic: AIRFLOW AIR FILTER\n - generic: 1600 Hz\n - slider\n - generic: 4. TELEMETRY\n - generic: 45%\n - generic: LEVEL\n - slider\n - generic: BACKGROUND CHIRP DENSITY\n - generic: 65%\n - slider\n - complementary:\n - generic: INTERMIX CASCADE\n - generic: INTERMIX CHAMBER // ACTIVE\n - generic: GENERATIVE EXPERIENCE\n - generic: LOCAL WEBLLM + KOKORO TTS\n - generic: LOCAL GENERATIVE SYSTEM\n - generic: LOCAL AI READY\n - text: LANGUAGE MODEL\n - text: Qwen3-1.7B-q4f16_1-MLC\n - text: Automatically prepared when the experience starts. No model selection required.\n - text: WEBLLM\n - generic: READY\n - text: KOKORO\n - generic: READY\n - button \"PREPARE AI\"\n - button \"GENERATE NOW\"\n - checkbox \"AUTO UPDATES\" [checked]\n - text: AUTO UPDATES\n - checkbox \"SPEAK\" [checked]\n - text: SPEAK\n - combobox \"Kokoro voice\":\n - option \"Heart (af_heart)\" [selected]\n - option \"Alloy (af_alloy)\"\n - option \"Aoede (af_aoede)\"\n - option \"Bella (af_bella)\"\n - option \"Jessica (af_jessica)\"\n - option \"Kore (af_kore)\"\n - option \"Nicole (af_nicole)\"\n - option \"Nova (af_nova)\"\n - option \"River (af_river)\"\n - option \"Sarah (af_sarah)\"\n - option \"Sky (af_sky)\"\n - option \"Adam (am_adam)\"\n - option \"Echo (am_echo)\"\n - option \"Eric (am_eric)\"\n - option \"Fenrir (am_fenrir)\"\n - option \"Liam (am_liam)\"\n - option \"Michael (am_michael)\"\n - option \"Onyx (am_onyx)\"\n - option \"Puck (am_puck)\"\n - option \"Santa (am_santa)\"\n - option \"Emma (bf_emma)\"\n - option \"Isabella (bf_isabella)\"\n - option \"George (bm_george)\"\n - option \"Lewis (bm_lewis)\"\n - option \"Alice (bf_alice)\"\n - option \"Lily (bf_lily)\"\n - option \"Daniel (bm_daniel)\"\n - option \"Fable (bm_fable)\"\n - generic: VOICE AUTOMATION\n - generic: 84%\n - slider \"Robotic voice effect intensity\"\n - generic: AUTO CADENCE\n - generic: 90-180 SEC\n - slider\n - generic: Qwen3-1.7B-q4f16_1-MLC + Kokoro prepared.\n - generic: GENERATIVE CHANNEL STANDBY\n - generic: TV PRESENTATION\n - generic: CAST-FRIENDLY 16:9\n - checkbox \"LOCK EXPERIENCE TO 16:9\" [checked]\n - text: LOCK EXPERIENCE TO 16:9\n - generic: RENDER DENSITY\n - generic: 1080P TARGET\n - combobox:\n - option \"720P PERFORMANCE\"\n - option \"1080P STANDARD\" [selected]\n - option \"1440P HIGH\"\n - option \"2160P ULTRA\"\n - generic: The scene keeps a canonical 16:9 layout on phones, PCs, and cast displays. Chrome still negotiates the final Cast stream resolution.\n - generic: SLEEP TIMER\n - generic: TIMER INACTIVE (CONTINUOUS)\n - button \"15M\"\n - button \"30M\"\n - button \"60M\"\n - button \"OFF\"\n - generic: LCARD KEYPAD\n - button \"COM BADGE CHIRP\"\n - button \"DOOR PNEUMATIC SWISH\"\n - button \"BOSUN'S PIPE WHISTLE\"\n - button \"MEDBAY ECG MONITOR\"\n - button \"CARDASSIAN BULKHEAD DOOR\"\n - button \"TURBOCHUTE WHOOSH\"\n - button \"FABRICATOR SHIMMER\"\n - button \"TRANSLOCATOR CYCLE\"\n - button \"LCARD SINGLE CHIRP\"\n - contentinfo:\n - generic: \"SYSTEM STATUS: NORMAL\"\n - link \"VIBE ENGINEERED BY LABYRICORN STUDIOS\":\n - /url: https://www.labyricorn.com/\n - generic: \"SPACE: PLAY/PAUSE | M: MUTE | ↑/↓: VOLUME\"\n - heading \"Exhibit controls (61)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"audio\" [level=3]\n - article:\n - heading \"Master Volume\" [level=3]\n - code: mix.master\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.75\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.75\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Level\" [level=3]\n - code: mix.hull.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Frequency\" [level=3]\n - code: mix.hull.frequency\n - paragraph: range · readable · writable\n - status: \"Reported value: 50\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"50\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Cutoff\" [level=3]\n - code: mix.hull.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 105\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"105\"\n - button \"Set\"\n - article:\n - heading \"Drive / Core Level\" [level=3]\n - code: mix.drive.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.35\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.35\"\n - button \"Set\"\n - article:\n - heading \"Drive Pulse Rate\" [level=3]\n - code: mix.drive.pulse-rate\n - paragraph: range · readable · writable\n - status: \"Reported value: 48\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"48\"\n - button \"Set\"\n - article:\n - heading \"Drive Carrier Frequency\" [level=3]\n - code: mix.drive.carrier\n - paragraph: range · readable · writable\n - status: \"Reported value: 58\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"58\"\n - button \"Set\"\n - article:\n - heading \"Environmental Airflow Level\" [level=3]\n - code: mix.environment.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Environmental Filter Cutoff\" [level=3]\n - code: mix.environment.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 1600\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"1600\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Level\" [level=3]\n - code: mix.telemetry.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.45\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.45\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Density\" [level=3]\n - code: mix.telemetry.density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Level\" [level=3]\n - code: fnc.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Active\" [level=3]\n - code: fnc.enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"speech\" [level=3]\n - article:\n - heading \"Speech Robotic Modulation\" [level=3]\n - code: speech.robot-amount\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.84\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.84\"\n - button \"Set\"\n - article:\n - heading \"Speak Text Line\" [level=3]\n - code: speech.say\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - article:\n - heading \"Generate Ambient AI Announcement\" [level=3]\n - code: speech.generate-announcement\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"observation\" [level=3]\n - article:\n - heading \"Observation Visual Activity\" [level=3]\n - code: view.activity\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.6\"\n - button \"Set\"\n - article:\n - heading \"Observation Viewport Active\" [level=3]\n - code: view.observation\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - article:\n - heading \"Starship Bezel Frame Framing\" [level=3]\n - code: view.viewport-frame\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"transport\" [level=3]\n - article:\n - heading \"Audio Engine Online\" [level=3]\n - code: transport.playing\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - article:\n - heading \"Master Mute\" [level=3]\n - code: transport.muted\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - heading \"alert\" [level=3]\n - article:\n - heading \"Active Tactical Alert Status\" [level=3]\n - code: alert.active\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"none\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Condition Green (Normal)\" [selected]\n - option \"Yellow Alert\"\n - option \"Red Alert\"\n - button \"Set\"\n - heading \"universe\" [level=3]\n - article:\n - heading \"Active Sci-Fi Universe\" [level=3]\n - code: universe.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"starfleet\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Starfleet / Galaxy\" [selected]\n - option \"Whataverse / Tardix\"\n - option \"Industrial / Weyland\"\n - option \"Bioships / Leviathan\"\n - option \"Retro Future / 1960s\"\n - option \"Colonial Military\"\n - option \"Deep Space Generation\"\n - option \"Frontier & Salvage\"\n - option \"Orbital Stations & Docks\"\n - option \"Whimsical Fleet\"\n - button \"Set\"\n - heading \"preset\" [level=3]\n - article:\n - heading \"Active Audio Preset\" [level=3]\n - code: preset.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"tng-bridge\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - 'option \"Interprise-G: Main Bridge\" [selected]'\n - 'option \"Interprise-G: Main Engineering\"'\n - 'option \"Interprise-G: Crew Quarters\"'\n - 'option \"USS Wayfarer: Bridge\"'\n - 'option \"USS Wayfarer: Class-N Worp Core\"'\n - 'option \"USS Defiance: Tactical Bridge\"'\n - 'option \"Deep Space 8: Ops Center\"'\n - 'option \"Interprise NCC-1701.5: Bridge (TOS)\"'\n - 'option \"Interprise NCC-1701.5: Engineering (TOS)\"'\n - 'option \"Interprise NS-01: Command Bridge\"'\n - option \"1962 Classic Console (1st / 2nd Doctors)\"\n - option \"Edwardian Secondary Console (4th Doctor)\"\n - option \"The Coral Living TARDIX (9th / 10th Doctors)\"\n - option \"The Bronze Workshop (11th Doctor)\"\n - option \"The Cool Machine (12th Doctor)\"\n - option \"The Humming Crystal Console (13th Doctor)\"\n - option \"The Endless White (14th / 15th Doctors)\"\n - 'option \"USCSS Nostramo: Ore Refinery Bridge\"'\n - 'option \"Serenety: Firewing-Class Cargo Hold\"'\n - 'option \"Red Dwarve: Main Drive Corridor\"'\n - 'option \"Starbug Z: Cockpit Environment\"'\n - 'option \"The Rasa: Dark Matter Bridge\"'\n - 'option \"Rocinanta: Combat Ops & Epsilon Drive\"'\n - 'option \"Hawk Translocator: Command Module\"'\n - 'option \"Valley Foundry: Agro-Dome Forest Hub\"'\n - 'option \"Meya: Leviathon Central Nexus\"'\n - 'option \"Talym: Gunship Neural Bridge\"'\n - 'option \"The Lexxi: Primary Organ Bridge\"'\n - 'option \"Vorlan Cruiser: Sentient Core\"'\n - 'option \"Wraithe Hive Ship: Throne Chamber\"'\n - 'option \"Species 8675309: Fluid Bioship\"'\n - 'option \"Jupiter Duece: Upper Deck Astrogator\"'\n - 'option \"The Liberation: Zen Flight Bridge\"'\n - 'option \"USS Cygnis: Victorian Engine Hall\"'\n - 'option \"USS Palomina: Deep Research Pod\"'\n - 'option \"Discovery Zero: Habitation Centrifuge\"'\n - 'option \"Dim Star: Bomb Bay & Quarters\"'\n - 'option \"Gunstarr: Tactical Combat Cockpit\"'\n - 'option \"The Seeker: 25th Century Flagship\"'\n - 'option \"USS Solaco: Conestega Hangar Deck\"'\n - 'option \"Battlestar Galactica: Combat Information Center (CIC)\"'\n - 'option \"Pale Star: Minbara/Vorlan Hybrid Bridge\"'\n - 'option \"EAS Agamemnon II: Omega Destroyer Bridge\"'\n - 'option \"Andromeda Ascending: Command Deck\"'\n - 'option \"Excaliber: Triumph-Class Heavy Combat Core\"'\n - 'option \"Colonial Adder Mk II: Cockpit Atmosphere\"'\n - 'option \"The Avalin: Interstellar Cruise Concourse\"'\n - 'option \"The Nightflier: Telepathic Corridor\"'\n - 'option \"USS Ascention: Generation Ship Promenade\"'\n - 'option \"Ark Zero: Evacuation Ark Bridge\"'\n - 'option \"Icarus I: Solar Shield Core\"'\n - 'option \"Event Boundary: Gravity Singularity Core\"'\n - 'option \"Lewis & Clarke: Rescue Cutter Bridge\"'\n - 'option \"The Betsy: Salvage Freighter Mess\"'\n - 'option \"BeBoppa: Living Quarters & Hangar\"'\n - 'option \"Outlaw Sun: Grapple Bridge\"'\n - 'option \"Scorpius: Wanderer Salvage Vessel\"'\n - 'option \"The Raider: Havoc Shuttle Bridge\"'\n - 'option \"The Milan: M-Ship Cockpit Lounge\"'\n - 'option \"Babylon 4: Core Control & Zocolo\"'\n - 'option \"Moonbase Beta: Main Mission Control\"'\n - 'option \"Sebastopol Station: Habitation Deck\"'\n - 'option \"Tyco Station: Asteroid Construction Bay\"'\n - 'option \"Ceres Minor Station: Sub-Crustal Tunnels\"'\n - 'option \"Gatehouse Station: Quarantine Transfer Deck\"'\n - 'option \"USS Norval: Command Bridge\"'\n - 'option \"NSEA Protractor: Beryllium Orb Engine Room\"'\n - 'option \"Heart of Silver: Unlikelihood Bridge\"'\n - 'option \"HMS Camden Key: Flight Deck & Mess\"'\n - 'option \"Spaceorb One: Mega-Warship Bridge\"'\n - button \"Set\"\n - heading \"display\" [level=3]\n - article:\n - heading \"Display Ticker Message (Transient)\" [level=3]\n - code: display.ticker\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - heading \"event\" [level=3]\n - article:\n - heading \"Red Alert Impulse\" [level=3]\n - code: event.alert-red\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Warp Jump Impulse\" [level=3]\n - code: event.warp-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Cycle Impulse\" [level=3]\n - code: event.demat\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Time Vortex Impulse\" [level=3]\n - code: event.vortex\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cloister Bell Impulse\" [level=3]\n - code: event.cloister\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Flak Barrage Impulse\" [level=3]\n - code: event.flak-barrage\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"FTL Jump Impulse\" [level=3]\n - code: event.ftl-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Singularity Engage Impulse\" [level=3]\n - code: event.singularity\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Flare Roar Impulse\" [level=3]\n - code: event.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Afterburner Ignite Impulse\" [level=3]\n - code: event.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Ludicrous Speed Impulse\" [level=3]\n - code: event.ludicrous-speed\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Improbability Drive Flip\" [level=3]\n - code: event.improbability-flip\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"sfx\" [level=3]\n - article:\n - heading \"Comm Badge Chime\" [level=3]\n - code: sfx.comm-badge\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Pneumatic Door Swish\" [level=3]\n - code: sfx.door-swish\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Bosun Pipe Call\" [level=3]\n - code: sfx.bosun-whistle\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Medical Vital Monitor\" [level=3]\n - code: sfx.medical-monitor\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Heavy Bulkhead Door\" [level=3]\n - code: sfx.cardassian-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Turbochute Whoosh\" [level=3]\n - code: sfx.turbolift\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Fabricator Materialize\" [level=3]\n - code: sfx.replicator\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Translocator Beam\" [level=3]\n - code: sfx.transporter\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Single Chirp\" [level=3]\n - code: sfx.console-chirp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Sonic Acoustic Screwdriver\" [level=3]\n - code: sfx.sonic\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Mechanical Lever Clunk\" [level=3]\n - code: sfx.lever-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Tardix Wood & Lock Door\" [level=3]\n - code: sfx.tardix-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Switch Rotor\" [level=3]\n - code: sfx.demat-switch\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Telepathic Circuit Chime\" [level=3]\n - code: sfx.telepathic-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Handler Valve Thud\" [level=3]\n - code: sfx.air-handler\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Mag-Boot Solenoid Latch\" [level=3]\n - code: sfx.mag-boot\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Comlock Electronic Tone\" [level=3]\n - code: sfx.comlock\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Radiation Wind\" [level=3]\n - code: sfx.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cassette Magnetic Transport Clunk\" [level=3]\n - code: sfx.tape-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Thruster Burner Pulse\" [level=3]\n - code: sfx.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Radiation Geiger Counter\" [level=3]\n - code: sfx.geiger\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Valve & Dock Clamp\" [level=3]\n - code: sfx.dock-clamp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Dradis Combat Sonar Ping\" [level=3]\n - code: sfx.dradis-ping\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"HAL Logic Melodic Chime\" [level=3]\n - code: sfx.hal-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\"\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"SciFi-XZBT\\\", \\\"version\\\": \\\"5.2.0\\\", \\\"build\\\": \\\"production\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 1, \\\"sequence\\\": 7 }\"\n - generic \"Current persistent state\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[ { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 1, \\\"timestamp\\\": 1789415130043, \\\"target\\\": \\\"speech.say\\\", \\\"args\\\": { \\\"text\\\": \\\"NGN Step 5 speech interoperability check.\\\" }, \\\"correlationId\\\": \\\"ngn-4-13\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"capability.changed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 2, \\\"timestamp\\\": 1789415130094, \\\"capability\\\": \\\"speech\\\", \\\"state\\\": \\\"ready\\\", \\\"source\\\": \\\"system\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 3, \\\"timestamp\\\": 1789415130253, \\\"target\\\": \\\"display.ticker\\\", \\\"args\\\": { \\\"text\\\": \\\"\\\" }, \\\"correlationId\\\": \\\"ngn-4-17\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"state.changed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 4, \\\"timestamp\\\": 1789415146082, \\\"stateRevision\\\": 1, \\\"target\\\": \\\"transport.playing\\\", \\\"value\\\": true, \\\"source\\\": \\\"ui\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 5, \\\"timestamp\\\": 1789415146083, \\\"target\\\": \\\"display.ticker\\\", \\\"args\\\": { \\\"text\\\": \\\"NGN Step 5 ticker interoperability check.\\\" }, \\\"correlationId\\\": \\\"ngn-4-19\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"capability.changed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 6, \\\"timestamp\\\": 1789415147299, \\\"capability\\\": \\\"speech\\\", \\\"state\\\": \\\"loading\\\", \\\"source\\\": \\\"system\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"capability.changed\\\", \\\"sessionId\\\": \\\"sess-igidi1h9\\\", \\\"sequence\\\": 7, \\\"timestamp\\\": 1789415149009, \\\"capability\\\": \\\"speech\\\", \\\"state\\\": \\\"ready\\\", \\\"source\\\": \\\"system\\\" } ]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:40:57.390Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-inf3scqz\\\" } }, { \\\"time\\\": \\\"2026-09-14T19:45:19.625Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-igidi1h9\\\" } } ]\"" +} diff --git a/test-fixtures/evidence/step5-scifi-arguments-ui.json b/test-fixtures/evidence/step5-scifi-arguments-ui.json new file mode 100644 index 0000000..9de723b --- /dev/null +++ b/test-fixtures/evidence/step5-scifi-arguments-ui.json @@ -0,0 +1,5 @@ +{ + "capturedAt": "2026-09-14T19:41:21.836Z", + "url": "http://127.0.0.1:4173/", + "snapshot": "- banner:\n - heading \"XZBT-NGN Exhibit Engine\" [level=1]\n - paragraph: Contract 5.2 · One exhibit at a time\n- main:\n - heading \"Connection\" [level=2]\n - generic: Exhibit URL (same origin)\n - textbox \"Exhibit URL (same origin)\":\n - /placeholder: /public/exhibits/example/index.html\n - text: /test-fixtures/local/scifi-arguments/index.html\n - button \"Load exhibit\"\n - button \"Reconnect\"\n - button \"Disconnect\"\n - paragraph: Enter a trusted exhibit's local served path or a URL on this origin. Load exhibit also switches exhibits. Reconnect starts a new session, or reloads the last exhibit after disconnect.\n - status: connected · synchronized\n - paragraph: SciFi-XZBT · 5.2.0 · Contract 5.2\n - paragraph: \"Exhibit location: http://127.0.0.1:4173/test-fixtures/local/scifi-arguments/index.html\"\n - iframe:\n - banner:\n - generic \"Click to Change Sci-Fi Universe\":\n - generic: STARFLIGHT\n - generic: ▼\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - button \"MUTE\"\n - generic: 75%\n - generic: STARFLIGHT SOUND MATRIX v2.9\n - main:\n - complementary:\n - generic: STARSHIP PRESETS\n - 'button \"Interprise-G: Main Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Main Engineering STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Main Engineering\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise-G: Crew Quarters STARFLIGHT COMMAND\"':\n - generic: \"Interprise-G: Crew Quarters\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Wayfarer: Class-N Worp Core STARFLIGHT COMMAND\"':\n - generic: \"USS Wayfarer: Class-N Worp Core\"\n - generic: STARFLIGHT COMMAND\n - 'button \"USS Defiance: Tactical Bridge STARFLIGHT COMMAND\"':\n - generic: \"USS Defiance: Tactical Bridge\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Deep Space 8: Ops Center STARFLIGHT COMMAND\"':\n - generic: \"Deep Space 8: Ops Center\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Bridge (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Bridge (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NCC-1701.5: Engineering (TOS) STARFLIGHT COMMAND\"':\n - generic: \"Interprise NCC-1701.5: Engineering (TOS)\"\n - generic: STARFLIGHT COMMAND\n - 'button \"Interprise NS-01: Command Bridge STARFLIGHT COMMAND\"':\n - generic: \"Interprise NS-01: Command Bridge\"\n - generic: STARFLIGHT COMMAND\n - generic: \"INTERPRISE-G: MAIN BRIDGE\"\n - generic: Warm, low-frequency 50Hz hull tone with gentle ventilation and soft LCARD computer chirps.\n - generic: AUDIO ENGINE\n - generic: STANDBY\n - generic: CORE FREQ\n - generic: 58.0 Hz\n - button \"▶ ENGAGE\":\n - generic: ▶\n - generic: ENGAGE\n - button \"WARP JUMP\"\n - button \"RED ALERT\"\n - button \"YELLOW ALERT\"\n - button \"WATCH EXPERIENCE\"\n - generic: Full-screen procedural visuals, soundscape, telemetry, events, and optional local AI announcements.\n - 'generic \"Toggle starship viewport window frame (ON: framed starship lounge / OFF: pure deep space)\"':\n - generic: VIEWPORT\n - checkbox \"Toggle Observation Viewport Frame\" [checked]\n - generic: \"ON\"\n - 'generic \"Toggle vertical structural window pillars / mullions (OFF: unobstructed panoramic view / ON: structural posts)\"':\n - generic: PILLARS\n - checkbox \"Toggle Window Pillars\"\n - generic: \"OFF\"\n - generic \"Controls how frequently and intensely Observation generates transient visual activity\":\n - generic: ACTIVITY\n - slider \"Observation activity level\"\n - generic: 60%\n - generic \"Keep OBSERVATION profile/status information visible. Turn off to fade it out over 30 seconds.\":\n - generic: HUD HOLD\n - checkbox \"Keep Observation information visible\" [checked]\n - generic: \"ON\"\n - generic: MAIN MASTER VOLUME\n - generic: 75%\n - generic: \"-2.5 dB\"\n - button \"MUTE\"\n - button \"25%\"\n - button \"50%\"\n - button \"75%\"\n - button \"MAX\"\n - button \"−\"\n - slider \"Main Volume\"\n - button \"+\"\n - generic: FNC // FUTURISTIC NOICE CANCELLATION\n - generic: ACTIVE\n - button \"FNC ENABLED\" [pressed]\n - generic: FNC LEVEL\n - slider \"Futuristic Noice Cancellation level\"\n - generic: 65%\n - generic: Comfort suppression active — Life Support 83%, Hull 49%, Worp Core 34% reduction.\n - generic: ACOUSTIC FREQUENCY SPECTRUM\n - generic: PROCEDURAL HARMONIC ANALYSIS\n - generic: \"SPECTRUM BIN: 32 CH // REAL-TIME FFT\"\n - generic: PROCEDURAL SOUND ENGINE MIXER\n - generic: LIVE PARAMETER MODULATION\n - generic: 1. HULL DRONE\n - generic: 65%\n - generic: LEVEL\n - slider\n - generic: BASE TONE\n - generic: 50 Hz\n - slider\n - generic: DAMPING CUTOFF\n - generic: 105 Hz\n - slider\n - generic: 2. WORP CORE\n - generic: 35%\n - generic: LEVEL\n - slider\n - generic: PULSE RATE (BPM)\n - generic: 48 BPM\n - slider\n - generic: REACTOR PITCH\n - generic: 58 Hz\n - slider\n - generic: 3. LIFE SUPPORT\n - generic: 55%\n - generic: LEVEL\n - slider\n - generic: AIRFLOW AIR FILTER\n - generic: 1600 Hz\n - slider\n - generic: 4. TELEMETRY\n - generic: 45%\n - generic: LEVEL\n - slider\n - generic: BACKGROUND CHIRP DENSITY\n - generic: 65%\n - slider\n - complementary:\n - generic: INTERMIX CASCADE\n - generic: INTERMIX CHAMBER // ACTIVE\n - generic: GENERATIVE EXPERIENCE\n - generic: LOCAL WEBLLM + KOKORO TTS\n - generic: LOCAL GENERATIVE SYSTEM\n - generic: STANDBY\n - text: LANGUAGE MODEL\n - text: Qwen3-1.7B-q4f16_1-MLC\n - text: Automatically prepared when the experience starts. No model selection required.\n - text: WEBLLM\n - generic: \"OFF\"\n - text: KOKORO\n - generic: \"OFF\"\n - button \"PREPARE AI\"\n - button \"GENERATE NOW\" [disabled]\n - checkbox \"AUTO UPDATES\" [checked]\n - text: AUTO UPDATES\n - checkbox \"SPEAK\" [checked]\n - text: SPEAK\n - combobox \"Kokoro voice\":\n - option \"Kokoro voice loads on preparation\" [selected]\n - generic: VOICE AUTOMATION\n - generic: 84%\n - slider \"Robotic voice effect intensity\"\n - generic: AUTO CADENCE\n - generic: 90-180 SEC\n - slider\n - generic: AI and neural voice models load only when needed and are cached by the browser when supported.\n - generic: GENERATIVE CHANNEL STANDBY\n - generic: TV PRESENTATION\n - generic: CAST-FRIENDLY 16:9\n - checkbox \"LOCK EXPERIENCE TO 16:9\" [checked]\n - text: LOCK EXPERIENCE TO 16:9\n - generic: RENDER DENSITY\n - generic: 1080P TARGET\n - combobox:\n - option \"720P PERFORMANCE\"\n - option \"1080P STANDARD\" [selected]\n - option \"1440P HIGH\"\n - option \"2160P ULTRA\"\n - generic: The scene keeps a canonical 16:9 layout on phones, PCs, and cast displays. Chrome still negotiates the final Cast stream resolution.\n - generic: SLEEP TIMER\n - generic: TIMER INACTIVE (CONTINUOUS)\n - button \"15M\"\n - button \"30M\"\n - button \"60M\"\n - button \"OFF\"\n - generic: LCARD KEYPAD\n - button \"COM BADGE CHIRP\"\n - button \"DOOR PNEUMATIC SWISH\"\n - button \"BOSUN'S PIPE WHISTLE\"\n - button \"MEDBAY ECG MONITOR\"\n - button \"CARDASSIAN BULKHEAD DOOR\"\n - button \"TURBOCHUTE WHOOSH\"\n - button \"FABRICATOR SHIMMER\"\n - button \"TRANSLOCATOR CYCLE\"\n - button \"LCARD SINGLE CHIRP\"\n - contentinfo:\n - generic: \"SYSTEM STATUS: NORMAL\"\n - link \"VIBE ENGINEERED BY LABYRICORN STUDIOS\":\n - /url: https://www.labyricorn.com/\n - generic: \"SPACE: PLAY/PAUSE | M: MUTE | ↑/↓: VOLUME\"\n - heading \"Exhibit controls (61)\" [level=2]\n - generic \"Capabilities\"\n - paragraph: Reported values are authoritative. New values are drafts; select Set or Invoke to submit. Rediscovery resets drafts.\n - heading \"audio\" [level=3]\n - article:\n - heading \"Master Volume\" [level=3]\n - code: mix.master\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.75\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.75\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Level\" [level=3]\n - code: mix.hull.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Frequency\" [level=3]\n - code: mix.hull.frequency\n - paragraph: range · readable · writable\n - status: \"Reported value: 50\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"50\"\n - button \"Set\"\n - article:\n - heading \"Hull Drone Cutoff\" [level=3]\n - code: mix.hull.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 105\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"105\"\n - button \"Set\"\n - article:\n - heading \"Drive / Core Level\" [level=3]\n - code: mix.drive.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.35\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.35\"\n - button \"Set\"\n - article:\n - heading \"Drive Pulse Rate\" [level=3]\n - code: mix.drive.pulse-rate\n - paragraph: range · readable · writable\n - status: \"Reported value: 48\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"48\"\n - button \"Set\"\n - article:\n - heading \"Drive Carrier Frequency\" [level=3]\n - code: mix.drive.carrier\n - paragraph: range · readable · writable\n - status: \"Reported value: 58\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"58\"\n - button \"Set\"\n - article:\n - heading \"Environmental Airflow Level\" [level=3]\n - code: mix.environment.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.55\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.55\"\n - button \"Set\"\n - article:\n - heading \"Environmental Filter Cutoff\" [level=3]\n - code: mix.environment.cutoff\n - paragraph: range · readable · writable\n - status: \"Reported value: 1600\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"1600\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Level\" [level=3]\n - code: mix.telemetry.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.45\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.45\"\n - button \"Set\"\n - article:\n - heading \"Telemetry Density\" [level=3]\n - code: mix.telemetry.density\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Level\" [level=3]\n - code: fnc.level\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.65\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.65\"\n - button \"Set\"\n - article:\n - heading \"Futuristic Noise Cancellation Active\" [level=3]\n - code: fnc.enabled\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"speech\" [level=3]\n - article:\n - heading \"Speech Robotic Modulation\" [level=3]\n - code: speech.robot-amount\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.84\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.84\"\n - button \"Set\"\n - article:\n - heading \"Speak Text Line\" [level=3]\n - code: speech.say\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - generic: \"{ \\\"id\\\": \\\"speech.say\\\", \\\"label\\\": \\\"Speak Text Line\\\", \\\"kind\\\": \\\"impulse\\\", \\\"readable\\\": false, \\\"writable\\\": false, \\\"restorable\\\": false, \\\"category\\\": \\\"speech\\\", \\\"requires\\\": [ \\\"speech\\\" ], \\\"arguments\\\": [ { \\\"name\\\": \\\"text\\\", \\\"type\\\": \\\"string\\\", \\\"required\\\": true, \\\"maxLength\\\": 2000 } ] }\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - article:\n - heading \"Generate Ambient AI Announcement\" [level=3]\n - code: speech.generate-announcement\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"observation\" [level=3]\n - article:\n - heading \"Observation Visual Activity\" [level=3]\n - code: view.activity\n - paragraph: range · readable · writable\n - status: \"Reported value: 0.6\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - spinbutton \"New value\": \"0.6\"\n - button \"Set\"\n - article:\n - heading \"Observation Viewport Active\" [level=3]\n - code: view.observation\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - article:\n - heading \"Starship Bezel Frame Framing\" [level=3]\n - code: view.viewport-frame\n - paragraph: state · readable · writable\n - status: \"Reported value: true\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\" [checked]\n - button \"Set\"\n - heading \"transport\" [level=3]\n - article:\n - heading \"Audio Engine Online\" [level=3]\n - code: transport.playing\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - article:\n - heading \"Master Mute\" [level=3]\n - code: transport.muted\n - paragraph: state · readable · writable\n - status: \"Reported value: false\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - checkbox \"New value\"\n - button \"Set\"\n - heading \"alert\" [level=3]\n - article:\n - heading \"Active Tactical Alert Status\" [level=3]\n - code: alert.active\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"none\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Condition Green (Normal)\" [selected]\n - option \"Yellow Alert\"\n - option \"Red Alert\"\n - button \"Set\"\n - heading \"universe\" [level=3]\n - article:\n - heading \"Active Sci-Fi Universe\" [level=3]\n - code: universe.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"starfleet\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - option \"Starfleet / Galaxy\" [selected]\n - option \"Whataverse / Tardix\"\n - option \"Industrial / Weyland\"\n - option \"Bioships / Leviathan\"\n - option \"Retro Future / 1960s\"\n - option \"Colonial Military\"\n - option \"Deep Space Generation\"\n - option \"Frontier & Salvage\"\n - option \"Orbital Stations & Docks\"\n - option \"Whimsical Fleet\"\n - button \"Set\"\n - heading \"preset\" [level=3]\n - article:\n - heading \"Active Audio Preset\" [level=3]\n - code: preset.selected\n - paragraph: selection · readable · writable\n - status: \"Reported value: \\\"tng-bridge\\\"\"\n - generic \"Target descriptor\"\n - paragraph\n - text: New value\n - combobox \"New value\":\n - 'option \"Interprise-G: Main Bridge\" [selected]'\n - 'option \"Interprise-G: Main Engineering\"'\n - 'option \"Interprise-G: Crew Quarters\"'\n - 'option \"USS Wayfarer: Bridge\"'\n - 'option \"USS Wayfarer: Class-N Worp Core\"'\n - 'option \"USS Defiance: Tactical Bridge\"'\n - 'option \"Deep Space 8: Ops Center\"'\n - 'option \"Interprise NCC-1701.5: Bridge (TOS)\"'\n - 'option \"Interprise NCC-1701.5: Engineering (TOS)\"'\n - 'option \"Interprise NS-01: Command Bridge\"'\n - option \"1962 Classic Console (1st / 2nd Doctors)\"\n - option \"Edwardian Secondary Console (4th Doctor)\"\n - option \"The Coral Living TARDIX (9th / 10th Doctors)\"\n - option \"The Bronze Workshop (11th Doctor)\"\n - option \"The Cool Machine (12th Doctor)\"\n - option \"The Humming Crystal Console (13th Doctor)\"\n - option \"The Endless White (14th / 15th Doctors)\"\n - 'option \"USCSS Nostramo: Ore Refinery Bridge\"'\n - 'option \"Serenety: Firewing-Class Cargo Hold\"'\n - 'option \"Red Dwarve: Main Drive Corridor\"'\n - 'option \"Starbug Z: Cockpit Environment\"'\n - 'option \"The Rasa: Dark Matter Bridge\"'\n - 'option \"Rocinanta: Combat Ops & Epsilon Drive\"'\n - 'option \"Hawk Translocator: Command Module\"'\n - 'option \"Valley Foundry: Agro-Dome Forest Hub\"'\n - 'option \"Meya: Leviathon Central Nexus\"'\n - 'option \"Talym: Gunship Neural Bridge\"'\n - 'option \"The Lexxi: Primary Organ Bridge\"'\n - 'option \"Vorlan Cruiser: Sentient Core\"'\n - 'option \"Wraithe Hive Ship: Throne Chamber\"'\n - 'option \"Species 8675309: Fluid Bioship\"'\n - 'option \"Jupiter Duece: Upper Deck Astrogator\"'\n - 'option \"The Liberation: Zen Flight Bridge\"'\n - 'option \"USS Cygnis: Victorian Engine Hall\"'\n - 'option \"USS Palomina: Deep Research Pod\"'\n - 'option \"Discovery Zero: Habitation Centrifuge\"'\n - 'option \"Dim Star: Bomb Bay & Quarters\"'\n - 'option \"Gunstarr: Tactical Combat Cockpit\"'\n - 'option \"The Seeker: 25th Century Flagship\"'\n - 'option \"USS Solaco: Conestega Hangar Deck\"'\n - 'option \"Battlestar Galactica: Combat Information Center (CIC)\"'\n - 'option \"Pale Star: Minbara/Vorlan Hybrid Bridge\"'\n - 'option \"EAS Agamemnon II: Omega Destroyer Bridge\"'\n - 'option \"Andromeda Ascending: Command Deck\"'\n - 'option \"Excaliber: Triumph-Class Heavy Combat Core\"'\n - 'option \"Colonial Adder Mk II: Cockpit Atmosphere\"'\n - 'option \"The Avalin: Interstellar Cruise Concourse\"'\n - 'option \"The Nightflier: Telepathic Corridor\"'\n - 'option \"USS Ascention: Generation Ship Promenade\"'\n - 'option \"Ark Zero: Evacuation Ark Bridge\"'\n - 'option \"Icarus I: Solar Shield Core\"'\n - 'option \"Event Boundary: Gravity Singularity Core\"'\n - 'option \"Lewis & Clarke: Rescue Cutter Bridge\"'\n - 'option \"The Betsy: Salvage Freighter Mess\"'\n - 'option \"BeBoppa: Living Quarters & Hangar\"'\n - 'option \"Outlaw Sun: Grapple Bridge\"'\n - 'option \"Scorpius: Wanderer Salvage Vessel\"'\n - 'option \"The Raider: Havoc Shuttle Bridge\"'\n - 'option \"The Milan: M-Ship Cockpit Lounge\"'\n - 'option \"Babylon 4: Core Control & Zocolo\"'\n - 'option \"Moonbase Beta: Main Mission Control\"'\n - 'option \"Sebastopol Station: Habitation Deck\"'\n - 'option \"Tyco Station: Asteroid Construction Bay\"'\n - 'option \"Ceres Minor Station: Sub-Crustal Tunnels\"'\n - 'option \"Gatehouse Station: Quarantine Transfer Deck\"'\n - 'option \"USS Norval: Command Bridge\"'\n - 'option \"NSEA Protractor: Beryllium Orb Engine Room\"'\n - 'option \"Heart of Silver: Unlikelihood Bridge\"'\n - 'option \"HMS Camden Key: Flight Deck & Mess\"'\n - 'option \"Spaceorb One: Mega-Warship Bridge\"'\n - button \"Set\"\n - heading \"display\" [level=3]\n - article:\n - heading \"Display Ticker Message (Transient)\" [level=3]\n - code: display.ticker\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\" [active]\n - generic: \"{ \\\"id\\\": \\\"display.ticker\\\", \\\"label\\\": \\\"Display Ticker Message (Transient)\\\", \\\"kind\\\": \\\"impulse\\\", \\\"readable\\\": false, \\\"writable\\\": false, \\\"restorable\\\": false, \\\"category\\\": \\\"display\\\", \\\"requires\\\": [ \\\"display-text\\\" ], \\\"arguments\\\": [ { \\\"name\\\": \\\"text\\\", \\\"type\\\": \\\"string\\\", \\\"required\\\": true, \\\"maxLength\\\": 512 } ] }\"\n - paragraph\n - text: text\n - textbox \"text\"\n - button \"Invoke\"\n - heading \"event\" [level=3]\n - article:\n - heading \"Red Alert Impulse\" [level=3]\n - code: event.alert-red\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Warp Jump Impulse\" [level=3]\n - code: event.warp-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Cycle Impulse\" [level=3]\n - code: event.demat\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Time Vortex Impulse\" [level=3]\n - code: event.vortex\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cloister Bell Impulse\" [level=3]\n - code: event.cloister\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Flak Barrage Impulse\" [level=3]\n - code: event.flak-barrage\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"FTL Jump Impulse\" [level=3]\n - code: event.ftl-jump\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Singularity Engage Impulse\" [level=3]\n - code: event.singularity\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Flare Roar Impulse\" [level=3]\n - code: event.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Afterburner Ignite Impulse\" [level=3]\n - code: event.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Ludicrous Speed Impulse\" [level=3]\n - code: event.ludicrous-speed\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Improbability Drive Flip\" [level=3]\n - code: event.improbability-flip\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"sfx\" [level=3]\n - article:\n - heading \"Comm Badge Chime\" [level=3]\n - code: sfx.comm-badge\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Pneumatic Door Swish\" [level=3]\n - code: sfx.door-swish\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Bosun Pipe Call\" [level=3]\n - code: sfx.bosun-whistle\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Medical Vital Monitor\" [level=3]\n - code: sfx.medical-monitor\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Heavy Bulkhead Door\" [level=3]\n - code: sfx.cardassian-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Turbochute Whoosh\" [level=3]\n - code: sfx.turbolift\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Fabricator Materialize\" [level=3]\n - code: sfx.replicator\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Translocator Beam\" [level=3]\n - code: sfx.transporter\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Single Chirp\" [level=3]\n - code: sfx.console-chirp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Sonic Acoustic Screwdriver\" [level=3]\n - code: sfx.sonic\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Console Mechanical Lever Clunk\" [level=3]\n - code: sfx.lever-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Tardix Wood & Lock Door\" [level=3]\n - code: sfx.tardix-door\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Demat Switch Rotor\" [level=3]\n - code: sfx.demat-switch\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Telepathic Circuit Chime\" [level=3]\n - code: sfx.telepathic-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Handler Valve Thud\" [level=3]\n - code: sfx.air-handler\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Mag-Boot Solenoid Latch\" [level=3]\n - code: sfx.mag-boot\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Comlock Electronic Tone\" [level=3]\n - code: sfx.comlock\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Solar Radiation Wind\" [level=3]\n - code: sfx.solar-roar\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Cassette Magnetic Transport Clunk\" [level=3]\n - code: sfx.tape-clunk\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Thruster Burner Pulse\" [level=3]\n - code: sfx.afterburner\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Radiation Geiger Counter\" [level=3]\n - code: sfx.geiger\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Air Valve & Dock Clamp\" [level=3]\n - code: sfx.dock-clamp\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"Dradis Combat Sonar Ping\" [level=3]\n - code: sfx.dradis-ping\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - article:\n - heading \"HAL Logic Melodic Chime\" [level=3]\n - code: sfx.hal-chime\n - paragraph: impulse · not readable · not writable\n - status: No persistent value reported.\n - generic \"Target descriptor\"\n - paragraph\n - button \"Invoke\"\n - heading \"State and metadata\" [level=2]\n - button \"Refresh state\"\n - generic \"Session and exhibit metadata\"\n - generic: \"{ \\\"sessionId\\\": \\\"sess-inf3scqz\\\", \\\"contract\\\": { \\\"major\\\": 5, \\\"minor\\\": 2 }, \\\"exhibit\\\": { \\\"product\\\": \\\"SciFi-XZBT\\\", \\\"version\\\": \\\"5.2.0\\\", \\\"build\\\": \\\"production\\\" }, \\\"registryRevision\\\": 1, \\\"stateRevision\\\": 0, \\\"sequence\\\": 3 }\"\n - generic \"Current persistent state\"\n - heading \"Protocol diagnostics\" [level=2]\n - generic \"Incoming events\"\n - paragraph: Latest 200 session events, including source, sequence, revision, and correlation when supplied.\n - generic \"Incoming events\": \"[ { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-inf3scqz\\\", \\\"sequence\\\": 1, \\\"timestamp\\\": 1789414873452, \\\"target\\\": \\\"speech.say\\\", \\\"args\\\": { \\\"text\\\": \\\"NGN Step 5 speech interoperability check.\\\" }, \\\"correlationId\\\": \\\"ngn-2-4\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"action.executed\\\", \\\"sessionId\\\": \\\"sess-inf3scqz\\\", \\\"sequence\\\": 2, \\\"timestamp\\\": 1789414873761, \\\"target\\\": \\\"display.ticker\\\", \\\"args\\\": { \\\"text\\\": \\\"NGN Step 5 ticker interoperability check.\\\" }, \\\"correlationId\\\": \\\"ngn-2-6\\\", \\\"source\\\": \\\"host\\\" }, { \\\"xzbt\\\": \\\"5.2\\\", \\\"type\\\": \\\"capability.changed\\\", \\\"sessionId\\\": \\\"sess-inf3scqz\\\", \\\"sequence\\\": 3, \\\"timestamp\\\": 1789414873798, \\\"capability\\\": \\\"speech\\\", \\\"state\\\": \\\"ready\\\", \\\"source\\\": \\\"system\\\" } ]\"\n - generic \"Protocol and errors\"\n - paragraph: Latest 200 entries across connections in this page. Timestamps identify earlier failures.\n - button \"Clear log\"\n - log: \"[ { \\\"time\\\": \\\"2026-09-14T19:40:57.390Z\\\", \\\"code\\\": \\\"SESSION\\\", \\\"message\\\": \\\"Exhibit session established.\\\", \\\"detail\\\": { \\\"sessionId\\\": \\\"sess-inf3scqz\\\" } } ]\"" +} diff --git a/test-fixtures/evidence/step5-scifi-files.json b/test-fixtures/evidence/step5-scifi-files.json new file mode 100644 index 0000000..1610867 --- /dev/null +++ b/test-fixtures/evidence/step5-scifi-files.json @@ -0,0 +1,50 @@ +[ + { + "path": "index.html", + "sha256": "9A851185BB5CB78AD034E25709253222BB35CE061716F611750CB758A55BC44C" + }, + { + "path": "css\\style.css", + "sha256": "93032672388E757EFB6A52C5059344C378A7CED9F738D2C907EF4D969BB87CEE" + }, + { + "path": "js\\app.js", + "sha256": "499280FE19BAF4D4429AD915958C2BB54247018CDAC1195EB17E906DDC6370FE" + }, + { + "path": "js\\audio.js", + "sha256": "0E8C4D8C09B7C53F0C2F402C259AACE95656AA4181709CB9F4D8F30F0D451A64" + }, + { + "path": "js\\config.js", + "sha256": "8ED99BDABD688C5D738DF6515BADCED90CD2EC73C81CAFC07376E62BFCF15CE5" + }, + { + "path": "js\\contract-adapter.js", + "sha256": "0ED3234477EE21C50F1E872313B6259DF37E269D06C523DA60E41B484DA5236F" + }, + { + "path": "js\\control-bus.js", + "sha256": "47160558AF54F8F7E6408B6E2E52E726568EBA02058C4518CC221B86AC8F2234" + }, + { + "path": "js\\core-animations.js", + "sha256": "0CA430E307090F2D391FD92C074B00B7193259DD82FC656612DBF7397D945E7D" + }, + { + "path": "js\\generative-experience.js", + "sha256": "371FF099E9529DA9D93F20B943F46A40466248AAEF91955D11BF005DC29817DB" + }, + { + "path": "js\\observation-bezels.js", + "sha256": "18B853477F66C39CA6BCC7FE08C9C2BF868E2A909D3E18C38681D589205108FA" + }, + { + "path": "js\\observation-engine.js", + "sha256": "A5FEE90BDE61F733D28FD9F5EA58A81BCA911F9B3D0BBE41AE65CB06C5188376" + }, + { + "path": "js\\visualizer.js", + "sha256": "ED33A96E62674D0F7F30DDDC9FE83E94CA488BFCA45F32585E7AE51F0B35B89A" + } +] diff --git a/test-fixtures/evidence/step5-scifi-session.json b/test-fixtures/evidence/step5-scifi-session.json new file mode 100644 index 0000000..7cd8608 --- /dev/null +++ b/test-fixtures/evidence/step5-scifi-session.json @@ -0,0 +1,1651 @@ +{ + "descriptors": [ + { + "category": "audio", + "id": "mix.master", + "kind": "range", + "label": "Master Volume", + "legacyId": "master-vol", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "mix.hull.level", + "kind": "range", + "label": "Hull Drone Level", + "legacyId": "hull-vol", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "mix.hull.frequency", + "kind": "range", + "label": "Hull Drone Frequency", + "legacyId": "hull-freq", + "max": 120, + "min": 30, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 1, + "unit": "hz", + "writable": true + }, + { + "category": "audio", + "id": "mix.hull.cutoff", + "kind": "range", + "label": "Hull Drone Cutoff", + "legacyId": "hull-cutoff", + "max": 400, + "min": 40, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 1, + "unit": "hz", + "writable": true + }, + { + "category": "audio", + "id": "mix.drive.level", + "kind": "range", + "label": "Drive / Core Level", + "legacyId": "warp-vol", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "mix.drive.pulse-rate", + "kind": "range", + "label": "Drive Pulse Rate", + "legacyId": "warp-bpm", + "max": 120, + "min": 20, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 1, + "unit": "bpm", + "writable": true + }, + { + "category": "audio", + "id": "mix.drive.carrier", + "kind": "range", + "label": "Drive Carrier Frequency", + "legacyId": "warp-carrier", + "max": 140, + "min": 30, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.5, + "unit": "hz", + "writable": true + }, + { + "category": "audio", + "id": "mix.environment.level", + "kind": "range", + "label": "Environmental Airflow Level", + "legacyId": "air-vol", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "mix.environment.cutoff", + "kind": "range", + "label": "Environmental Filter Cutoff", + "legacyId": "air-cutoff", + "max": 4000, + "min": 500, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 50, + "unit": "hz", + "writable": true + }, + { + "category": "audio", + "id": "mix.telemetry.level", + "kind": "range", + "label": "Telemetry Level", + "legacyId": "telemetry-vol", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "mix.telemetry.density", + "kind": "range", + "label": "Telemetry Density", + "legacyId": "telemetry-density", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.05, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "fnc.level", + "kind": "range", + "label": "Futuristic Noise Cancellation Level", + "legacyId": "fnc-level", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "audio", + "id": "fnc.enabled", + "kind": "state", + "label": "Futuristic Noise Cancellation Active", + "legacyId": "fnc-enable", + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "valueType": "boolean", + "writable": true + }, + { + "category": "speech", + "id": "speech.robot-amount", + "kind": "range", + "label": "Speech Robotic Modulation", + "legacyId": "ai-robot-amount", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "speech" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "args": { + "text": { + "maxLength": 2000, + "required": true, + "type": "string" + } + }, + "category": "speech", + "id": "speech.say", + "kind": "impulse", + "label": "Speak Text Line", + "readable": false, + "requires": [ + "speech" + ], + "restorable": false, + "writable": false + }, + { + "category": "speech", + "id": "speech.generate-announcement", + "kind": "impulse", + "label": "Generate Ambient AI Announcement", + "legacyId": "generate-announcement", + "readable": false, + "requires": [ + "speech" + ], + "restorable": false, + "writable": false + }, + { + "category": "observation", + "id": "view.activity", + "kind": "range", + "label": "Observation Visual Activity", + "legacyId": "observation-activity", + "max": 1, + "min": 0, + "readable": true, + "requires": [ + "observation" + ], + "restorable": true, + "step": 0.01, + "unit": "ratio", + "writable": true + }, + { + "category": "observation", + "id": "view.observation", + "kind": "state", + "label": "Observation Viewport Active", + "legacyId": "watch-experience", + "readable": true, + "requires": [ + "observation" + ], + "restorable": false, + "valueType": "boolean", + "writable": true + }, + { + "category": "observation", + "id": "view.viewport-frame", + "kind": "state", + "label": "Starship Bezel Frame Framing", + "legacyId": "observation-viewport", + "readable": true, + "requires": [ + "observation" + ], + "restorable": true, + "valueType": "boolean", + "writable": true + }, + { + "category": "transport", + "id": "transport.playing", + "kind": "state", + "label": "Audio Engine Online", + "legacyId": "master-play", + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "valueType": "boolean", + "writable": true + }, + { + "category": "transport", + "id": "transport.muted", + "kind": "state", + "label": "Master Mute", + "legacyId": "mute", + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "valueType": "boolean", + "writable": true + }, + { + "category": "alert", + "id": "alert.active", + "kind": "selection", + "label": "Active Tactical Alert Status", + "options": [ + { + "label": "Condition Green (Normal)", + "value": "none" + }, + { + "label": "Yellow Alert", + "value": "yellow" + }, + { + "label": "Red Alert", + "value": "red" + } + ], + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "writable": true + }, + { + "category": "universe", + "id": "universe.selected", + "kind": "selection", + "label": "Active Sci-Fi Universe", + "options": [ + { + "label": "Starfleet / Galaxy", + "value": "starfleet" + }, + { + "label": "Whataverse / Tardix", + "value": "whataverse" + }, + { + "label": "Industrial / Weyland", + "value": "industrial" + }, + { + "label": "Bioships / Leviathan", + "value": "bioships" + }, + { + "label": "Retro Future / 1960s", + "value": "retrofuture" + }, + { + "label": "Colonial Military", + "value": "military" + }, + { + "label": "Deep Space Generation", + "value": "deepspace" + }, + { + "label": "Frontier & Salvage", + "value": "outlaw" + }, + { + "label": "Orbital Stations & Docks", + "value": "spacestations" + }, + { + "label": "Whimsical Fleet", + "value": "comedy" + } + ], + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "writable": true + }, + { + "category": "preset", + "id": "preset.selected", + "kind": "selection", + "label": "Active Audio Preset", + "options": [ + { + "label": "Interprise-G: Main Bridge", + "universe": "starfleet", + "value": "tng-bridge" + }, + { + "label": "Interprise-G: Main Engineering", + "universe": "starfleet", + "value": "tng-engineering" + }, + { + "label": "Interprise-G: Crew Quarters", + "universe": "starfleet", + "value": "tng-quarters" + }, + { + "label": "USS Wayfarer: Bridge", + "universe": "starfleet", + "value": "voyager-bridge" + }, + { + "label": "USS Wayfarer: Class-N Worp Core", + "universe": "starfleet", + "value": "voyager-engineering" + }, + { + "label": "USS Defiance: Tactical Bridge", + "universe": "starfleet", + "value": "defiant-bridge" + }, + { + "label": "Deep Space 8: Ops Center", + "universe": "starfleet", + "value": "ds9-ops" + }, + { + "label": "Interprise NCC-1701.5: Bridge (TOS)", + "universe": "starfleet", + "value": "tos-bridge" + }, + { + "label": "Interprise NCC-1701.5: Engineering (TOS)", + "universe": "starfleet", + "value": "tos-engineering" + }, + { + "label": "Interprise NS-01: Command Bridge", + "universe": "starfleet", + "value": "nx-bridge" + }, + { + "label": "1962 Classic Console (1st / 2nd Doctors)", + "universe": "whataverse", + "value": "tardix-1963" + }, + { + "label": "Edwardian Secondary Console (4th Doctor)", + "universe": "whataverse", + "value": "tardix-victorian" + }, + { + "label": "The Coral Living TARDIX (9th / 10th Doctors)", + "universe": "whataverse", + "value": "tardix-coral" + }, + { + "label": "The Bronze Workshop (11th Doctor)", + "universe": "whataverse", + "value": "tardix-copper" + }, + { + "label": "The Cool Machine (12th Doctor)", + "universe": "whataverse", + "value": "tardix-cold-machine" + }, + { + "label": "The Humming Crystal Console (13th Doctor)", + "universe": "whataverse", + "value": "tardix-crystal" + }, + { + "label": "The Endless White (14th / 15th Doctors)", + "universe": "whataverse", + "value": "tardix-infinite-white" + }, + { + "label": "USCSS Nostramo: Ore Refinery Bridge", + "universe": "industrial", + "value": "ind-nostromo" + }, + { + "label": "Serenety: Firewing-Class Cargo Hold", + "universe": "industrial", + "value": "ind-serenity" + }, + { + "label": "Red Dwarve: Main Drive Corridor", + "universe": "industrial", + "value": "ind-red-dwarf" + }, + { + "label": "Starbug Z: Cockpit Environment", + "universe": "industrial", + "value": "ind-starbug" + }, + { + "label": "The Rasa: Dark Matter Bridge", + "universe": "industrial", + "value": "ind-raza" + }, + { + "label": "Rocinanta: Combat Ops & Epsilon Drive", + "universe": "industrial", + "value": "ind-rocinante" + }, + { + "label": "Hawk Translocator: Command Module", + "universe": "industrial", + "value": "ind-eagle" + }, + { + "label": "Valley Foundry: Agro-Dome Forest Hub", + "universe": "industrial", + "value": "ind-valley-forge" + }, + { + "label": "Meya: Leviathon Central Nexus", + "universe": "bioships", + "value": "bio-moya" + }, + { + "label": "Talym: Gunship Neural Bridge", + "universe": "bioships", + "value": "bio-talyn" + }, + { + "label": "The Lexxi: Primary Organ Bridge", + "universe": "bioships", + "value": "bio-lexx" + }, + { + "label": "Vorlan Cruiser: Sentient Core", + "universe": "bioships", + "value": "bio-vorlon" + }, + { + "label": "Wraithe Hive Ship: Throne Chamber", + "universe": "bioships", + "value": "bio-wraith" + }, + { + "label": "Species 8675309: Fluid Bioship", + "universe": "bioships", + "value": "bio-species-8472" + }, + { + "label": "Jupiter Duece: Upper Deck Astrogator", + "universe": "retrofuture", + "value": "ret-jupiter-2" + }, + { + "label": "The Liberation: Zen Flight Bridge", + "universe": "retrofuture", + "value": "ret-liberator" + }, + { + "label": "USS Cygnis: Victorian Engine Hall", + "universe": "retrofuture", + "value": "ret-cygnus" + }, + { + "label": "USS Palomina: Deep Research Pod", + "universe": "retrofuture", + "value": "ret-palomino" + }, + { + "label": "Discovery Zero: Habitation Centrifuge", + "universe": "retrofuture", + "value": "ret-discovery-one" + }, + { + "label": "Dim Star: Bomb Bay & Quarters", + "universe": "retrofuture", + "value": "ret-dark-star" + }, + { + "label": "Gunstarr: Tactical Combat Cockpit", + "universe": "retrofuture", + "value": "ret-gunstar" + }, + { + "label": "The Seeker: 25th Century Flagship", + "universe": "retrofuture", + "value": "ret-searcher" + }, + { + "label": "USS Solaco: Conestega Hangar Deck", + "universe": "military", + "value": "mil-sulaco" + }, + { + "label": "Battlestar Galactica: Combat Information Center (CIC)", + "universe": "military", + "value": "mil-galactica" + }, + { + "label": "Pale Star: Minbara/Vorlan Hybrid Bridge", + "universe": "military", + "value": "mil-white-star" + }, + { + "label": "EAS Agamemnon II: Omega Destroyer Bridge", + "universe": "military", + "value": "mil-agamemnon" + }, + { + "label": "Andromeda Ascending: Command Deck", + "universe": "military", + "value": "mil-andromeda" + }, + { + "label": "Excaliber: Triumph-Class Heavy Combat Core", + "universe": "military", + "value": "mil-excalibur" + }, + { + "label": "Colonial Adder Mk II: Cockpit Atmosphere", + "universe": "military", + "value": "mil-viper" + }, + { + "label": "The Avalin: Interstellar Cruise Concourse", + "universe": "deepspace", + "value": "deep-avalon" + }, + { + "label": "The Nightflier: Telepathic Corridor", + "universe": "deepspace", + "value": "deep-nightflyer" + }, + { + "label": "USS Ascention: Generation Ship Promenade", + "universe": "deepspace", + "value": "deep-ascension" + }, + { + "label": "Ark Zero: Evacuation Ark Bridge", + "universe": "deepspace", + "value": "deep-ark-one" + }, + { + "label": "Icarus I: Solar Shield Core", + "universe": "deepspace", + "value": "deep-icarus-ii" + }, + { + "label": "Event Boundary: Gravity Singularity Core", + "universe": "deepspace", + "value": "deep-event-horizon" + }, + { + "label": "Lewis & Clarke: Rescue Cutter Bridge", + "universe": "deepspace", + "value": "deep-lewis-clark" + }, + { + "label": "The Betsy: Salvage Freighter Mess", + "universe": "outlaw", + "value": "out-betty" + }, + { + "label": "BeBoppa: Living Quarters & Hangar", + "universe": "outlaw", + "value": "out-bebop" + }, + { + "label": "Outlaw Sun: Grapple Bridge", + "universe": "outlaw", + "value": "out-outlaw-star" + }, + { + "label": "Scorpius: Wanderer Salvage Vessel", + "universe": "outlaw", + "value": "out-scorpio" + }, + { + "label": "The Raider: Havoc Shuttle Bridge", + "universe": "outlaw", + "value": "out-marauder" + }, + { + "label": "The Milan: M-Ship Cockpit Lounge", + "universe": "outlaw", + "value": "out-milano" + }, + { + "label": "Babylon 4: Core Control & Zocolo", + "universe": "spacestations", + "value": "sta-babylon-5" + }, + { + "label": "Moonbase Beta: Main Mission Control", + "universe": "spacestations", + "value": "sta-moonbase-alpha" + }, + { + "label": "Sebastopol Station: Habitation Deck", + "universe": "spacestations", + "value": "sta-sevastopol" + }, + { + "label": "Tyco Station: Asteroid Construction Bay", + "universe": "spacestations", + "value": "sta-tycho" + }, + { + "label": "Ceres Minor Station: Sub-Crustal Tunnels", + "universe": "spacestations", + "value": "sta-ceres" + }, + { + "label": "Gatehouse Station: Quarantine Transfer Deck", + "universe": "spacestations", + "value": "sta-gateway" + }, + { + "label": "USS Norval: Command Bridge", + "universe": "comedy", + "value": "com-orville" + }, + { + "label": "NSEA Protractor: Beryllium Orb Engine Room", + "universe": "comedy", + "value": "com-protector" + }, + { + "label": "Heart of Silver: Unlikelihood Bridge", + "universe": "comedy", + "value": "com-heart-of-gold" + }, + { + "label": "HMS Camden Key: Flight Deck & Mess", + "universe": "comedy", + "value": "com-camden-lock" + }, + { + "label": "Spaceorb One: Mega-Warship Bridge", + "universe": "comedy", + "value": "com-spaceball-one" + } + ], + "readable": true, + "requires": [ + "audio" + ], + "restorable": true, + "writable": true + }, + { + "args": { + "text": { + "maxLength": 512, + "required": true, + "type": "string" + } + }, + "category": "display", + "id": "display.ticker", + "kind": "impulse", + "label": "Display Ticker Message (Transient)", + "readable": false, + "requires": [ + "display-text" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.alert-red", + "kind": "impulse", + "label": "Red Alert Impulse", + "legacyId": "red-alert", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.warp-jump", + "kind": "impulse", + "label": "Warp Jump Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.demat", + "kind": "impulse", + "label": "Demat Cycle Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.vortex", + "kind": "impulse", + "label": "Time Vortex Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.cloister", + "kind": "impulse", + "label": "Cloister Bell Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.flak-barrage", + "kind": "impulse", + "label": "Flak Barrage Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.ftl-jump", + "kind": "impulse", + "label": "FTL Jump Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.singularity", + "kind": "impulse", + "label": "Singularity Engage Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.solar-roar", + "kind": "impulse", + "label": "Solar Flare Roar Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.afterburner", + "kind": "impulse", + "label": "Afterburner Ignite Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.ludicrous-speed", + "kind": "impulse", + "label": "Ludicrous Speed Impulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "event", + "id": "event.improbability-flip", + "kind": "impulse", + "label": "Improbability Drive Flip", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.comm-badge", + "kind": "impulse", + "label": "Comm Badge Chime", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.door-swish", + "kind": "impulse", + "label": "Pneumatic Door Swish", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.bosun-whistle", + "kind": "impulse", + "label": "Bosun Pipe Call", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.medical-monitor", + "kind": "impulse", + "label": "Medical Vital Monitor", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.cardassian-door", + "kind": "impulse", + "label": "Heavy Bulkhead Door", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.turbolift", + "kind": "impulse", + "label": "Turbochute Whoosh", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.replicator", + "kind": "impulse", + "label": "Fabricator Materialize", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.transporter", + "kind": "impulse", + "label": "Translocator Beam", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.console-chirp", + "kind": "impulse", + "label": "Console Single Chirp", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.sonic", + "kind": "impulse", + "label": "Sonic Acoustic Screwdriver", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.lever-clunk", + "kind": "impulse", + "label": "Console Mechanical Lever Clunk", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.tardix-door", + "kind": "impulse", + "label": "Tardix Wood & Lock Door", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.demat-switch", + "kind": "impulse", + "label": "Demat Switch Rotor", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.telepathic-chime", + "kind": "impulse", + "label": "Telepathic Circuit Chime", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.air-handler", + "kind": "impulse", + "label": "Air Handler Valve Thud", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.mag-boot", + "kind": "impulse", + "label": "Mag-Boot Solenoid Latch", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.comlock", + "kind": "impulse", + "label": "Comlock Electronic Tone", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.solar-roar", + "kind": "impulse", + "label": "Solar Radiation Wind", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.tape-clunk", + "kind": "impulse", + "label": "Cassette Magnetic Transport Clunk", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.afterburner", + "kind": "impulse", + "label": "Thruster Burner Pulse", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.geiger", + "kind": "impulse", + "label": "Radiation Geiger Counter", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.dock-clamp", + "kind": "impulse", + "label": "Air Valve & Dock Clamp", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.dradis-ping", + "kind": "impulse", + "label": "Dradis Combat Sonar Ping", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + }, + { + "category": "sfx", + "id": "sfx.hal-chime", + "kind": "impulse", + "label": "HAL Logic Melodic Chime", + "readable": false, + "requires": [ + "audio" + ], + "restorable": false, + "writable": false + } + ], + "events": [ + { + "correlationId": "ngn-2-4", + "sequence": 1, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 1, + "target": "mix.master", + "timestamp": 1789398481614, + "type": "state.changed", + "value": 0.42, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-6", + "sequence": 2, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 2, + "target": "transport.muted", + "timestamp": 1789398482169, + "type": "state.changed", + "value": true, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-8", + "sequence": 3, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 3, + "target": "view.observation", + "timestamp": 1789398482745, + "type": "state.changed", + "value": true, + "xzbt": "5.2" + }, + { + "capability": "speech", + "sequence": 4, + "sessionId": "sess-3qv6dl1l", + "source": "system", + "state": "loading", + "timestamp": 1789398487493, + "type": "capability.changed", + "xzbt": "5.2" + }, + { + "capability": "speech", + "sequence": 5, + "sessionId": "sess-3qv6dl1l", + "source": "system", + "state": "ready", + "timestamp": 1789398489493, + "type": "capability.changed", + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-14", + "sequence": 6, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 4, + "target": "view.observation", + "timestamp": 1789398496868, + "type": "state.changed", + "value": false, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 7, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.hull.level", + "timestamp": 1789398497138, + "type": "state.changed", + "value": 0.55, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 8, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.hull.frequency", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 54, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 9, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.hull.cutoff", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 130, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 10, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.drive.level", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 0.95, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 11, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.drive.carrier", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 62, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 12, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.environment.level", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 0.4, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 13, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.environment.cutoff", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 1400, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 14, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.telemetry.level", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 0.3, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 15, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "mix.telemetry.density", + "timestamp": 1789398497139, + "type": "state.changed", + "value": 0.35, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-16", + "sequence": 16, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 5, + "target": "preset.selected", + "timestamp": 1789398497139, + "type": "selection.changed", + "value": "tng-engineering", + "xzbt": "5.2" + }, + { + "args": {}, + "correlationId": "ngn-2-18", + "sequence": 17, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "target": "sfx.comm-badge", + "timestamp": 1789398497437, + "type": "action.executed", + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 18, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.hull.level", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 0.5, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 19, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.hull.frequency", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 68, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 20, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.hull.cutoff", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 150, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 21, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.drive.level", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 0.65, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 22, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.drive.pulse-rate", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 52, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 23, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.drive.carrier", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 96, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 24, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.environment.level", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 0.45, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 25, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.environment.cutoff", + "timestamp": 1789398513254, + "type": "state.changed", + "value": 1800, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 26, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.telemetry.level", + "timestamp": 1789398513255, + "type": "state.changed", + "value": 0.55, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 27, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "mix.telemetry.density", + "timestamp": 1789398513255, + "type": "state.changed", + "value": 0.6, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 28, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "speech.robot-amount", + "timestamp": 1789398513255, + "type": "state.changed", + "value": 0.88, + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 29, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "universe.selected", + "timestamp": 1789398513255, + "type": "selection.changed", + "value": "whataverse", + "xzbt": "5.2" + }, + { + "correlationId": "ngn-2-22", + "sequence": 30, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "stateRevision": 6, + "target": "preset.selected", + "timestamp": 1789398513255, + "type": "selection.changed", + "value": "tardix-1963", + "xzbt": "5.2" + }, + { + "args": {}, + "correlationId": "ngn-2-24", + "sequence": 31, + "sessionId": "sess-3qv6dl1l", + "source": "host", + "target": "event.demat", + "timestamp": 1789398513519, + "type": "action.executed", + "xzbt": "5.2" + } + ], + "logs": [ + { + "code": "SESSION", + "detail": { + "sessionId": "sess-3qv6dl1l" + }, + "message": "Exhibit session established.", + "time": "2026-09-14T15:07:34.395Z" + }, + { + "code": "CAPABILITY_UNAVAILABLE", + "detail": null, + "message": "Event 'demat' not available in active universe", + "time": "2026-09-14T15:08:17.685Z" + }, + { + "code": "INVALID_VALUE", + "detail": null, + "message": "Missing required argument 'text'", + "time": "2026-09-14T15:08:18.244Z" + }, + { + "code": "INVALID_VALUE", + "detail": null, + "message": "Missing required argument 'text'", + "time": "2026-09-14T15:08:33.778Z" + } + ], + "metadata": { + "contract": { + "major": 5, + "minor": 2 + }, + "exhibit": { + "build": "production", + "product": "SciFi-XZBT", + "version": "5.2.0" + }, + "registryRevision": 1, + "sequence": 31, + "sessionId": "sess-3qv6dl1l", + "stateRevision": 6 + } +} \ No newline at end of file diff --git a/test-fixtures/host-verification.js b/test-fixtures/host-verification.js index 46a189c..58b600b 100644 --- a/test-fixtures/host-verification.js +++ b/test-fixtures/host-verification.js @@ -19,7 +19,7 @@ async function raw(transport, message) { }); } run.addEventListener('click', async () => { - run.disabled = true; results.textContent = ''; document.querySelector('#fixtures').replaceChildren(); + run.disabled = true; results.textContent = `Started: ${new Date().toISOString()}\n`; document.querySelector('#fixtures').replaceChildren(); let passed = 0; for (const name of ['aquarium', 'planetarium', 'haunted-house']) { const host = new ExhibitHost(); @@ -74,5 +74,6 @@ run.addEventListener('click', async () => { } catch (error) { results.textContent += `FAIL ${name}: ${error.message}\n`; } finally { host.disconnect(); frame.remove(); } } - results.textContent += `${passed}/3 real exhibits passed.\n`; run.disabled = false; + results.textContent += `${passed}/3 real exhibits passed.\nFinished: ${new Date().toISOString()}\n`; run.disabled = false; + console.info(results.textContent); }); diff --git a/test-fixtures/reference-exhibits/haunted-house/exhibit.js b/test-fixtures/reference-exhibits/haunted-house/exhibit.js index 03ed0c5..28c3bc2 100644 --- a/test-fixtures/reference-exhibits/haunted-house/exhibit.js +++ b/test-fixtures/reference-exhibits/haunted-house/exhibit.js @@ -582,7 +582,8 @@ switch (targetId) { case 'room.current': return h.room; case 'room.spirit': return h.spirit; - case 'room.haunt-level': return Math.round(h.hauntLevel * 1000) / 1000; + // Keep the simulation continuous; report bounded, integer hundredths. + case 'room.haunt-level': return Math.round(clamp(h.hauntLevel, 0, 1) * 100) / 100; case 'room.fog-density': return h.fogDensity; case 'room.candlelight': return h.candlelight; case 'room.audio-enabled': return h.audioEnabled; diff --git a/tests/connection.test.js b/tests/connection.test.js new file mode 100644 index 0000000..0e6c34e --- /dev/null +++ b/tests/connection.test.js @@ -0,0 +1,63 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { ExhibitConnection, exhibitURL } from '../src/connection.js'; +import { validateCatalog } from '../src/validation.js'; + +const base = 'http://127.0.0.1:4173/'; +test('connection accepts served paths and same-origin URLs, rejects unsupported locations', () => { + assert.equal(exhibitURL('/public/example/index.html', base), base + 'public/example/index.html'); + assert.equal(exhibitURL(' public/example/ ', base), base + 'public/example/'); + for (const value of ['', ' ', 'file:///C:/exhibit.html', 'https://other.example/', 'http://127.0.0.1:4174/', 'javascript:alert(1)', 'http://user:pass@127.0.0.1:4173/']) { + assert.throws(() => exhibitURL(value, base)); + } +}); +function setup(t, loadTimeoutMs = 1000) { + const frames = [], connections = [], errors = []; + const host = { disconnect() {}, async connect(frame) { connections.push(frame); }, report(e) { errors.push(e); } }; + const connection = new ExhibitConnection({ host, base, loadTimeoutMs, transport: frame => frame, + createFrame() { + const frame = { handlers: {}, addEventListener(type, fn) { this.handlers[type] = fn; }, remove() { this.removed = true; } }; + frames.push(frame); return frame; + } + }); + t.after(() => connection.disconnect()); + return { connection, frames, connections, errors }; +} +test('switch and disconnect cancel old frame loads; invalid URL preserves active frame', async t => { + const { connection, frames, connections } = setup(t); + connection.load('/public/one/'); connection.load('/public/two/'); + frames[0].handlers.load(); assert.equal(connections.length, 0); + frames[1].handlers.load(); assert.equal(connections.length, 1); + assert.throws(() => connection.load('https://other.example/')); + assert.equal(connection.frame, frames[1]); + connection.disconnect(); frames[1].handlers.load(); + assert.equal(connections.length, 1); assert.equal(connection.frame, null); + assert.ok(frames.every(f => f.removed)); +}); +test('reconnect negotiates existing frame and reloads remembered URL after disconnect', async t => { + const { connection, frames, connections } = setup(t); + connection.load('/public/one/'); frames[0].handlers.load(); + await connection.reconnect(); assert.equal(connections.length, 2); assert.equal(frames.length, 1); + connection.disconnect(); connection.reconnect(); + assert.equal(frames.length, 2); assert.equal(frames[1].src, base + 'public/one/'); + frames[1].handlers.load(); assert.equal(connections.length, 3); +}); +test('stalled document load ends with recoverable error', async t => { + const { connection, frames, errors } = setup(t, 5); + connection.load('/public/stalled/'); + await new Promise(resolve => setTimeout(resolve, 20)); + assert.equal(connection.loading, false); assert.equal(connection.frame, null); + assert.equal(errors[0].code, 'LOAD_TIMEOUT'); + connection.reconnect(); assert.equal(frames.length, 2); +}); +test('optional descriptor metadata is permissive when omitted and typed when supplied', () => { + const message = { exhibit: {}, contract: { major: 5 }, registryRevision: 0, stateRevision: 0, capabilities: [], + targets: [{ id: 'sample.enabled', kind: 'state', readable: true, writable: true, valueType: 'boolean', requires: [] }] }; + validateCatalog(message); + for (const metadata of [{ restorable: false, category: '' }, { restorable: true, category: 'Custom category' }]) { + validateCatalog({ ...message, targets: [{ ...message.targets[0], ...metadata }] }); + } + for (const metadata of [{ restorable: 'false' }, { restorable: null }, { restorable: 1 }, { category: null }, { category: [] }, { category: false }]) { + assert.throws(() => validateCatalog({ ...message, targets: [{ ...message.targets[0], ...metadata }] }), { code: 'INVALID_MESSAGE' }); + } +}); diff --git a/tests/haunted-house.test.js b/tests/haunted-house.test.js new file mode 100644 index 0000000..1faa5f6 --- /dev/null +++ b/tests/haunted-house.test.js @@ -0,0 +1,63 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import vm from 'node:vm'; +import { validateReportedValue } from '../src/validation.js'; + +function setup() { + const context = vm.createContext({ window: {} }); + for (const file of ['shared/contract-core.js', 'haunted-house/exhibit.js', 'haunted-house/contract-adapter.js']) { + vm.runInContext(readFileSync(new URL(`../test-fixtures/reference-exhibits/${file}`, import.meta.url), 'utf8'), context); + } + const exhibit = new context.window.HauntedHouseExhibit({ + canvas: { getContext: () => ({ setTransform() {} }), getBoundingClientRect: () => ({ width: 100, height: 100 }) }, + announcer: { flash() {} } + }); + const core = context.window.HauntedHouseContract.create(exhibit, { sync() {} }); + const descriptor = core.catalog.get('room.haunt-level'); + assert.deepEqual([descriptor.min, descriptor.max, descriptor.step], [0, 1, 0.01]); + return { exhibit, core, descriptor }; +} + +test('Haunted House reports bounded hundredths while preserving continuous drift', () => { + const { exhibit, core, descriptor } = setup(); + for (const [raw, expected] of [[-0.1, 0], [0, 0], [0.0049, 0], [0.0051, 0.01], [0.303, 0.3], [0.999, 1], [1, 1], [1.1, 1]]) { + exhibit.house.hauntLevel = raw; + assert.equal(core.readValue(descriptor.id), expected); + assert.equal(exhibit.house.hauntLevel, raw, 'reading must not quantize the simulation'); + } + for (const room of ['foyer', 'library', 'cellar', 'attic', 'nursery']) { + exhibit.setRoom(room); + for (const spirit of ['restless', 'vengeful', 'mourning', 'mischievous']) { + exhibit.setSpirit(spirit); + exhibit.house.hauntLevel = 0.303; + for (let i = 0; i < 600; i++) { + exhibit.house.step(1 / 60); + validateReportedValue(descriptor, core.readValue(descriptor.id)); + } + assert.notEqual(exhibit.house.hauntLevel, 0.303, 'simulation must keep drifting'); + } + } +}); + +test('Haunted House snapshots and mutation events stay valid through Manifest, drift and Calm', () => { + const { exhibit, core, descriptor } = setup(); + core.handleHello({ supportedContractMajors: [5] }); + const checkSnapshot = () => { + const snapshot = core.stateSnapshot(); + for (const target of core.catalog.descriptors().filter(t => t.readable)) validateReportedValue(target, snapshot.values[target.id]); + return snapshot.values[descriptor.id]; + }; + assert.equal(core.invokeAction('action.manifest', {}, 'host').ok, true); + assert.equal(exhibit.house.hauntLevel, 1); + assert.equal(checkSnapshot(), 1); + for (let i = 0; i < 600; i++) { exhibit.house.step(1 / 60); checkSnapshot(); } + for (const value of [0, 0.01, 0.29, 0.5, 0.99, 1]) { + assert.equal(core.applyMutation(descriptor.id, value, 'ui').ok, true); + assert.equal(checkSnapshot(), value); + } + for (const event of core.eventLog().filter(e => e.type === 'state.changed')) validateReportedValue(core.catalog.get(event.target), event.value); + assert.ok(core.eventLog().some(e => e.type === 'action.executed' && e.target === 'action.manifest')); + assert.equal(core.invokeAction('action.calm', {}, 'host').ok, true); + assert.equal(checkSnapshot(), 0); +});