Files
XZBT-NGN/test-fixtures/reference-exhibits/haunted-house/contract-adapter.js
T
Labyricorn 745912e451 Steps 6.4-6.7B — Local surfaces, reference-exhibit validation, SciFi Observation surface
One commit for the work accumulated in the working tree since Step 6.3,
which had never been split into per-step commits:

- src/local-surfaces.js + src/surface-url.js (new); src/ui.js,
  src/validation.js, src/connection.js and public/index.html updated for
  local-surface hosting and generic surface rendering
- tests: local-surfaces (20), scifi-surfaces (24) and postmessage-interop (7)
  new; connection/museum-gallery/surface-validation suites updated
- reference exhibits: shared/contract-core.js defaults to Contract 5.3
  (major 5, minor 3, xzbt 5.3); museum-gallery advertises its surface
  catalog; aquarium/haunted-house/planetarium adapters updated
- SciFi-XZBT (Step 6.7A/6.7B): surface-mode.js + surface-bus.js,
  Observation-surface boot branch, local-change hooks, view.pillars /
  view.warp-flight targets; fixture byte-identical to G:/.vibe/SciFi-XZBT
- SciFi-XZBT contract adapter handshake fix: the inbound bridge filter no
  longer gates on an exact advisory xzbt value (Contract 5.3 §6.5), only on
  its presence/type, matching the host's own envelope validation; the
  adapter now advertises contract minor 3 / version 5.3.0, which it already
  implemented via the 5.3 surfaces field. Root cause of the five failing
  postmessage-interop tests (host hello was silently dropped).
- docs: architecture 6.4 and 6.7A, reference 6.6 and 6.7; evidence logs;
  test-fixtures/PROVENANCE.md resync record

Test results: NGN 154/154 (was 149/154); postmessage-interop 7/7 (was 2/7);
SciFi contract harness 21/21, real-adapter suite 32/32. git diff --check
clean for changed files; two pre-existing trailing-whitespace lines remain
in test-fixtures/reference-exhibits/scifi/index.html, copied verbatim from
the authoritative SciFi source.

Step 6.7 live verification (browser Observation, packaged standalone) is
still pending and is not claimed here.
2026-09-14 19:45:27 -07:00

219 lines
6.9 KiB
JavaScript

/*
* Haunted House — XZBT Exhibit Contract 5.3 adapter.
*
* This adapter is the one that has to be honest about capabilities. The
* exhibit's audio engine cannot start until the browser has seen a user
* gesture, so `audio` is declared `available` at boot and moves to
* `loading` then `ready` when it really does. The adapter mirrors the
* exhibit's real state into the capability registry; it never invents a
* transition (Authoring Guide §M).
*
* The audio-dependent actions are gated on that capability, so invoking
* `action.moan` before the engine is ready returns CAPABILITY_UNAVAILABLE
* rather than silently doing nothing.
*/
(function () {
'use strict';
var Core = window.XZBTContractCore;
var IDENTITY = {
product: 'Haunted House',
version: '1.0.0',
build: 'reference-exhibit'
};
var DESCRIPTORS = [
{
id: 'room.current',
kind: 'selection',
label: 'Room',
description: 'Room the visitor is standing in.',
options: [
{ value: 'foyer', label: 'Foyer' },
{ value: 'library', label: 'Library' },
{ value: 'cellar', label: 'Cellar' },
{ value: 'attic', label: 'Attic' },
{ value: 'nursery', label: 'Nursery' }
],
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.spirit',
kind: 'selection',
label: 'Spirit',
description: 'Which presence is currently resident.',
options: [
{ value: 'restless', label: 'Restless' },
{ value: 'vengeful', label: 'Vengeful' },
{ value: 'mourning', label: 'Mourning' },
{ value: 'mischievous', label: 'Mischievous' }
],
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.haunt-level',
kind: 'range',
label: 'Haunt level',
description: 'Intensity of the haunting, 0 (dormant) to 1 (manifest).',
min: 0, max: 1, step: 0.01,
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.fog-density',
kind: 'range',
label: 'Fog density',
description: 'Density of the ground fog.',
min: 0, max: 1, step: 0.01,
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.candlelight',
kind: 'range',
label: 'Candlelight',
description: 'Strength of the candle pool of light.',
min: 0, max: 1, step: 0.01,
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.audio-enabled',
kind: 'state',
valueType: 'boolean',
label: 'Audio enabled',
description: 'Whether the house is permitted to make sound.',
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'room.paused',
kind: 'state',
valueType: 'boolean',
label: 'Held',
description: 'Whether the haunt simulation is frozen.',
readable: true, writable: true, restorable: true,
category: 'room', requires: []
},
{
id: 'action.moan',
kind: 'impulse',
label: 'Moan',
description: 'Emit a long descending moan.',
readable: false, writable: false, restorable: false,
category: 'action', requires: ['audio']
},
{
id: 'action.slam-door',
kind: 'impulse',
label: 'Slam door',
description: 'Slam a door somewhere in the house.',
readable: false, writable: false, restorable: false,
category: 'action', requires: ['audio']
},
{
id: 'action.manifest',
kind: 'impulse',
label: 'Manifest',
description: 'Bring the apparition to full manifestation.',
readable: false, writable: false, restorable: false,
category: 'action', requires: []
},
{
id: 'action.calm',
kind: 'impulse',
label: 'Calm',
description: 'Settle the house back to dormancy.',
readable: false, writable: false, restorable: false,
category: 'action', requires: []
},
{
id: 'telemetry.audio-state',
kind: 'state',
valueType: 'string',
label: 'Audio engine state',
description: 'Live state of the Web Audio engine.',
readable: true, writable: false, restorable: false,
category: 'telemetry', requires: []
},
{
id: 'telemetry.room-damp',
kind: 'state',
valueType: 'number',
label: 'Room damping',
description: 'How strongly the current room suppresses the haunt.',
readable: true, writable: false, restorable: false,
category: 'telemetry', requires: []
}
];
function createHauntedHouseContract(exhibit, ui) {
var catalog = new Core.Catalog(DESCRIPTORS);
var capabilities = new Core.CapabilityRegistry();
capabilities.declare('render', 'ready');
/* Honest initial state: the engine exists but the browser will not let
* it start until a gesture arrives. */
capabilities.declare('audio', 'available');
var setters = {
'room.current': function (v) { return exhibit.setRoom(v); },
'room.spirit': function (v) { return exhibit.setSpirit(v); },
'room.haunt-level': function (v) { return exhibit.setHauntLevel(v); },
'room.fog-density': function (v) { return exhibit.setFogDensity(v); },
'room.candlelight': function (v) { return exhibit.setCandlelight(v); },
'room.audio-enabled': function (v) { return exhibit.setAudioEnabled(v); },
'room.paused': function (v) { return exhibit.setPaused(v); }
};
var readers = {};
var ids = catalog.ids();
for (var i = 0; i < ids.length; i++) {
(function (id) {
readers[id] = function () { return exhibit.read(id); };
})(ids[i]);
}
var actions = {
'action.moan': function () { return exhibit.moan(); },
'action.slam-door': function () { return exhibit.slamDoor(); },
'action.manifest': function () { return exhibit.manifest(); },
'action.calm': function () { return exhibit.calm(); }
};
var core = new Core.ContractCore({
identity: IDENTITY,
catalog: catalog,
capabilities: capabilities,
setters: setters,
readers: readers,
actions: actions
});
/* Mirror the exhibit's real audio lifecycle into the contract. This is
* a genuine state change driven by the browser, not a scripted one. */
exhibit.audio.onStateChange = function (state) {
core.setCapabilityState('audio', state);
ui.sync();
};
ui.dispatchOverride = function (targetId, value) {
core.applyMutation(targetId, value, 'ui');
ui.sync();
};
ui.invokeOverride = function (targetId) {
core.invokeAction(targetId, {}, 'ui');
ui.sync();
};
exhibit.onChange = function () { ui.sync(); };
return core;
}
window.HauntedHouseContract = { create: createHauntedHouseContract, IDENTITY: IDENTITY };
})();