generated from Labyricorn/labyricorn-project-template
219 lines
6.9 KiB
JavaScript
219 lines
6.9 KiB
JavaScript
/*
|
|
* Haunted House — XZBT Exhibit Contract 5.2 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 };
|
|
})();
|