import * as Cesium from 'cesium'; import { StyleManager } from './ui.js'; import { flyToAustin } from './camera.js'; import { DataLayerManager } from './data/manager.js'; import flightsLayer from './data/flights.js'; import militaryFlightsLayer from './data/militaryFlights.js'; import earthquakesLayer from './data/earthquakes.js'; import satellitesLayer from './data/satellites.js'; import rocketLaunchesLayer from './data/rocketLaunches.js'; import trafficLayer from './data/traffic.js'; import cctvLayer from './data/cctv.js'; import radioLayer from './data/radio.js'; import bikeshareLayer from './data/bikeshare.js'; import aisLiveVesselsLayer from './data/aisLiveVessels.js'; import militaryInstallationsLayer from './data/militaryInstallations.js'; import militaryAwarenessLayer from './data/militaryAwareness.js'; import communityPresenceLayer from './data/communityPresence.js'; import localDataLayers from './data/localLayers.js'; import { LAYER_STATE_REGISTRY } from './data/layerState.js'; import { registerDataCredits } from './data/dataCredits.js'; import { SceneDirector } from './scenes/director.js'; import { initGevVoiceCommands } from './voice/gevRealtime.js'; import { MapStackController } from './mapStackController.js'; import { initAnnotations } from './annotations/index.js'; import { initLogoGaze } from './logoGaze.js'; import { initCockpitCloudEffects } from './cockpitCloudEffects.js'; import { installRenderGovernor, getRenderGovernorDiagnostics, governorRequestRender, holdContinuousRender, releaseContinuousRender, } from './renderGovernor.js'; import { installScopeMask } from './scopeMask.js'; import { initFirstRunExperience } from './firstRunExperience.js'; import { initKeySetup } from './keySetup.js'; import { loadPhotorealisticTileset } from './mapStartup.js'; initLogoGaze(); function installEmbedExit() { if (window.parent === window) return; const button = document.createElement('button'); button.type = 'button'; button.textContent = '← ARCADE'; button.setAttribute('aria-label', 'Leave the globe'); Object.assign(button.style, { position: 'fixed', top: '12px', left: '12px', zIndex: '10000', border: '1px solid rgba(255,255,255,.3)', borderRadius: '999px', background: 'rgba(0,0,0,.7)', color: '#fff', padding: '8px 12px', font: '600 11px system-ui', letterSpacing: '.08em', cursor: 'pointer', }); button.addEventListener('click', () => { window.parent.postMessage({ source: 'gods-eye-view', type: 'exit' }, '*'); }); document.body.appendChild(button); } installEmbedExit(); /** * Extract a human-readable error message from any thrown value. * Handles Error objects, strings, and plain objects with message/error fields. * @param {*} error — caught exception value * @returns {string} best-effort error description */ function describeError(error) { if (!error) return 'Unknown initialization error'; if (error instanceof Error) { if (error.message && error.message.trim()) return error.message.trim(); return error.name || 'Initialization error'; } if (typeof error === 'string' && error.trim()) return error.trim(); if (typeof error === 'object') { const maybeMessage = String(error.message || error.error || '').trim(); if (maybeMessage) return maybeMessage; try { const serialized = JSON.stringify(error); if (serialized && serialized !== '{}') return serialized; } catch { // ignore serialization error } } return String(error); } /** * GOD'S EYE VIEW — Main Entry Point * Initializes CesiumJS with Google Photorealistic 3D Tiles, * style system, intelligence HUD, location presets, and share links. */ async function init() { const loadingScreen = document.getElementById('loading-screen'); const loaderStatus = loadingScreen.querySelector('.loader-status'); try { loaderStatus.textContent = 'Configuring viewer...'; // A direct Google key provides Google 3D plus GEV place search. Cesium ion // can host the same 3D tiles and also powers Bing/world-terrain stacks. const cesiumToken = import.meta.env.CESIUM_ION_TOKEN; const googleApiKey = import.meta.env.GOOGLE_MAPS_API_KEY; if (googleApiKey) window.__GOOGLE_MAPS_API_KEY__ = googleApiKey; // Cesium's default star-field skybox is loaded through HTML image // elements. In a sandboxed iframe without `allow-same-origin`, even files // served by this app have an opaque origin; uploading those images to // WebGL throws a SecurityError and stops the entire render loop. Keep the // full skybox for the standalone app, while embedded hosts fall back to // the texture-free sky atmosphere below. const embedded = window.self !== window.top; // Create the Cesium viewer with minimal chrome const viewer = new Cesium.Viewer('cesiumContainer', { timeline: false, animation: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false, vrButton: false, selectionIndicator: false, infoBox: false, baseLayer: false, skyBox: embedded ? false : undefined, // Visible attribution container — Google Maps / 3D Tiles credits are // required by Google's Terms of Service, so they must be shown (styled // subtly via #cesium-credits). The credit line stays visible in // clean-view AND recording modes too (ToS requires attribution while the // content is displayed — those are the exact modes used to record // demos), including the "Data attribution" link that opens the per-layer // license popover. creditContainer: (() => { const el = document.createElement('div'); el.id = 'cesium-credits'; document.body.appendChild(el); return el; })(), msaaSamples: 4, contextOptions: { webgl: { preserveDrawingBuffer: true, }, }, }); // Cap the default render loop at 60 fps. Cesium's loop otherwise runs at // the display's refresh rate — 120 Hz on ProMotion panels — doubling GPU // and CPU burn for zero visual benefit in a map app whose animation // cadences (poll interpolation, trail fades, style crossfades) are all // designed against wall-clock time, not frame count. Measured on the // 2026-08-05 perf investigation as a strict halving of idle burn on // 120 Hz hardware; a no-op on 60 Hz displays. (perf item 2) viewer.targetFrameRate = 60; // Register per-layer data attribution into the "Data attribution" popover. // Required by each source's license (ODbL, CC BY-NC-SA, NASA FIRMS, etc.); // strings are verbatim from DATA_SOURCES.md. Static + always-present in the // expandable bottom-left credit lightbox (showOnScreen=false), so they never // clutter the on-globe line. See docs/pre-ship-audit-2026-07-01.md H11. registerDataCredits(viewer); // Hide Cesium's default globe — Google Photorealistic 3D Tiles provide their own // globe at all LODs (street level → orbital). The default globe's 2D imagery // clips through 3D tile buildings at close range. viewer.scene.globe.show = false; // Keep a sky behind Google 3D Tiles, but soften Cesium's high-intensity // default atmosphere. With the globe hidden its bright limb otherwise // reads as a hard cyan seam where distant photoreal tiles meet the sky. viewer.scene.skyAtmosphere.show = true; viewer.scene.skyAtmosphere.atmosphereLightIntensity = 18; viewer.scene.skyAtmosphere.saturationShift = -0.12; viewer.scene.skyAtmosphere.brightnessShift = -0.08; loaderStatus.textContent = googleApiKey || cesiumToken ? 'Loading Google 3D Tiles...' : 'Loading the keyless globe...'; const photoreal = await loadPhotorealisticTileset(Cesium, { googleApiKey, cesiumToken, }); const tileset = photoreal.tileset; if (tileset) { viewer.scene.primitives.add(tileset); // NOTE: Cesium World Terrain intentionally disabled — conflicts with Google 3D Tiles at high zoom. // Google Photorealistic 3D Tiles provide their own terrain/elevation. viewer.scene.globe.show = false; console.info(`[Init] Google 3D Tiles loaded via ${photoreal.route}.`); } else { if (photoreal.errors.length) { const tileError = photoreal.errors.at(-1); console.warn('[Init] Google 3D Tiles unavailable, using the keyless globe:', tileError); const tileErrorDetail = describeError(tileError); loaderStatus.textContent = `Google 3D Tiles unavailable (${tileErrorDetail}). Loading the keyless globe...`; } viewer.scene.globe.show = true; } loaderStatus.textContent = 'Initializing systems...'; const mapStackController = new MapStackController(viewer, { googleTileset: tileset, cesiumToken, initialStack: tileset ? 'photoreal' : 'esri-imagery', // Task 5 (height-datum fix): rebroadcast stack changes as a window // CustomEvent so data layers (CCTV per-regime ground resolution) can // react without coupling MapStackController to layer modules. Fires on // 'switching'/'ready'/'error'; listeners derive the surface regime from // live scene state, so intermediate emissions are harmless. onChange: (state) => { window.dispatchEvent(new CustomEvent('gev:map-stack-changed', { detail: state })); }, onError: (message) => console.warn('[MapStack]', message), }); await mapStackController.setStack(tileset ? 'photoreal' : 'esri-imagery', { silent: true }); // Initialize the style manager (post-processing, HUD, locations, share links) const styleManager = new StyleManager(viewer, { mapStackController }); // The previous multi-canvas weather compositor remains disabled. Cockpit // clouds use a separate, capped low-resolution GPU pass that never attaches // Cesium fog or post-process stages and is fully stopped in map mode. const weatherEffects = null; const cockpitCloudEffects = initCockpitCloudEffects(viewer); // If no share link state, do default fly-to Austin if (!styleManager.hasShareState) { loaderStatus.textContent = 'Flying to Austin, TX...'; flyToAustin(viewer); } else { loaderStatus.textContent = 'Restoring shared view...'; } // Initialize data layer manager const dataManager = new DataLayerManager(viewer, { allowQaRegistration: import.meta.env.DEV, }); dataManager.register(flightsLayer); dataManager.register(militaryFlightsLayer); dataManager.register(earthquakesLayer); dataManager.register(satellitesLayer); dataManager.register(rocketLaunchesLayer); rocketLaunchesLayer.attachDataManager(dataManager); dataManager.register(trafficLayer); dataManager.register(cctvLayer); dataManager.register(radioLayer); dataManager.register(bikeshareLayer); dataManager.register(aisLiveVesselsLayer); dataManager.register(militaryInstallationsLayer); dataManager.register(militaryAwarenessLayer); dataManager.register(communityPresenceLayer); militaryAwarenessLayer.attachDataManager(dataManager); for (const layer of localDataLayers) { dataManager.register(layer); } // Restoration starts only after the complete production registry is sealed. dataManager.finalizeRegistrations(LAYER_STATE_REGISTRY); if (import.meta.env.DEV) { window.__gevQaRegisterLayer = (targetManager, layerModule) => { if (targetManager !== dataManager) throw new Error('QA layer manager mismatch'); return dataManager.registerForQa(layerModule); }; window.__gevQaUnregisterLayer = (targetManager, layerId) => { if (targetManager !== dataManager) throw new Error('QA layer manager mismatch'); return dataManager.unregisterForQa(layerId); }; } dataManager.buildTogglePanel(document.getElementById('data-toggles')); styleManager.attachDataManager(dataManager); // Initialize deterministic scene playback for social clip capture const sceneDirector = new SceneDirector(viewer, styleManager, dataManager); // Initialize the voice "whiteboard" annotation engine (world-space renderer) const annotations = initAnnotations({ viewer, tileset }); // Keep startup chrome truthful: a share is not restored until camera, // visual/map/panel lanes, and every requested layer have terminated. void Promise.all([ styleManager.initialRestorePromise, new Promise((resolve) => setTimeout(resolve, 1000)), ]).finally(() => { loadingScreen.classList.add('hidden'); // Reveal only after the loading cover has yielded. transitionend can be // absent under reduced motion, so a bounded fallback makes this reliable. let firstRunRevealed = false; const revealFirstRun = () => { if (firstRunRevealed) return; firstRunRevealed = true; // dataManager is passed explicitly: the globe missions enable bundled // keyless layers through it, and reaching for styleManager._dataManager // would make a private field part of this feature's contract. initFirstRunExperience({ styleManager, dataManager }); }; loadingScreen.addEventListener('transitionend', revealFirstRun, { once: true }); setTimeout(revealFirstRun, 900); }); // Provider Settings (the POWER UP chip + dialog). Fire-and-forget: the // module removes its own surface when the dev-server endpoint is absent // (prod builds, non-local visitors), so this costs prod exactly nothing. void initKeySetup(); // Expose for debugging // Idle render governor: flips the scene into requestRenderMode whenever // nothing animates per frame. Installed AFTER every module above has had // its chance to register pre-install holds. (perf wave 2) installRenderGovernor(viewer); // The explicit scope mask replaces the emergent six-pass artifact — // see src/scopeMask.js. Installed before the UI so the DISPLAY-rail // toggle finds it live. installScopeMask(viewer); // The follow camera recomputes the tracked target's dead-reckon position // every frame — tracking anything is a per-frame animation. (perf wave 2) viewer.trackedEntityChanged.addEventListener(() => { if (viewer.trackedEntity) holdContinuousRender('tracked-entity'); else releaseContinuousRender('tracked-entity'); }); // Hidden-state suspension (perf wave 2): when the window/tab is hidden, // stop the default render loop outright — a hidden canvas repaints for // nobody, and browser rAF throttling still lets throttled frames burn // GPU. Holder/data state is untouched, so return is seamless: restore // the loop, refresh the one DOM surface we gated, render a frame. const syncVisibilitySuspension = () => { const hidden = document.hidden; viewer.useDefaultRenderLoop = !hidden; cockpitCloudEffects?.setSuspended?.(hidden); if (!hidden) { if (dataManager._panelRefreshPendingOnVisible) { dataManager._panelRefreshPendingOnVisible = false; dataManager._refreshTogglePanel(); } governorRequestRender('visibility-restore'); } }; document.addEventListener('visibilitychange', syncVisibilitySuspension); // Apply the CURRENT state too — bootstrap can complete while the tab is // already hidden, and waiting for the next transition would leave the // loop burning behind a hidden tab. (perf wave 2 fix) syncVisibilitySuspension(); window.__godsEyeView = { viewer, styleManager, tileset, dataManager, sceneDirector, mapStackController, annotations, weatherEffects, cockpitCloudEffects, getRenderGovernorDiagnostics, requestRender: governorRequestRender, }; window.__godsEyeView.voiceCommands = initGevVoiceCommands({ viewer, styleManager, dataManager, sceneDirector, annotations }); } catch (error) { console.error("God's Eye View initialization failed:", error); loaderStatus.textContent = `Error: ${describeError(error)}`; loaderStatus.style.color = '#ff4444'; } } init();