generated from Labyricorn/labyricorn-project-template
Post Step 4 Completion
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 };
|
||||
})();
|
||||
Reference in New Issue
Block a user