hypertwist/Content/Browser/src/browser-spatial-runtime-fallback.js
2026-06-22 23:24:30 +00:00

863 lines
26 KiB
JavaScript

const BROWSER_SUPPORT_ADAPTERS = [
{
activation: 'bundled-module',
description: 'Reference glTF renderer and viewer module wired into the first-party browser runtime.',
id: '1e-gltf-sample-renderer',
phase: '1E',
repo: 'KhronosGroup/glTF-Sample-Renderer',
sourcePathHint: '.external/glTF-Sample-Renderer'
},
{
activation: 'native-sidecar',
description: 'Native/OpenCV solver adjunct retained as a browser-shell fallback lane rather than a direct npm package.',
id: '1g-tentone-native-solver',
phase: '1G',
repo: 'tentone/rubix-solver',
sourcePathHint: '.external/rubix-solver'
},
{
activation: 'source-sidecar',
description: 'Rust donor retained as a source-sidecar surface for follow-up browser spatial composition.',
id: '1h-code-vr',
phase: '1H',
repo: 'NuiLab/code-vr',
sourcePathHint: '.external/code-vr'
},
{
activation: 'prebuilt-script',
description: 'Prebuilt ClayGL runtime script bridged into the browser shell for viewer and QA surfaces.',
id: '1h-claygl',
phase: '1H',
repo: 'pissang/claygl',
sourcePathHint: '.external/claygl/dist/claygl.js'
},
{
activation: 'prebuilt-script',
description: 'Prebuilt Clay Viewer shell loaded beside ClayGL for bounded browser inspection.',
id: '1h-clay-viewer',
phase: '1H',
repo: 'pissang/clay-viewer',
sourcePathHint: '.external/clay-viewer/dist/clay-viewer.js'
},
{
activation: 'bundled-module',
description: 'Three.js post-processing core bundled into the runtime module graph.',
id: '1h-postprocessing',
phase: '1H',
repo: 'pmndrs/postprocessing',
sourcePathHint: '.external/postprocessing'
},
{
activation: 'bundled-module',
description: 'React bridge for post-processing loaded into the browser spatial stack.',
id: '1h-react-postprocessing',
phase: '1H',
repo: 'pmndrs/react-postprocessing',
sourcePathHint: '.external/react-postprocessing'
},
{
activation: 'bundled-module',
description: 'Drei helper surface bundled for browser-spatial utility composition.',
id: '1h-drei',
phase: '1H',
repo: 'pmndrs/drei',
sourcePathHint: '.external/drei'
},
{
activation: 'bundled-module',
description: 'World-anchored UI kit bundled for browser-spatial HUD and control surfaces.',
id: '1h-uikit',
phase: '1H',
repo: 'pmndrs/uikit',
sourcePathHint: '.external/uikit'
},
{
activation: 'bundled-module',
description: 'Three.js helper library bundled for browser runtime support.',
id: '1h-three-stdlib',
phase: '1H',
repo: 'pmndrs/three-stdlib',
sourcePathHint: '.external/three-stdlib'
},
{
activation: 'bundled-module',
description: 'Animation and math helper pack bundled into the runtime.',
id: '1h-maath',
phase: '1H',
repo: 'pmndrs/maath',
sourcePathHint: '.external/maath'
},
{
activation: 'bundled-module',
description: 'State-store runtime bundled for browser shell state and derived panel demos.',
id: '1h-zustand',
phase: '1H',
repo: 'pmndrs/zustand',
sourcePathHint: '.external/zustand'
},
{
activation: 'bundled-module',
description: 'Parameter-control runtime bundled for future tuning overlays.',
id: '1h-leva',
phase: '1H',
repo: 'pmndrs/leva',
sourcePathHint: '.external/leva'
},
{
activation: 'bundled-module',
description: 'Gesture capture hooks bundled for browser interaction surfaces.',
id: '1h-use-gesture',
phase: '1H',
repo: 'pmndrs/use-gesture',
sourcePathHint: '.external/use-gesture'
},
{
activation: 'bundled-module',
description: 'Motion and interpolation runtime bundled for browser spatial transitions.',
id: '1h-react-spring',
phase: '1H',
repo: 'pmndrs/react-spring',
sourcePathHint: '.external/react-spring'
},
{
activation: 'bundled-module',
description: '2D render engine bundled beneath the analytics surface.',
id: '1i-zrender',
phase: '1I',
repo: 'ecomfe/zrender',
sourcePathHint: '.external/zrender'
},
{
activation: 'bundled-module',
description: '3D analytics extension bundled beneath the ECharts runtime.',
id: '1i-echarts-gl',
phase: '1I',
repo: 'ecomfe/echarts-gl',
sourcePathHint: '.external/echarts-gl'
},
{
activation: 'bundled-module',
description: 'Model Viewer web component bundled into the browser shell.',
id: '1j-model-viewer',
phase: '1J',
repo: 'google/model-viewer/packages/model-viewer',
sourcePathHint: '.external/model-viewer/packages/model-viewer'
},
{
activation: 'bundled-module',
description: 'Effects package bundled beside model-viewer for decorated previews.',
id: '1j-model-viewer-effects',
phase: '1J',
repo: 'google/model-viewer/packages/model-viewer-effects',
sourcePathHint: '.external/model-viewer/packages/model-viewer-effects'
},
{
activation: 'editor-sidecar',
description: 'Editor-oriented inspection UI retained as a sidecar instead of a default runtime export.',
id: '1j-space-opera',
phase: '1J',
repo: 'google/model-viewer/packages/space-opera',
sourcePathHint: '.external/model-viewer/packages/space-opera'
},
{
activation: 'qa-sidecar',
description: 'Renderer comparison and fidelity tooling kept in the QA lane rather than loaded by default.',
id: '1j-render-fidelity-tools',
phase: '1J',
repo: 'google/model-viewer/packages/render-fidelity-tools',
sourcePathHint: '.external/model-viewer/packages/render-fidelity-tools'
}
];
const BUNDLED_ADAPTER_ACTIVATIONS = new Set(['bundled-module', 'prebuilt-script']);
function resolveFallbackBootState()
{
const ExistingBootState = window.HyperTwistBrowserBootState;
if (ExistingBootState && typeof ExistingBootState === 'object')
{
return ExistingBootState;
}
const BootState = {};
window.HyperTwistBrowserBootState = BootState;
return BootState;
}
function getUnrealBridgeHandle()
{
const Candidate = window.ue;
if (!Candidate || typeof Candidate !== 'object')
{
return null;
}
const HyperTwistBridge = Candidate.hypertwist;
return HyperTwistBridge && typeof HyperTwistBridge === 'object'
? HyperTwistBridge
: null;
}
function tryParseJsonPayload(Value)
{
if (typeof Value !== 'string')
{
return Value;
}
try
{
return JSON.parse(Value);
}
catch
{
return Value;
}
}
function createElement(TagName, ClassName, TextContent)
{
const Element = document.createElement(TagName);
if (ClassName)
{
Element.className = ClassName;
}
if (typeof TextContent === 'string')
{
Element.textContent = TextContent;
}
return Element;
}
function serializePretty(Payload)
{
if (typeof Payload === 'string')
{
return Payload;
}
try
{
return JSON.stringify(Payload, null, 2);
}
catch
{
return String(Payload);
}
}
function createSectionHeader(Title, Note)
{
const Header = createElement('div', 'section-header');
Header.append(
createElement('h2', 'section-title', Title),
createElement('p', 'section-note', Note)
);
return Header;
}
function dispatchToListeners(Listeners, Payload)
{
Listeners.forEach((Listener) => Listener(Payload));
}
function createFallbackBridge(BootState)
{
const CommandListeners = new Set();
const ShellStateListeners = new Set();
let LastCommandPayload;
let LastShellStatePayload;
let HasLastCommandPayload = false;
let HasLastShellStatePayload = false;
function postEnvelope(Type, Payload)
{
const Envelope = {
type: Type,
payload: Payload,
emittedAtUtc: new Date().toISOString()
};
const SerializedEnvelope = JSON.stringify(Envelope);
const UnrealBridgeHandle = getUnrealBridgeHandle();
let DeliveredToUnreal = false;
if (UnrealBridgeHandle && typeof UnrealBridgeHandle.notifyEnvelope === 'function')
{
UnrealBridgeHandle.notifyEnvelope(SerializedEnvelope);
DeliveredToUnreal = true;
}
if (Type === 'hypertwist-state'
&& UnrealBridgeHandle
&& typeof UnrealBridgeHandle.notifyState === 'function')
{
UnrealBridgeHandle.notifyState(JSON.stringify(Payload));
DeliveredToUnreal = true;
}
if (DeliveredToUnreal)
{
return;
}
if (window.parent && typeof window.parent.postMessage === 'function')
{
window.parent.postMessage(Envelope, '*');
}
}
window.addEventListener('message', function onBridgeMessage(Event)
{
const Envelope = Event.data;
if (!Envelope || typeof Envelope !== 'object')
{
return;
}
if (Envelope.type === 'hypertwist-command')
{
Bridge.receiveCommand(Envelope.payload);
}
else if (Envelope.type === 'hypertwist-shell-state')
{
Bridge.setShellState(Envelope.payload);
}
});
const Bridge = {
onCommand(Listener)
{
CommandListeners.add(Listener);
if (HasLastCommandPayload)
{
Listener(LastCommandPayload);
}
return function unsubscribeCommand()
{
CommandListeners.delete(Listener);
};
},
onShellState(Listener)
{
ShellStateListeners.add(Listener);
if (HasLastShellStatePayload)
{
Listener(LastShellStatePayload);
}
return function unsubscribeShellState()
{
ShellStateListeners.delete(Listener);
};
},
receiveCommand(Payload)
{
LastCommandPayload = tryParseJsonPayload(Payload);
HasLastCommandPayload = true;
BootState.commandReceiveCount = (BootState.commandReceiveCount || 0) + 1;
BootState.lastCommandReceivedAtUtc = new Date().toISOString();
dispatchToListeners(CommandListeners, LastCommandPayload);
},
setShellState(Payload)
{
LastShellStatePayload = tryParseJsonPayload(Payload);
HasLastShellStatePayload = true;
BootState.shellStateReceiveCount = (BootState.shellStateReceiveCount || 0) + 1;
BootState.lastShellStateReceivedAtUtc = new Date().toISOString();
dispatchToListeners(ShellStateListeners, LastShellStatePayload);
},
sendState(Payload)
{
postEnvelope('hypertwist-state', Payload);
},
notifyRuntimeReady(Payload)
{
const UnrealBridgeHandle = getUnrealBridgeHandle();
if (UnrealBridgeHandle && typeof UnrealBridgeHandle.notifyRuntimeReady === 'function')
{
UnrealBridgeHandle.notifyRuntimeReady(JSON.stringify(Payload));
return;
}
postEnvelope('hypertwist-runtime-ready', Payload);
}
};
return Bridge;
}
async function loadAdapter(AdapterId)
{
const Adapter = BROWSER_SUPPORT_ADAPTERS.find((Candidate) => Candidate.id === AdapterId);
if (!Adapter)
{
throw new Error('Unknown browser support adapter: ' + AdapterId);
}
if (BUNDLED_ADAPTER_ACTIVATIONS.has(Adapter.activation))
{
return {
details: ['Clean checkout fallback is active; build the browser bundle to exercise this adapter in-browser.'],
id: AdapterId,
note: 'Fallback runtime is active, so bundled browser modules are advertised but not loaded from source checkout state.',
status: 'deferred'
};
}
return {
details: [Adapter.sourcePathHint],
id: AdapterId,
note: 'Adapter is intentionally represented as a sidecar in the plain-JS fallback shell.',
status: 'loaded'
};
}
function createHeroSection(BootState)
{
const HeroElement = createElement('section', 'hero');
HeroElement.append(
createElement('h1', undefined, 'HyperTwist Browser Runtime'),
createElement(
'p',
undefined,
'This clean-checkout fallback keeps the embedded browser lane truthful and runnable. When the built runtime bundle is present the authoritative shell upgrades automatically; when it is absent, the shell still renders, bridges Unreal state, and surfaces which adapters remain bundled or sidecar-only.'
)
);
const HeroMetrics = createElement('div', 'hero-grid');
[
{ label: 'Runtime mode', value: 'plain-js fallback' },
{ label: 'Fallback reason', value: BootState.fallbackReason || 'bundle unavailable' },
{ label: 'Adapters surfaced', value: String(BROWSER_SUPPORT_ADAPTERS.length) }
].forEach(function appendMetric(Metric)
{
const MetricElement = createElement('div', 'metric');
MetricElement.append(
createElement('strong', undefined, Metric.label),
createElement('span', undefined, Metric.value)
);
HeroMetrics.appendChild(MetricElement);
});
HeroElement.appendChild(HeroMetrics);
return HeroElement;
}
function createLogPanel()
{
const LogPanel = createElement('section', 'panel');
LogPanel.append(
createElement('h2', undefined, 'Runtime Log'),
createElement(
'p',
'runtime-note',
'Bridge and adapter events stay inspectable here during clean-checkout fallback mode.'
)
);
const LogList = createElement('div', 'log-list');
LogPanel.appendChild(LogList);
return {
appendLog(Title, Message)
{
const Entry = createElement('div', 'log-entry');
Entry.append(
createElement('strong', undefined, Title),
createElement('div', undefined, Message)
);
LogList.prepend(Entry);
},
panel: LogPanel
};
}
function buildRuntimeStatusSnapshot(BootState, Counters)
{
return {
runtimeStatusId: BootState.runtimeStatusId || 'runtime-ready',
runtimeMode: BootState.runtimeMode || 'unknown',
bootstrapStartedAtUtc: BootState.bootstrapStartedAtUtc || 'pending',
runtimeReadyAtUtc: BootState.runtimeReadyAtUtc || 'pending',
shellAuthority: BootState.shellAuthority || 'Content/Browser/index.html',
adapterCount: BootState.adapterCount || BROWSER_SUPPORT_ADAPTERS.length,
queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady || 0,
queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady || 0,
commandReceiveCount: Counters.commandReceiveCount,
shellStateReceiveCount: Counters.shellStateReceiveCount,
lastCommandReceivedAtUtc: BootState.lastCommandReceivedAtUtc || 'none',
lastShellStateReceivedAtUtc: BootState.lastShellStateReceivedAtUtc || 'none',
bridgeTrafficStatus: Counters.shellStateReceiveCount > 0
? 'shell-state-live'
: Counters.commandReceiveCount > 0
? 'command-only-live'
: 'awaiting-unreal-traffic',
fallbackReason: BootState.fallbackReason || null
};
}
function createRuntimeStatusPanel(BootState, Counters, UEBridge, AppendLog)
{
const StatusPanel = createElement('section', 'panel');
StatusPanel.append(
createElement('h2', undefined, 'Browser Runtime Status'),
createElement(
'p',
'runtime-note',
'The clean-checkout fallback shell now exposes first-party runtime mode, retained startup queue carryover, and live Unreal bridge traffic in one compact status surface.'
)
);
const StatusActions = createElement('div', 'solver-actions');
const SendStatusButton = createElement('button', undefined, 'Send Runtime Status');
const StatusData = createElement('div', 'data-block');
const StatusPre = createElement('pre');
StatusData.appendChild(StatusPre);
StatusActions.appendChild(SendStatusButton);
StatusPanel.append(StatusActions, StatusData);
function refresh()
{
StatusPre.textContent = serializePretty(buildRuntimeStatusSnapshot(BootState, Counters));
}
SendStatusButton.addEventListener('click', function onSendStatus()
{
UEBridge.sendState({
type: 'browser-runtime-status',
source: 'browser-runtime-fallback',
status: buildRuntimeStatusSnapshot(BootState, Counters)
});
AppendLog('Browser Runtime Status', 'Sent the current browser runtime status envelope to Unreal.');
});
return {
panel: StatusPanel,
refresh
};
}
function createStatePanel()
{
const StatePanel = createElement('section', 'panel');
StatePanel.append(
createElement('h2', undefined, 'Browser Shell State'),
createElement(
'p',
'runtime-note',
'Unreal can push shell-state and command envelopes into the fallback runtime exactly like the bundled runtime.'
)
);
const StateData = createElement('div', 'data-block');
const StatePre = createElement('pre');
StatePre.textContent = '{\n "status": "waiting-for-unreal-shell-state"\n}';
StateData.appendChild(StatePre);
StatePanel.appendChild(StateData);
return {
panel: StatePanel,
setPayload(Payload)
{
StatePre.textContent = serializePretty(Payload);
}
};
}
function createAdapterCard(Adapter, UEBridge, AppendLog)
{
const CardElement = createElement('article', 'card');
const CardHeader = createElement('div', 'card-header');
const CardTitleBlock = createElement('div');
CardTitleBlock.append(
createElement('span', 'phase-chip', Adapter.phase),
createElement('h2', undefined, Adapter.repo)
);
const StatusChip = createElement('span', 'status-chip', 'idle');
StatusChip.dataset.status = 'idle';
CardHeader.append(CardTitleBlock, StatusChip);
const ActivationChip = createElement('span', 'activation-chip', Adapter.activation);
const Description = createElement('p', undefined, Adapter.description);
const SourceCode = createElement('code', undefined, Adapter.sourcePathHint);
const Actions = createElement('div', 'card-actions');
const LoadButton = createElement('button', undefined, 'Resolve Adapter');
const EnvelopeButton = createElement('button', 'secondary', 'Send Envelope');
LoadButton.addEventListener('click', async function onLoadAdapter()
{
StatusChip.dataset.status = 'loading';
StatusChip.textContent = 'loading';
try
{
const Result = await loadAdapter(Adapter.id);
StatusChip.dataset.status = Result.status === 'loaded' ? 'ready' : 'idle';
StatusChip.textContent = Result.status;
AppendLog(
Adapter.repo,
Result.note || ('Resolved ' + Adapter.id + '.')
);
}
catch (Error)
{
StatusChip.dataset.status = 'error';
StatusChip.textContent = 'error';
AppendLog(Adapter.repo, Error instanceof Error ? Error.message : 'Unknown load failure.');
}
});
EnvelopeButton.addEventListener('click', function onSendEnvelope()
{
UEBridge.sendState({
source: 'browser-runtime-fallback',
adapterId: Adapter.id,
repo: Adapter.repo,
activation: Adapter.activation,
runtimeMode: 'fallback-js'
});
AppendLog(Adapter.repo, 'Sent adapter envelope to Unreal.');
});
Actions.append(LoadButton, EnvelopeButton);
CardElement.append(CardHeader, ActivationChip, Description, SourceCode, Actions);
return CardElement;
}
function createAdapterSection(UEBridge, AppendLog)
{
const AdapterSection = createElement('section', 'section');
AdapterSection.appendChild(
createSectionHeader(
'Adapter Surface',
'Adapters remain visible in clean checkout state. Bundled browser modules are marked deferred until `npm run build` produces the browser runtime bundle.'
)
);
const AdapterGrid = createElement('div', 'stack-grid');
BROWSER_SUPPORT_ADAPTERS.forEach(function appendAdapter(Adapter)
{
AdapterGrid.appendChild(createAdapterCard(Adapter, UEBridge, AppendLog));
});
AdapterSection.append(AdapterGrid);
return AdapterSection;
}
function createSolverPanel(UEBridge, AppendLog)
{
const SolverPanel = createElement('section', 'panel');
SolverPanel.append(
createElement('h2', undefined, 'Tentone Native Solver Request'),
createElement(
'p',
'runtime-note',
'The OpenCV donor remains a native-sidecar lane. This fallback shell sends a first-party solver request envelope instead of pretending to import the donor in-browser.'
)
);
const SolverField = createElement('label', 'field');
SolverField.appendChild(createElement('span', undefined, 'Classic facelet string'));
const SolverInput = createElement('textarea');
SolverInput.value = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB';
SolverField.appendChild(SolverInput);
const SolverActions = createElement('div', 'solver-actions');
const SolverButton = createElement('button', undefined, 'Send Solver Request');
SolverButton.addEventListener('click', function onSolve()
{
UEBridge.sendState({
type: 'tentone-native-solver-request',
source: 'browser-runtime-fallback',
faceletString: SolverInput.value.trim()
});
AppendLog('Tentone Native Solver', 'Sent a solver request envelope to Unreal.');
});
SolverActions.appendChild(SolverButton);
SolverPanel.append(SolverField, SolverActions);
return SolverPanel;
}
function createUtilitySection(BootState, Counters, UEBridge)
{
const UtilitySection = createElement('section', 'section');
UtilitySection.appendChild(
createSectionHeader(
'Fallback Utilities',
'The plain-JS runtime keeps state bridging and native-sidecar requests live even when bundled browser modules have been cleaned after validation.'
)
);
const { appendLog, panel: LogPanel } = createLogPanel();
const { panel: StatusPanel, refresh: RefreshRuntimeStatus } = createRuntimeStatusPanel(
BootState,
Counters,
UEBridge,
appendLog
);
const StatePanel = createStatePanel();
const UtilityGrid = createElement('div', 'panel-grid');
UtilityGrid.append(
StatusPanel,
createSolverPanel(UEBridge, appendLog),
StatePanel.panel,
LogPanel
);
UtilitySection.appendChild(UtilityGrid);
return {
appendLog,
refreshRuntimeStatus: RefreshRuntimeStatus,
section: UtilitySection,
setShellStatePayload: StatePanel.setPayload
};
}
function synchronizeCommandCounter(BootState, Counters)
{
Counters.commandReceiveCount = BootState.commandReceiveCount || (Counters.commandReceiveCount + 1);
}
function synchronizeShellStateCounter(BootState, Counters)
{
Counters.shellStateReceiveCount = BootState.shellStateReceiveCount || (Counters.shellStateReceiveCount + 1);
}
function wireBridgeSubscriptions(BootState, Counters, UEBridge, AppendLog, RefreshRuntimeStatus, SetShellStatePayload)
{
UEBridge.onCommand(function onCommand(Payload)
{
synchronizeCommandCounter(BootState, Counters);
RefreshRuntimeStatus();
AppendLog('UE Command', serializePretty(Payload));
});
UEBridge.onShellState(function onShellState(Payload)
{
synchronizeShellStateCounter(BootState, Counters);
SetShellStatePayload(Payload);
RefreshRuntimeStatus();
AppendLog('Browser Shell State', 'Received fresh shell state from Unreal.');
});
}
function createBrowserShell(RootElement, BootState, UEBridge)
{
RootElement.innerHTML = '';
const Counters = {
commandReceiveCount: BootState.commandReceiveCount || 0,
shellStateReceiveCount: BootState.shellStateReceiveCount
|| (BootState.lastShellStateReceivedAtUtc ? 1 : 0)
};
const ShellElement = createElement('div', 'shell');
const HeroElement = createHeroSection(BootState);
const UtilitySection = createUtilitySection(BootState, Counters, UEBridge);
const AdapterSection = createAdapterSection(UEBridge, UtilitySection.appendLog);
ShellElement.append(HeroElement, AdapterSection, UtilitySection.section);
RootElement.appendChild(ShellElement);
UtilitySection.refreshRuntimeStatus();
wireBridgeSubscriptions(
BootState,
Counters,
UEBridge,
UtilitySection.appendLog,
UtilitySection.refreshRuntimeStatus,
UtilitySection.setShellStatePayload
);
}
function createFallbackRuntimeApi(BootState, UEBridge)
{
return {
adapters: BROWSER_SUPPORT_ADAPTERS,
listAdapters()
{
return BROWSER_SUPPORT_ADAPTERS.slice();
},
loadAdapter,
receiveCommand(Payload)
{
UEBridge.receiveCommand(Payload);
},
setShellState(Payload)
{
UEBridge.setShellState(Payload);
},
renderShell(RootElement)
{
createBrowserShell(RootElement, BootState, UEBridge);
},
sendState(Payload)
{
UEBridge.sendState(Payload);
}
};
}
function replayPendingBridgeTraffic(BootState, RuntimeApi)
{
const PendingCommands = Array.isArray(BootState.pendingCommands) ? BootState.pendingCommands.slice() : [];
BootState.queuedCommandCountAtRuntimeReady = PendingCommands.length;
BootState.pendingCommands = [];
PendingCommands.forEach(function replayPendingCommand(Payload)
{
RuntimeApi.receiveCommand(Payload);
});
const PendingShellStates = Array.isArray(BootState.pendingShellStates) ? BootState.pendingShellStates.slice() : [];
BootState.queuedShellStateCountAtRuntimeReady = PendingShellStates.length;
BootState.pendingShellStates = [];
PendingShellStates.forEach(function replayPendingShellState(Payload)
{
RuntimeApi.setShellState(Payload);
});
}
function notifyFallbackRuntimeReady(BootState, UEBridge)
{
UEBridge.notifyRuntimeReady({
status: 'ready',
runtime: 'hypertwist-browser-runtime-fallback',
mode: 'fallback-js',
fallbackReason: BootState.fallbackReason || 'bundle unavailable',
adapterCount: BootState.adapterCount,
shellAuthority: BootState.shellAuthority,
queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady,
queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady
});
}
function mountFallbackShell(RuntimeApi)
{
const RootElement = document.getElementById('app');
if (RootElement instanceof HTMLElement)
{
RuntimeApi.renderShell(RootElement);
}
}
function bootstrapHyperTwistBrowserFallbackRuntime()
{
const BootState = resolveFallbackBootState();
BootState.runtimeMode = 'fallback-js';
BootState.runtimeReadyAtUtc = new Date().toISOString();
BootState.runtimeStatusId = 'runtime-ready';
BootState.adapterCount = BROWSER_SUPPORT_ADAPTERS.length;
BootState.shellAuthority = 'Content/Browser/index.html';
const UEBridge = createFallbackBridge(BootState);
const RuntimeApi = createFallbackRuntimeApi(BootState, UEBridge);
window.HyperTwistBrowserRuntime = RuntimeApi;
mountFallbackShell(RuntimeApi);
replayPendingBridgeTraffic(BootState, RuntimeApi);
notifyFallbackRuntimeReady(BootState, UEBridge);
}
bootstrapHyperTwistBrowserFallbackRuntime();