generated from Labyricorn/labyricorn-project-template
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.
219 lines
6.9 KiB
JavaScript
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 };
|
|
})();
|