# Application construction `gods-eye-view/application` exports `createApplication`. Importing it does not create a viewer, discover configuration, start requests, or attach browser listeners. Construction is also inactive until the caller invokes `start()`. The caller supplies four constructors, in this order: | Constructor | Receives | Standalone implementation | | ---------------- | ---------------------------------------------- | --------------------------------------------- | | `createScene` | `signal`, `defer` | Viewer, attribution and initial map | | `createControls` | `scene`, `signal`, `defer` | Style manager and camera presentation | | `createData` | `scene`, `controls`, `signal`, `defer` | Layer catalog, registration and restoration | | `createTools` | `scene`, `controls`, `data`, `signal`, `defer` | Scenes, annotations, voice and page listeners | Each constructor returns its component object, or a promise for that object. Those objects are passed unchanged to later constructors. Configuration and service instances belong in the caller's closures; the application does not interpret provider names, environment variables, endpoints, or module paths. There is no module discovery or automatic import mechanism. ```js import { createApplication } from 'gods-eye-view/application'; import { createApplicationViewer } from 'gods-eye-view/application/viewer'; const app = createApplication({ createScene({ defer }) { const viewer = createApplicationViewer({ container, creditContainer }); defer(() => viewer.destroy()); return { viewer }; }, createControls, createData, createTools, }); const unsubscribe = app.subscribe(({ status, phase }) => { // Update the caller's startup presentation. }); await app.start(); // When the application is no longer needed: unsubscribe(); await app.destroy(); ``` The example's containers and three remaining constructors are supplied by the consumer. The viewer helper preserves the standalone viewer's render settings; it neither selects map sources nor reads keys. Supply a visible credit container and retain all attribution required by the chosen sources. Cesium is external to this export: the consuming build must provide the same instance used elsewhere in its application. ## Lifecycle and ownership Register `defer(cleanup)` immediately after acquiring each resource, before any `await`. Cleanup registration remains open until that constructor settles. Each callback may return a promise. Within a phase, callbacks run in reverse order. Across phases, teardown runs **tools, controls, data, scene**: controls must cancel restoration while their data manager and viewer are still alive. `start()` returns the same promise on repeated calls. `destroy()` is terminal and also returns the same promise on repeated calls. It aborts the shared signal immediately, waits for an in-flight constructor to settle, and runs all registered cleanup callbacks. Constructors must forward the signal to cancellable operations and check it after awaits. If a provider cannot cancel construction, register cleanup for its late result before returning. Destruction waits for that result; it does not pretend a pending resource has already been released. A constructor failure triggers the same cleanup before rejecting startup. Cleanup failures do not stop the remaining callbacks; they are reported through an `AggregateError`. Constructors own cleanup for resources created internally, including anything allocated before their own construction throws. `getState()` returns a frozen `{ status, phase }` snapshot. Status is `created`, `starting`, `ready`, `destroying`, `destroyed`, or `failed`. `phase` identifies the constructor during startup. `subscribe(listener)` immediately reports the current snapshot and returns an unsubscribe function. Install observers before `start()` to receive every startup phase. Observer failures cannot interrupt startup or teardown. `getComponents()` returns a frozen, shallow snapshot of the currently constructed component objects; the component instances themselves remain mutable. `ready` means all constructors have returned. Background feed activity and the standalone share-restoration result keep their existing separate contracts. ## Pointer input `gods-eye-view/input` exports `inputFor(viewer)`, the scene's one input dispatcher. Every layer and tool takes its pointer input from it, and so can the caller: one `ScreenSpaceEventHandler` per scene, and a click picked once and handed to every subscriber. ```js import { inputFor } from 'gods-eye-view/input'; const input = inputFor(viewer); const off = input.onClick((event) => { if (event.ownerId === 'flights') showFlight(event.id); }); // Later: off(); ``` `onClick` runs while no tool holds the pointer (the draw tool, the imagery box), so a vertex placed on an aircraft is not also a click on it. Its event reads lazily; a subscriber that reads nothing costs no pick: | Member | What it is | | ------------------ | -------------------------------------------------------------------- | | `screenPosition` | Where the user clicked, in CSS pixels; for DOM and overlay hit-tests | | `position` | Where that lands in the pick buffer (corrected under sensor styles) | | `gesture` | `{ travelPx, durationMs }` of the press, to tell a click from a drag | | `pick` | `scene.pick` at `position` | | `hit` | `pick`, or on a coarse pointer the contact under a surface pick | | `id`, `ownerId` | The hit's pick id and the layer that registered it, or `null` | | `meta` | The hit's own metadata, when its pick carries any | | `surface` | Whether the hit is the map itself | | `drill(limit)` | `scene.drillPick`, shared by every subscriber of the click | | `pickWindow(w, h)` | `scene.pick` over a `w`×`h` window around `position` | | `ground()` | The world position under the click, or `null` over the sky | `onPointer(type, fn, { lease })` subscribes to `down`, `move`, `up`, `click` or `dblclick`; with a lease it runs only while that lease is current. A tool takes its lease with `claimPointer(ownerId)` from the same export (and gives it back with `releasePointer(lease)`), which also keeps every ambient subscriber off its clicks; `isPointerFree()` and `pointerOwner()` report who holds it. `suspendCamera(reason)` and `suppressStockClicks()` return their own release: camera input and the viewer's stock select and double-click-track come back when the last hold is released, so two holders cannot undo each other. The dispatcher owns the camera controller's `enableInputs`: a direct write (Cesium's camera flights make one when they start and end) is kept, but reads true only while no hold is out. A caller-supplied `createScene` that creates its own viewer registers `defer(() => inputFor(viewer).destroy())` right after it; the standalone scene does this and returns the dispatcher as `scene.input`. ## Height queries `gods-eye-view/queries/heights` exports `heightsFor(scene)`, the one place height queries go. Layers, tools and the camera ask it instead of calling `scene.sampleHeight`, `globe.getHeight`, `scene.clampToHeight` or the `*MostDetailed` variants themselves, so a renderer that answers heights another way plugs in without touching them. ```js import { heightsFor } from 'gods-eye-view/queries/heights'; const heights = heightsFor(viewer.scene); const ground = heights.now({ lon, lat }, { exclude: [ownMarkers] }); if (ground !== null) place(ground); ``` | Member | What it answers | | ----------------------------- | ------------------------------------------------------------------------- | | `now(point, opts)` | Height in metres from what is loaded, or `null` | | `clampNow(position, opts)` | The `Cartesian3` moved onto the drawn surface, or `null` | | `batch(points, opts)` | A promise of heights once the most detailed tiles stream; `null` per miss | | `clampBatch(positions, opts)` | A promise of clamped copies of the positions; `null` per miss | | `supported()` | `{ surface, clamp }`: whether the drawn surface can be queried at all | A point is `{ lon, lat }` in degrees or a `Cesium.Cartographic` in radians. `against: 'surface'` (the default) reads what is drawn, 3D tiles and globe; `against: 'terrain'` reads the loaded terrain tiles alone. `exclude` lists the primitives and entities a query sees through. Every miss is `null`, including a Cesium throw for missing depth-texture support, and every caller treats it as one. Cesium answers `now()` synchronously from the scene, so it costs what the direct call did. ## Cesium internals Core reaches private Cesium fields only through `src/render/cesium/internals.js`, one named helper per field (the main and offscreen pick views, a billboard's collection, a cluster's point collection, the widget's render-loop latch, the WebGL context and whether a camera flight is under way). `internals.test.mjs` fails when the installed Cesium stops declaring one of them, and when a private field is reached anywhere else. Imagery that rations its tile requests states the policy as a hook with `withTileRequestHook(provider, { requestImage, credit })` from `src/render/cesium/tileRequests.js`, which leaves the provider untouched. ## Standalone wiring `src/main.js` reads the existing browser configuration and starts `src/standalone/application.js`. That module selects the four implementations in its directory. Scene setup, controls, layer registration, tools and loading chrome have separate owners. The existing `window.__godsEyeView` debugging shape is preserved while the app is running. The standalone controls and layer modules still contain page-scoped state. Only one standalone application may be constructed per page. Shutdown releases its runtime resources and debugging handles; reload the page to start again. It is not an embeddable, removable HTML shell or a multiple-viewer implementation. Consumers of the small application export supply their own component ownership; the lifecycle controller itself has no shared instance state. Later component extractions can replace a constructor's internals without changing the startup contract or moving standalone imports into the package. Run the unit suite, package boundary gate, build, tracking and first-run browser checks when changing this wiring. ## Geospatial services `src/search` owns forward/reverse geocoding, place search and route provider interfaces. `createStandaloneApplication({ geospatial })` accepts `endpoints` and `providers`. Default forward search tries decimal coordinates and bundled place names (`presets`) first, without a network request, then Google when a key is configured, Photon, and the local `/api/geocode` Nominatim route. The defaults also keep Google place context and the local OSRM route proxy. Location, annotations, HUD labels and voice use the composed service. Provider-specific credentials belong in the selected transport; server secrets never belong in browser configuration. For a compatible protocol, configure `geocode`, `photon`, `nominatim`, `reverse`, `textSearch`, `nearby` or `route` endpoints. These are developer-selected configuration, not URLs accepted from page queries or model arguments. A different protocol supplies an adapter function instead. The exported `createDefaultPlaceSearch` constructor supports the same options for direct composition: ```js const services = createDefaultPlaceSearch({ resolveApiKey, signal, endpoints: { photon: 'https://search.example/api/', route: '/api/routes' }, providers: { route: routeProvider, routeProfiles: ['foot', 'car'], }, }); ``` Forward `providers.geocode` is the existing ordered geocoder array. Other operations are independent functions: `reverseGeocode(latitude, longitude, { signal })`, `textSearch(query, point, { signal })`, `nearby(point, { signal })` and `route(coordinates, profile, { signal })`. Points use `latitude`, `longitude` and `radiusM`; route coordinates use `[longitude, latitude]` in WGS84. Routes return `{ geometry, distanceM, durationS }` with metres and seconds. Unavailable routes return null, retaining the explicitly labelled direct-line fallback. Place search returns normalized place arrays; reverse lookup returns address, locality, region, country and label fields used by scene context. Unsupported operations are reported in `capabilities`; adapters can provide attribution and supported route profiles. A provider's lifetime and caller cancellation both invalidate late response bodies. Node middleware is configured separately. `googlePlacesContextProxy` accepts `endpoints: { nearby, textSearch }`, `resolveApiKey` and `fetchImpl`. `overpassProxy({ routing: { endpoints: { foot, car, bike }, fetchImpl } })` selects compatible OSRM base URLs while preserving coordinate/span limits. `regionalBriefProxy({ placeProvider })` accepts the regional lookup function; `createRegionalPlaceProvider({ endpoint, requestJson })` supplies the existing serialized Nominatim implementation. Configuration is per instance, including routing and regional caches. Request parameters cannot override these endpoints. ### Voice connections and controls Voice controls accept an action runner and a controller factory through `createVoiceCommands` (`./voice/commands`). The default Realtime controller owns microphone tracks, playback, push-to-talk, tool cancellation and radio handoff. Its backend supplies `requestToken({ tier, authMode, signal })` and `negotiate({ offerSdp, credential, signal })`. The Realtime-compatible adapter (`./voice/realtime-backend`) accepts separate token and connection transports and endpoints. Only the short-lived client secret reaches the SDP endpoint. Stopping or ending the application lifetime aborts connection requests and rejects delayed responses; a new start requests a fresh secret. Secret expiry limits connection creation and does not describe the connected session lifetime. `createStandaloneApplication({ voice })` passes these construction options to the controls. An incompatible protocol needs a separate controller adapter; changing an endpoint alone does not translate protocol messages. No alternate model is bundled by this extraction. The Node Realtime provider accepts `realtime: { endpoint, models: { standard, mini }, resolveApiKey, fetchImpl }`; existing environment variables remain the default configuration. Model choices come from server configuration. Keep secret keys in the server adapter. Tool schemas, model defaults and cost estimates are unchanged. Unknown model IDs retain the existing conservative estimate until their rates are registered. ### Composing the existing application components `application/scene`, `application/controls`, `application/data` and `application/tools` expose the existing globe, controls, catalog and tools as separate constructors. Pass these to `createApplication`; use its `signal` and `defer` for ownership. The compatibility catalog is still page-scoped: construct one application per page. Controls, actions and renderers use the same layer instances. Configure sources with `application/sources` before registration or state restoration, and release them after the consumers stop. The catalog constructor (`application/layers`) also requires `wind`, `weather` and `cyclones` sources with `getSnapshot` for the Weather layers, and owns the observed-history clock those layers share for the catalog's lifetime. `application/services` accepts boundary, terrain, regional-context, weather and summary services; its weather service supplies cockpit local conditions, not the Weather layers. `application/requests` supplies the existing HTTP protocols with configurable endpoints and a scoped transport. Changing an endpoint works only for a compatible protocol; another protocol supplies a service adapter. Cancellation discards late response bodies, and replacing a source invalidates its pending results. Source disposal does not silently reinstate a default. `ui/composition` supplies the default engines to the shell. Applications can supply a HUD implementation or request policy through control services. The shared chrome owns the welcome/loading transition; standalone composition adds Provider Settings. A page places each Vue view in `src/ui/views` with a host, `
`, and writes its own loading screen (`#loading-screen` with a `.loader-status` line) as static markup so it paints before any script runs. `createApplication()` renders the views before anything reads the page; `omitViews` names views the application leaves out, such as `provider-settings`, and their hosts are removed. Leaving out `layer-panels` also leaves out the scene sharing dialogs it draws, and leaving out `scene-chrome` the before/after imagery dividers (the imagery still splits, with no line or handle to move it), the status chips, the toast, the ORBIT indicator, the FPS readout, the Scene actions panel, the data-pack attribution cards, the wind inspection marker, the Space Missions replay vehicle, the full-globe celestial ring, the screen-space map annotations, the visible map target list and the Global Context compass; leaving out `command-dock` the voice control and its pointer reticle; leaving out `context` the Bhote Koshi reconstruction panel; and leaving out `intel-hud` the Intel HUD overlay (`#intel-hud`), whose readouts the HUD implementation publishes to the `gev-hud` store. An unknown view name is an error. A host that does not initialize Core's locale passes `localePicker: false` to `createApplicationControls` instead of editing the markup. Node tests that import application modules register the component loader with `node --import=