The Cesium quality port supports pixel ratio, multisampling, shadows,
3D tile and terrain detail and the target frame rate, each applied when
first set and nothing changed before. Detail scales each tileset's and
the globe's own screen-space error and gives it back at full detail;
while tile detail is lowered, tilesets added later follow it. Frame
signals carry each drawn tick's CPU time, size, loop mode and holds.
The application and conformance renderers have the port, and the
conformance page runs the quality checks against Cesium.
The Cesium camera module now loads the credits factory, so the flight,
military and satellite packages own the credits modules too, and the
infrastructure consumer build lists them.
The renderer bridge builds and keeps an effects port over a viewer no
renderer is bound to, with a factory the Cesium effects module provides,
so visual settings on a consumer's own viewer draw their styles, bloom
and sharpen as before and give the scene's bloom back when destroyed.
The application shell loads the factory; a test checks every export
that reaches the effects lookup loads it.
VisualEffects defines each preset on the renderer and keeps its own
records of intensities and parameters, written through to the port, so
crossfades, the anime sky fade, bloom and sharpen behave as before. The
renderer animates the animated styles. Sensor picks read the style
records instead of the renderer's stages. The application renderer now
has the effects port and its capabilities.
The effects port puts each style Core defines on its own post-process
stage, takes the scene's bloom stage and adds a sharpen stage, with the
mappings the application uses today. An animated style holds frames and
advances its time uniform while it draws. A replaced style keeps drawing
until its intensity is 0, so Core can crossfade; the contract, the stub
and a new conformance check say so. The conformance renderer has the
port; the application renderer does not yet.
The street-level provider credits, the map source credit, the shared
inline OpenStreetMap and OpenMapTiles credits and the ALPR provider credit
go through the renderer's credits port instead of Cesium's credit display.
Their markup has one link each and is read as the port's plain data; the
ALPR credit is built as plain data, which the port escapes. src/maps/credits.js
and src/layers/streetLevel/credits.js no longer reach Cesium and leave the
allow-list.
findCredits finds the port as findCamera finds the camera: a Cesium viewer
of a consumer's own, with no renderer bound, gets a credits port over its
own credit display, so the public entries keep their attribution. A port
built that way is released, not disposed, when a renderer binds later, and
a credit let go after its viewer is destroyed leaves the display alone.
The per-layer data credits and the transit and event credits stay on
Cesium's display: their markup carries several links each, which the
contract's credit cannot hold.
The shared Cesium ports gain credits, on both the conformance renderer and
the application's. list() reports what Cesium owes as plain data: its logo,
its static credits and the last drawn frame's imagery, terrain and tile
credits, read from the credit display through internals.js, with the
credits Core adds listed as given where the display shows them. addStatic
shows a credit through Cesium's display, as markup built from the plain
data with every value escaped and only web links kept, so attribution stays
on screen. change fires when Core adds or removes a credit, and after a
drawn frame whose Cesium credits differ from the last one's.
src/render/creditMarkup.js reads markup credits as plain data. The credits
listener check moves out of renderer.listenerRules into its own check, so a
renderer without every port can run it; it and the credits check now run on
Cesium.
setFov holds the vertical angle it is given across resizes (Cesium
keeps the wider one), waits for a frustum with a field of view, and
rejects angles outside (0, pi). matrices returns Cesium's view and
projection matrices, ECEF to eye in 3D, the only mode Core runs. The
conformance run checks both on Cesium.
Flights and military follow their tracked aircraft with an
entityFollowTarget: framed in ENU from viewFrom, never closer than
150 m, presentation read from the drawn entity's record. Satellites
follow in Cesium's autodetect frame. The layers' own framing
(applyTrackedCameraFrame) goes; the port's follow has the same
close-range rules, and trackedCamera.js no longer imports Cesium.
The port's helper entity draws nothing: its point's outline is
transparent too, and click and voice picks of it count as picks of
the target's drawn entity. The fake camera binder follows when the
fake has Cesium's entity tracking.
The tracked card, screen annotations, north-up, share links and Cockpit
ask data/followed.js what the camera follows, so they work the same when
a layer follows through the camera port. followed.js gains
entityFollowTarget (a FollowTarget whose presentation reads the drawn
entity's record), resumeFollowing and followedPosition.
src/data/followed.js answers what the camera follows: the camera port's
target, or the entity a layer still tracks itself, seen through the same
fields. Ownership checks, render holds, guards, voice actions, the CCTV
pacing and the navigation release paths read it instead of Cesium's
trackedEntity, so they keep working when layers follow through the port.
The release paths stop following, cancel the flight and release the
camera through the port. The rotate refusal names the followed target by
its name or label title.
The Cesium camera port follows a target with Cesium's own entity
tracking, on a hidden helper entity per target, and gains followed,
followFramed, followOffset and followChanged. A target with a minimum
range gets GEV's close-range rules, moved here from trackedCamera.js:
an exact first framing, no zoom coasting, a zoom floor, and an eye that
never crosses through the target. followChanged now fires only when
what is followed changes, on the stub too. No layer follows through the
port yet.
The Cesium camera port gains move, look, rotateAround and zoom, and the
browser conformance run now checks them, the lookAt-frame commands, the
changed thresholds and moveEnd (timed by the clock, not a frame count).
Orbit, the Bhote Koshi focus and cinematic, the launch zoom anchor and
replay chase, the camera verbs' motion tick and roll levelling, and
voice zoom write through the port.
The camera-port commits ran prettier over four test files that were
never prettier-formatted. Restore their earlier text and keep only the
camera-port edits.
The scene director's shot flights, start pose and stops, the annotation
framings and fly-to, the voice overhead framing and the local GeoJSON
click flight now drive the renderer's camera port. The director's stops
look the camera up with findCamera: teardown can run after the viewer is
gone, and then there is no flight to stop. The local GeoJSON flight,
part of the public infrastructure export, works on a plain Cesium viewer
through the unbound camera, and looks it up before taking its input
hold. Voice zoom stays for the move slice.
The embed view, the celestial-ring focus, the ground-guard lift, the
Bhote Koshi approach and orbit, launch selection and replay flights, and
the cockpit's view and takeover now drive the renderer's camera port.
The contract gains a sineInOut easing, pinned by a conformance trace.
A viewer with no renderer bound, such as one a consumer of a public
export brings, now gets a camera port too, as it already got an input
port: cameraOf builds one over the viewer's own Cesium camera and keeps
it for the viewer's life, until a renderer is bound. The Cesium adapter
provides the factory as it loads; code that reaches Cesium takes cameraOf
from the adapter so the factory comes with it, and findCamera answers
null where a missing camera is not an error (HUD, share link, world
focus, Recent Imagery's ZOOM IN). A test walks every package export and
fails one that can reach the bridge without loading the factory.
A destroyed viewer gets no camera and lets go of a built one, and the
application renderer stays bound until its viewer is destroyed.
flyToBounds takes an easing, as flyTo does: in the contract, the Cesium
port and the reference recorder, pinned by a new conformance trace.
The flights of radio, awareness, CCTV, submarine cables, cyclones, the
launch preview, installations, ALPR, street-level framing, recent
imagery, weather and world focus now go through the renderer's camera
port. Street-level framing cancels its own flight handle rather than
whatever flight is current. worldFocus.js no longer reaches Cesium and
leaves the allow-list.
Layer pose reads stay for each layer's own slice. The local GeoJSON
flight stays on Cesium: gods-eye-view/infrastructure is used with plain
Cesium viewers that have no renderer bound.
The Cesium camera port moves into render/cesium/camera.js, with the
listener list and position conversion shared from common.js. It now
covers flights (flyTo, flyToBounds, cancelFlight, flying), lookAt and
release, command hooks, and a flightEnd event for the flights it starts.
A flight's end that Cesium raises inside one of the port's own calls is
delivered once Cesium's call returns, so its callback sees the flight
let go of. A flight Cesium declines to start, mid scene-mode morph,
ends as cancelled instead of never settling.
Flight traces run in Node on the recorder's headless camera with a
virtual clock; the browser harness keeps the instant traces.
The startup fly-in, landmark and viewport flights, share-link restore
and the shell's camera state now go through the port. camera.js and
sharelink.js leave the Cesium allow-list.
The first camera-family slice. The camera port's events move into a
shared module (render/camera/events.js) that the stub and the Cesium
port both use: listener lists, delivery with errors reported, and the
contract's per-listener `changed` measure.
The Cesium port gains:
- camera.on: `changed` measured per listener on every tick, where Cesium
measures its single threshold (the counts match Cesium's own at each
threshold); moveStart and moveEnd relayed from Cesium's camera;
- camera.viewRectangle, in degrees (now in CESIUM_CHECKS);
- lon and lat in pose, and NaN where Cesium has no cartographic position.
Code that still holds the viewer reaches the renderer through
rendererOf(viewer); the application renderer binds it on create and
unbinds on destroy.
The HUD reads its telemetry from the camera port and no longer imports
Cesium (off the allow-list). The share link writes the hash from the
pose. Its restore path stays on the viewer's camera for a later slice.
Behaviour changes:
- The share link listens to `changed` at its own 0.5 threshold, not at
whatever global threshold layers had set, and also to moveEnd, so the
hash always catches up with where the camera comes to rest.
- The HUD's and share link's camera callbacks run from the port's single
relay, ahead of other moveStart/moveEnd listeners, and a throw in one
is reported to the renderer's error listeners instead of escaping from
Cesium's render.
The Cesium renderer now creates the input port and exposes it as
renderer.input. The input dispatcher becomes an adapter over the port:
one port listener per event type, fanned out to its subscribers with the
ownership check read once per event, keeping the legacy event shapes.
- Ctrl+wheel zoom is switched on through renderer.input.ctrlWheelZoom.
- A dispatcher made for a viewer with no port creates its own port with
takeStock: false, which borrows the stock select and double-click
follow only while they are suppressed.
- The stock actions see the press as it began: a click whose press
started while select or follow was held off never runs them.
- A scene accepts one input port; cesiumInputOf(scene) returns it.
- Releasing one mouse button while another is held no longer gives the
held button a click. Cesium still ends that press.
src/render/cesium/input.js feeds the shared router from one
ScreenSpaceEventHandler: every button's press, release and click, double
clicks, moves and pinches, under any one modifier. The DOM event being
dispatched gives the full modifiers and pointer type; an action Cesium
raises on a timer takes its own modifier. Wheel comes from the canvas in
lines and is never cancelled. Clicks are picked once, lazily, through the
query port. Cesium ends every held button on any release; those presses
end there, but only the button released clicks.
The viewer's stock select and follow move off its handler and run first,
as they did, but only for a press no lease took and while not suppressed.
Camera holds go through the scene's shared holds, ctrlWheelZoom through
ctrlWheelZoom.js. The router gains a before-hook and dispose, which stops
delivery mid-event.
The conformance renderer exposes the port and the harness feeds it;
input.lease, input.lease.grab, input.lease.press, input.clickPick and
input.behaviours join the Cesium checks.
installTrackpadPinchZoom and its pinch scaling move unchanged from
src/app/viewer.js to src/render/cesium/ctrlWheelZoom.js, where the input
port's ctrlWheelZoom will turn it on and off. The application scene
imports it from there; the application/viewer export no longer carries
this undocumented helper.
The take-over of the camera controller's enableInputs moves, unchanged,
to src/render/cesium/cameraInput.js. acquireCameraInput(scene) gives every
user one set of counted holds per scene, so the renderer's input and
camera ports can hold camera input alongside the dispatcher; the
controller gets its own property back when the last user releases. The
dispatcher acquires its share once its handler is bound. The input guard
names the one module allowed to write the flag, and the new render files
join the format scope.
Frame-phase and draw-set ready listeners now run in subscription order,
skip one removed during delivery, defer one added during it, and hand a
throw to the renderer's error listeners (or reportError) instead of into
Cesium's render loop. The renderer gains on('error' | 'contextLost' |
'contextRestored'); Cesium cannot restore a lost context, so it never sends
contextRestored. Dispose clears every list. renderer.listeners joins the
Cesium checks.
The stub's lease and press routing moves to src/render/input/router.js,
unchanged, so a real renderer can feed it its own engine's events. The
router also keeps the behaviour holds (telling the renderer when one turns
off or on, unless it lacks it) and ctrlWheelZoom, and dispatch says who an
event went to.
The governor no longer writes the Cesium scene's render mode itself: each
owner holds the renderer's frame port once, requests go to it, and the
port is the one place the loop's mode is set. The port now also keeps
the loop from redrawing for scene time alone, which the governor did.
The application installs the governor as soon as its renderer exists and
holds the loop continuous ('boot') until its tools are up, where the
governor used to be installed, so boot draws as before. The hidden-tab
suspension goes through the port too, and the application's renderer now
has the whole frame port.
The frame port now does everything the contract asks of it on Cesium:
preUpdate says whether the tick draws (Cesium's own render test, read
live until the tick is over), holds are counted per reason, and suspend,
target fps, renderNow and diagnostics are real. Releasing the last hold
asks for one more frame.
whenSettled also waits until Cesium stops redrawing on its own. Each
request or worker task Cesium completes queues a redraw (a terrain
picker build after the camera moved, say), which no loading flag shows,
so it now waits for quiet frames and then a short stretch in which the
loop draws nothing by itself. It is bounded by its timeout, names a
stopped loop, and ends on destroy.
The app renderer still leaves out the members that steer the loop:
the render governor and the page's visibility handling own those
settings until they move onto the port.
The new frame.prediction check compares the preUpdate answer with what
each tick drew.
The Cesium renderer now has every QueryPort member: picks and drill
picks, depth and globe picks, rays, projection, occlusion, heights now
and streamed, clamping and ray casts. Capabilities come from what the
scene supports (depth textures for pickPosition, sampleHeight,
finestHeights and rayCast), in both the conformance and app renderers.
- Picks skip unpickable and hidden items, and report each item once. A
pick under an unpickable item stops at anything that hides the rest.
drillPick's limit counts after that filtering, so 0 gives none.
- Height queries and ray casts see through sets not drawn with
includeInHeightQueries, and through the sets a caller excludes, by
their items' pick ids. The list is built once per change to the sets.
- `tiles` samples and casts run with the globe hidden for the call;
`terrain` casts use the globe alone. A sample reports whether it came
from tiles, terrain, the ellipsoid or a set.
- A position clampToSurface cannot read is a miss, not a failed batch.
- Terrain on an ellipsoid is height 0. Before, sampling it threw, for
want of tile availability.
whenSettled also waits for icon images, and fails when one cannot load.
The conformance checks that pick or query settle first, because a
renderer answers from what it has loaded; the stub settles at once.
The Cesium renderer now passes 14 checks in the browser, and its
visual references are unchanged.
The conformance page can now run the logic checks against the Cesium
scene renderer as well as the stub. CESIUM_CHECKS lists the checks of
the ports Cesium already has; each phase 2 migration adds its own, so
the suite shows how far the renderer is through the contract. All eight
listed checks pass in the browser, and the stub still passes its 69.
A harness frame is one tick of the render loop, observed through
frame.on('preUpdate'). It asks for no frame itself, so a check's own
requestRender is what makes a tick draw, and a loop that stops fails
the check after five seconds instead of hanging the run. The harness
frees each WebGL context on destroy.
FrameTime.dt is now measured rather than fixed at 1/60, and renderOnce
honours timeoutMs. A scene renderer made without the models capability
has no draw.models.
The frame, draw, camera, query and capture ports move out of the
conformance scene renderer into src/render/cesium/ports.js, unchanged.
The app's transitional renderer, now src/render/cesium/appRenderer.js,
builds the same ports over the full viewer; it still leaves out globe,
whenSettled and setTime, which would misreport photoreal tiles and stop
the app clock.
Disposing the ports now settles a pending renderOnce and detaches the
draw sets, so a model that loads after destroy is destroyed rather than
added to a destroyed collection. A viewer whose ports fail to build is
destroyed with its credits.
Checked in a browser: the app boots with one credits element, and all
eleven conformance scenes match their references exactly.
Outside src/render/cesium/, only the 236 files on cesiumAllowList.json
may reach Cesium, and each must still do so, so porting a file removes
its entry. A file reaches Cesium by importing it or the Cesium adapter,
by name or by path, or by being handed the namespace as a parameter,
binding or property. Files added to the list since origin/main fail the
check, once the list is on main. The renderer contract, stub and
conformance suite must stay Cesium-free, and runtime code in a language
the check cannot read fails it.
createApplicationScene takes a renderer plugin, defaulting to Cesium,
which builds today's viewer and credits exactly as before. Its renderer
is transitional: it has only the members it really has, with read-only
capabilities, and a test pins the contract members still to come. Code
not yet ported reaches the viewer through cesiumViewerOf, never through
a renderer member. A plugin whose renderer has no viewer is refused and
destroyed, since the application still runs on the viewer. The result
now includes the renderer.
Not yet checked in a browser.
Voice actions read degrees from ECEF positions with toLonLatH and
measure distances with src/geo, at the sites that only did maths. A
position that converts to NaN now reads as unknown, never as NaN
coordinates. The analyst test gives its subject a real ECEF position
instead of patching Cesium. Sites that keep a Cesium Cartographic, or
use the camera or picks, wait for the renderer ports.
Share links convert degrees, radians and the flight destination with
src/geo. The hash and the destination are bit for bit the same as
before; the flight itself still goes through the Cesium camera.
fromRadians divides by gamma as Cesium does, so geodetic to ECEF is now
bit for bit Cesium's, and the test checks it exactly. Its tests also
cover three height bands, points inside the Earth and NaN input. toEcef
reads the renderer contract's Position, so it moves to the stub's maths.
The ellipsoid docs pair each conversion with Cesium's.
Ports Cesium's axis-angle, product, rotation-matrix and heading-pitch-roll
quaternions, with rotations given as frame axes. They match Cesium bit for
bit, including heading-pitch-roll frames on the polar axis. The model
convention is documented: at heading 0 the forward axis points east.
A port of Cesium's Simon 1994 sun and moon positions, its TEME to
pseudo-fixed rotation and the JulianDate time handling, with epoch
milliseconds as the only input. Results are identical to Cesium's,
tested with exact equality from year -500 to 3000 and across every
leap second. Times outside the model throw a RangeError, where
Cesium's debug build throws.
The world packet converts the eye position with toLonLatH instead of
Cesium's Cartographic, so the agent-world-packet group no longer needs
cesium. Degree conversions use precomputed factors, as Cesium.Math does,
so results stay bit for bit the same.
WGS84, ECEF and geodetic conversions, surface normals and east-north-up
frames, as plain {x, y, z} objects, exported as ./geo. A test checks
them against Cesium over random points and the poles. The stub's maths
re-exports them and keeps only the camera angle conventions.
- setFrames takes a timed source (URL frames, or video tiles in
segments) on the scene clock by default or its own; frameReady fires
once each time the shown frame is drawn.
- drapeOn is 'surface' | 'objects' | 'all' plus lit; imagery.host() and
onHostChange go, and the drape rule Core derives is written out.
- ImagerySpec.fetchTile answers bytes, missing or defer on every
renderer; tileTransform, admit and canvas imagery need imageHooks, and
add() throws for them without it.
- Generic names for the default renderer's world data: worldDataToken,
the photoreal fallback route, and world terrain and imagery.
- clouds and water capabilities, with globe.setClouds and setWater.
- claimPointer takes a grab filter, asked on each down: a lease takes
only the presses it grabs, and wheel, pinch and every other press stay
with the camera and everyone else. Each event of a press goes where its
down went, per button, so a claim never takes a press under way.
- Core owns the styles: effects.defineStyle passes a GLSL (and optionally
WGSL) full-screen pass, with the shader interface written out.
- Credits are plain data ({ text, href, logoUrl, inline }) that Core
renders; CreateOptions.creditContainer goes.
- controls.coast { drag, zoom } in seconds replaces the inertia trio; the
stub's defaults are the default renderer's, converted.
- The frame order is the default renderer's own: a move made by follow or
in preUpdate reaches changed on the next frame.
- lodGroup links items across sets as one thing at different detail;
picks name the member shown and report the group. displayRange is
half-open.
- An orbits capability: the renderer propagates two-line elements with
SGP4 on the set's motion clock. The stub propagates with satellite.js.
- Height queries see draw sets only with includeInHeightQueries (was
excludeFromHeightQueries); HeightSample.source gains 'set'.
- courseGlide is a capability; courseBlendMs eases the course on every
renderer.
- Arc segments climb at climbMps; a negative minDtS stays for the
aircraft warm-up.
- The star catalogue is optional, a hidden column row keeps its motion,
and draw.clampState() reports the drape fallback.
Checks cover the climb and warm-up, lodGroup ranges, orbits against SGP4
(with and without motionSegments), hidden rows resuming, and sets not
answering height queries by default.
The renderer contract is what the application asks of a 3D renderer, so a
renderer other than Cesium can draw the same application. It is nine ports
(frame, globe, draw, camera, query, input, effects, credits, quality) plus
the renderer itself, one strict .d.ts per port in src/render/contract/.
PORTS lists every member at run time, and a TypeScript-compiler test fails
when the list and the types disagree on one member. An optional member
exists only with its capability; a required one answers null or empty
without it. Nothing in the application uses the contract yet.
The stub renderer (src/render/stub/) implements every port in memory and
draws nothing. Its camera is a port of the default camera's maths: views,
lookAt, flights with their height arc and easing, rectangle framing on the
geodesic midpoint, the view rectangle with its horizon and pole cases,
changed thresholds, follows, and flights that finish inside the call.
The conformance suite (src/render/conformance/) runs 59 checks against
any renderer through the contract alone, in Node or in a browser page. The
camera checks compare traces recorded from Cesium's own Camera, headless on
a virtual clock (scripts/record-camera-goldens.mjs), to 1 mm, 1e-9 and
1e-3 px. The other checks cover listener rules on every subscription, the
frame loop, draw sets, picks, motion clocks, heights, leases, imagery,
styles, credits and quality knobs. The stub passes all of them but the
model check, which needs a glTF asset.
Reference scenes draw one scene per primitive family, a city and a
calibration path from orbit to the street, from contract calls only and
with no network. /dev/conformance/ draws them on the Cesium scene renderer
(src/render/cesium/sceneRenderer.js), records references through a
dev-server-only, loopback-only endpoint, and compares captures with
pixelmatch's antialias detection plus a solid-pixel count. Measured on
Cesium: every MSAA setting passes, and a dropped 4 px point, a dropped 2 px
line and a 1 px shift of the view fail. visual.js states the limits.
The package exports ./render/contract, ./render/stub and
./render/conformance. Documented in docs/RENDERER-CONTRACT.md,
docs/CODE-BOUNDARIES.md and docs/CURRENT-STATE.md.
The Directions A and B letters and a data pack's media label were the two
Cesium text labels left, kept by an allow-list in noCesiumLabels.test.mjs.
Both now publish entries to the shared world-overlay host, and the
allow-list is gone, so any Cesium label in src is a test failure.
Directions publishes the letters to the directions-markers source; the
markers keep only their points. A clicked endpoint's letter hangs over the
surface that was clicked. One placed without a click stays hidden until its
ground-floor cell resolves, then requests a render, so a still camera shows
it; a cell that settles with no height falls back to the ellipsoid. Each
position is computed once.
A pack resource handle gains label(entry). The application passes its
shared overlay host to SceneDirector through dataPacks.overlayHost; without
one a media pack fails to load rather than drawing a native label. Each
labelled resource takes a source from a pool and returns it on dispose, as
the host never removes a source, so two presentations of one pack never
clear each other's labels and repeat loads add no sources. A label written
after dispose is ignored. The media label is protected, so it always draws
as before.
Live (Denver, keyless globe): a clicked A and a programmatic B both anchor
on their dots over 1.58 km ground. Documented in docs/CURRENT-STATE.md and
docs/DIRECTOR-DATA-PACKS.md.
The arrival ground guard replaced camera.flyTo, flyToBoundingSphere,
setView, lookAt and cancelFlight with wrappers so a direct caller would stop
it. It now remembers the pose it started on and, before each retry, yields
if a flight is under way or the camera has moved across the ground, down or
around. A straight rise is not a move: Cesium's terrain collision lifts a
buried eye that way as tiles load, which is the case the guard exists for.
This also catches owners the wrappers missed (a pose written directly,
lookAtTransform), and no camera method is replaced.
Recent imagery and weather overwrote provider.requestImage (weather also
provider.credit) to ration tiles. They now state that policy as a hook with
withTileRequestHook(provider, { requestImage, credit }) from
src/render/cesium/tileRequests.js, which hands Cesium a provider delegating
to the original and leaves the original untouched.
Every private Cesium field Core reaches goes through a named helper in
src/render/cesium/internals.js: the main and offscreen pick views and their
command extents, a billboard's collection, a cluster's point collection,
the widget's render-loop latch, the WebGL context and the current camera
flight. internals.test.mjs fails when the installed Cesium stops declaring
one of them, and a source scan fails on private access anywhere else. The
cyber sonar's primitives.update wrap stays until the effects work.
Live (Austin, keyless globe): a buried eye is lifted to 253 m over a 133 m
surface; a setView or flyTo by another caller makes the guard yield; radar
loads its tiles through the hook. Documented in docs/APPLICATION.md and
docs/CURRENT-STATE.md.
About 20 sites called scene.sampleHeight, globe.getHeight,
scene.clampToHeight or the MostDetailed variants directly, each with its own
idea of a miss (undefined, NaN, a throw). They now ask heightsFor(scene) in
src/queries/heights.js:
- now(point, { against, exclude }) answers from what is loaded, or null;
against is 'surface' (what is drawn) or 'terrain' (loaded terrain only).
- clampNow(position) moves a position onto the drawn surface, or null.
- batch() and clampBatch() wait for the most detailed tiles: null per
missed point, a rejection when the whole query fails.
- supported() reports { surface, clamp } without allocating.
Cesium still answers now() synchronously, so nothing changes on screen and
nothing is slower: in the live app 1,000 calls cost the same through the
service as direct (terrain 0.5-0.7 ms, surface 2-6 ms a call, run-to-run
noise larger than the difference). Every site already treated a miss as
"hold the last value or fall back"; a renderer that can only answer from a
cache or asynchronously can now plug in at one place.
- A Bhote Koshi evidence point whose terrain sample misses stays at its
fallback elevation instead of reading the miss as sea level.
- The mesh floor sampler skips a scene without depth-texture support
instead of spending its budget on probes that throw.
- The world annotation probe's trace reads hit/missed.
- A source scan forbids direct height calls outside the service.
The service is exported as ./queries/heights and documented in
docs/APPLICATION.md.
Every layer and tool bound its own ScreenSpaceEventHandler and picked the
same click again: with the usual layers on, one click ran about 26
scene.pick and 2 drillPick passes (120-215 ms in NYC with 18 layers). Pointer
input now comes in through src/input/dispatcher.js, one handler per scene.
A click is picked lazily and at most once per kind of query, and every
subscriber reads the same ClickEvent (pick, hit, id, ownerId, gesture,
drill, ground). The same click now costs 3 picks and 2 drills, 17-70 ms.
- Ambient onClick subscribers run only while no tool holds the pointer,
checked once per event; tools subscribe with their lease. Moves and
releases always reach ambient subscribers, so hover and drags end cleanly.
- Camera input and the viewer's stock select/track clicks are ref-counted
holds (suspendCamera, suppressStockClicks). The dispatcher owns the
controller's enableInputs, so a Cesium flight that ends no longer turns
input back on under a drag or the cockpit.
- The draw tool, imagery box, CCTV gizmo, cockpit, directions and every
selecting layer use it; bindTrackingClickGesture is gone (the gesture is
on the event). A source scan forbids new handlers, enableInputs writes and
stock-click removal outside the dispatcher.
- The sensor-style pick correction transit had now applies to every layer.
- Radio's 8-pick near-miss ring is one 17x17 windowed drill.
- A missed pick passed to pickContact is no longer picked again, and a
touch tap's contact search shares the click's drill.
- A gizmo drag whose release Cesium never reported ends on the next press
instead of leaving the camera held.
The dispatcher is exported as ./input with the pointer-lease functions, and
documented in docs/APPLICATION.md.
Aircraft draw from GPU icon sets, so the billboard mirror, the billboard
treatment and the aircraft visual-state module had no callers left. 3D models
are now records the layer holds under the contact's id (modelRecord.js):
show and placement are written through showModel/placeModel and read back
from the record; renderer-measured values stay on record.model.
The tracked-model click check only matches when a tracked model exists, so
an id-only pick is never mistaken for a click on the tracked plane.