Move the world overlay into scene.services.overlay. Each attachment owns its
sources, collision domains, paint lanes, canvases, observers, timers and
accessible action subscribers. Keep both canvases inside the engine container,
with detection beside the card root so its screen blend still reaches the
scene. Release shared font measurements only after the last host detaches.
Resolve retained layer publications against their attached engine, clear the
outgoing source before releasing its renderer, and reconnect the shell's action
subscription on attachment. Keep destroyed hosts inert and give parked engines
fresh services. Bind annotation rectangles and the existing tracked bridge to
their engine's overlay. The tracked bridge itself remains page-owned.
Preserve Cesium's projection, card placement, arbitration, fades, hit tests,
blend order and render demand. Navara keeps the same shared behaviour. No
user-visible change is intended. Exercise simultaneous hosts, either teardown,
stale handles, reattachment and a retained selected card on the fake and
headless Cesium engines. Update QA reads and package ownership for the service.
No coupling metric falls or grows: renderer lines remain 0 Cesium and 189
Navara; external Cesium import files 3; viewer lines 891; Navara kind checks 1;
engine sniff calls 0; extension reads 3; ground passes 1; SDK loads 3;
ScreenSpaceEventHandler files 4; engine-name lines 32.
Each host now takes the page's overlay ids from the host it replaces and gives
them back to a remaining host when destroyed, and the chrome's static overlay
placeholders are gone. The new hosts had left the ids on those empty
placeholders, so the voice pointer policy, the id selectors and the QA probes
that read #world-overlay-canvas missed the drawn canvas.
Checked in the browser, keyless: the overlay baselines on both engines, CCTV
v2 (58 checks), military names, and the engine, voice, ground-layer and Ghost
Maps switches pass. The transit script fails the same 25 checks as before.
The event's before and after images went to each engine's display as URLs,
so every engine switch fetched and decoded both again: the strict Nepal event
scenario saw three reads of each image where Cesium's kept providers made one.
The event now decodes the pair once with its own loader and hands the images
to each display, which takes a URL or a decoded image. The pair is dropped
with the rest of the event state on disable, and a failed load is retried on
the next attachment.
The wind engine-switch scenario waited for a diagnostics field the wind layer
lost when it moved onto engine.imagery; it now waits for imageryActive.
Checked in the browser, keyless: the Nepal event, wind and submarine-cable
engine switches pass.
Give each scene its own render governor, owner holds and recent idle requests.
Make the Cesium render backend own its request mode directly, so the governor
drives engine.render on both engines without a dependency back from Cesium.
Route layer requests through their attached engine and capture the attachment's
governor for camera motions, orbit, annotations and shell controls. Release
retained-layer holds before clearing the outgoing engine, and destroy the
governor after scene consumers have detached.
Preserve Cesium's owner-keyed holds, pre-install buffering, last-release settling
frame and one-shot requests during continuous rendering. Navara keeps the same
render policy. There is no intended visible change on either engine. Simultaneous
scenes no longer share holds, and an old captured service cannot release a
replacement attachment's hold. Cover both rules on fake and headless Cesium
engines, including an old orbit ending after reattachment.
Viewer-reference lines fall from 910 to 891, Navara-kind checks from 2 to 1,
and engine-name lines from 33 to 32. All other coupling counts stay unchanged.
Checked in the browser, keyless: qa-perf passes on both engines with the
same idle, active and release counts as before (24 checks on Cesium, 22 on
Navara).
Create pick registries and selection dispatchers for each scene attachment,
publish them through scene.services, and release their predicates and input
subscriptions before parking the renderer. Resolve layer ownership with the
attached engine so identical layer IDs in simultaneous scenes cannot overwrite
or remove one another. Re-register retained ALPR and cyclone picks on attachment,
and resolve voice pointer picks against the engine that supplied the pick.
Preserve Cesium's selection, sibling ownership, inert trails, touch expansion,
drag and long-press rules on both engines. Extract the unchanged gesture
predicates so portable picking does not acquire a native input dependency.
Exercise scene isolation, teardown and reattachment on fake and headless Cesium
engines, retaining the existing layer assertions.
No coupling count falls or grows. Cesium and Navara renderer lines remain 0 and
189; external Cesium imports 3; viewer lines 910; Navara kind checks 2; engine
sniff calls 0; extension reads 3; ground passes 1; SDK loads 3; screen-handler
files 4; engine-name lines 33.
Restrict runtime SDK imports to their engine directories, including package
subpaths, re-exports and dynamic imports. Keep three measured, reasoned
exceptions for the Bhote Koshi property-record models and reject stale
exceptions. Move the Cesium annotation renderers and both wind adapters to
their engine folders, preserving their implementations and lazy loading.
Load all 35 manifest display entries on a fake engine with an unknown kind
and no native extensions. Draw small fixtures and verify resource and
subscription cleanup before destroying the engine. Name the three entries
without display factories as skips. Update the architecture, infrastructure
lifecycle and engine integration documentation to describe the code today.
Cesium behavior is unchanged, and Navara keeps its existing behavior. The
weather comparison harness supplies the Navara flow-mesh factory and accepts
the previous backend location for comparison runs. No user-visible strings
change.
Cesium layer-renderer lines fall from 14 to 0, Navara layer-renderer lines
from 314 to 189, outside-engine Cesium import files from 5 to 3, viewer lines
from 933 to 910, extension reads from 4 to 3, and engine-name lines from 36
to 33. No coupling count grows. Targeted checks pass: 845 tests, three named
rehearsal skips. Import directions, package boundaries, formatting and syntax
checks pass.
Checked in the browser, keyless: the contract suite passes on Navara and on
Cesium apart from the camera-events check that also fails intermittently
before; Navara annotations pass across engine switches; the ground-look
comparison is pixel-identical. qa-draw-tool's hidden-line check and the wind
engine switch fail the same way before this change.
Move the page handoff sequence into SceneSwitcher and include restoration
in the application's candidate attachment. Release a failed candidate once
before the request coordinator recovers the working engine. Keep address
ordering, parked renderers, retained layers and cockpit resumption intact.
Define the layer session and its field owners. Share its retention decision
across teardown paths. Separate layer scene binding from lifecycle policy,
and shell scene composition from page controls. Keep failed renderer
releases in the binding owner, with the existing retry behavior.
Give Director the scene and use the engine host for its canvas and pack
container. Keep the existing tracked-subject interaction guard so neither
engine's behavior changes. Pass startup chrome the first backend's explicit
welcome policy instead of testing for a viewer. Cesium remains the behavior
reference; this restructuring intentionally changes no visible behavior on
either engine.
Viewer references outside the Cesium engine fall from 948 to 933. All other
coupling counts stay unchanged. Scene service ownership, raw debug access,
and the remaining shell and host queues still need consolidation.
Checked in the browser, keyless, on the dev server: the engine switch,
Director engine switch, layer panel and layer visibility on both engines
pass, and 14 of 15 strict scenarios pass (rapid, hidden-tab, context-loss,
parked-release, Navara HD, failure matrix and soak among them). app-once
and the Director next-shot step fail the same way before this change.
Render the Bhote Koshi event and locator through one ground and point display.
Keep Cesium's reveal paths, dashed halo, glowing core, polygon classification,
pick identities and independent point outline opacity. Add static pixel dash
masks, synchronous ground reveals after terrain bounds load, and per-point
outline colour so the shared display preserves those behaviours.
Add a ground pulse field with checked state on all three engines. Keep Cesium's
raised material disc, band equations, front widths, wake and rest timing and
reduced-motion behaviour. Navara now follows Cesium's raised pulse surface and
story styling, with ground effects composed after tone mapping. Move the wind
mesh shader into the Navara engine without changing its transparent pass.
Layer renderer lines fall from 210 to 14 for Cesium and from 794 to 314 for
Navara. Viewer lines fall from 953 to 948, extension reads from 6 to 4, and
engine-name lines from 37 to 36. No coupling count grows.
Checked in the browser, keyless: the contract suite, with its new pulse
pixel checks, passes on Navara and on Cesium apart from the camera-events
check that also fails intermittently before; the Nepal locator and Contacts
engine switches pass. The Nepal event and wind engine switches fail the same
way before this change. Side by side at the locator's overview, Cesium draws
the same pixels as before and Navara's river corridor now glows as Cesium's
does, where it was faint.
Share links read the current follow handle and displayed position through
engine.camera and restore their ENU offset with follow's swingTo option.
Cesium retains its first-frame wait, displayed-position cache, 60-degree
frame limit and range interpolation. Navara and the fake engine apply the
same swing. Cancellation, camera handoff and destruction release it; an
externally tracked Cesium entity keeps its existing handle.
Draw uses one surface with engine input, geodesic lines and point movers.
An exclusive pointer claim suppresses ambient subscriptions and deferred
picks across ownership changes. Cesium owns suspension and exact restoration
of its stock select and track actions. Camera gestures remain enabled.
Detaching releases the sketch and preview and retains shape and colour.
The separate Navara surface and Cesium data-source preview are removed.
Cesium remains the reference for clicked heights, line width and dash,
vertex size and outline, keyboard handling and completed annotations.
One deliberate visual fix is that the occluded rubber band uses the selected
colour instead of retaining the initial amber: it now identifies the same
sketch as its visible segment. Navara adopts Cesium's geodesic preview edges.
Finished annotation drapes are unchanged.
External Cesium import files fall from 7 to 5, viewer lines from 1005 to
953, and engine-name lines from 39 to 37. No coupling metric grows.
The full unit suite, import directions, package boundaries, formatting
and coupling checks pass.
Checked in the browser, keyless: the contract suite passes on Navara and on
Cesium apart from the camera-events check that also fails intermittently
before; qa-navara-draw passes; qa-draw-tool passes apart from its hidden-line
pixel check, which fails the same way before this change. Its destroy check
now expects no preview data source, since the preview has none.
Cesium treats the first imagery layer shown on the globe as its base
layer and stretches that layer's edge texels over the whole globe. With
no base map under it, a KMZ ground overlay turned the whole globe its
edge colour. The Cesium imagery adapter now keeps a floor under an
engine layer that covers less than the globe (a bounded rectangle, or
Web Mercator tiles, which stop at ±85.05°) whenever that layer would
otherwise be the base layer. The floor is one transparent pixel over the
globe at alpha 0, so Cesium draws nothing of it. It is placed when the
adapter adds or changes a layer, and again at each scene preUpdate,
before Cesium picks the base layer. When base imagery lies under the
layer, which is the app's normal state, there is no floor. A bounded
layer now draws only inside its rectangle, as the contract says.
When the globe is not the drawn surface, the import display now clamps
ground pins to the drawn scene, as Cesium's import did whenever the
globe was hidden. Before, it did this only for the map's own 3D tiles.
Each height query now leaves out the point sets of every imported file
on the engine, not only the file's own sets: Cesium's import kept all
imported pins out of height queries, so a higher pin from another file
never became the ground. The display also gives a footprint with its
points at the heights they are drawn at, and flying to a file frames
pins on raised tiles as Cesium did. Cesium's import clamped visible pins
with clampToHeightMostDetailed, which loads the finest tiles. The
display samples the tiles drawn now and samples them again after the
camera stops and while tiles load.
The KML reader's parity test against KmlDataSource now includes the
large-file QA's style case: a shared style with point, line and polygon
parts, inline overrides of each part, and named styles and an inline
StyleMap pair that are alike. The reader matches Cesium there.
QA scripts:
- qa-geo-import moves the camera over the files before it reads which
pins show. Ground pins are hidden only beyond the horizon.
- qa-geo-import-large compares clamped pins by longitude and latitude.
Cesium keeps a clamped billboard at its file height, while the import
draws its pins on the sampled ground.
- qa-geo-import-regressions gives its 3D-tiles fixture a non-zero
tileset geometricError, because Cesium never draws a tileset whose own
error is 0. Its XYZ check now waits for the import to sample the ground
again once the globe's tiles have loaded.
Known differences that remain: a KMZ ground overlay is now imagery, so
ground fills draw over it where the old ground rectangle drew over them;
right after an import on ellipsoid terrain, pins can sit on the coarse
globe mesh until the ground is sampled again.
The full unit suite and the boundary, format and coupling checks pass.
In the browser, keyless: qa-geo-import, qa-geo-import-large and
qa-navara-geo-import pass; qa-geo-import-regressions fails only its
portrait-flight check, which also fails before the refactor.
Add indexed world triangles to the required globe contract on Cesium,
Navara and the fake engine. Own geometry, textures, visibility, depth tests,
pick identities and teardown inside each engine. Reused canvas images
request another frame until their new pixels upload. Move obstruction rays
and owned mesh, line and mover exclusions into the Cesium surface adapter.
Draw CCTV monitor planes and viewshed volumes through shared mesh handles.
Draw data-pack points, straight lines, raised polygons, image rectangles
and media captions through movers, lines and meshes. Delete the feature
renderers for each engine and retain decoded assets across scene rebinds.
Register the modules, dependency, format scope and reduced coupling baseline.
Cesium is the reference for geometry, colours, image placement, marker sizes,
caption offsets, picking and resource ownership. Navara now uses straight
pack lines and per-position polygon heights without edge densification.
Navara meshes draw in the transparent pass after the effects, with the
scene-depth test and the view's exposure undone, as the pack meshes did:
in the scene pass the landscape look's exposure washed a red pack image to
white. The scene-depth helper moves into the Navara engine, and the stems
helper that only its test still used is deleted. The mesh picker takes the
globe when Navara's depth read answers with the camera itself, which it
does right after another click and which hid a polygon from a quick second
click. The 256 px media caption is not pickable: its transparent square took
clicks meant for nearby pack features, and no action targets it. Annotation drapes remain on their existing
renderers because bounded building classification, animated fills and wire
cages need a separate contract extension to preserve Cesium behaviour.
Coupling falls from 344 to 210 Cesium layer-renderer lines, 1294 to 794 Navara
layer-renderer lines, 9 to 7 external Cesium import files, 1026 to 1005 viewer
lines, 9 to 6 extension reads and 42 to 39 engine-name lines. Other counts do
not grow. The full unit suite, import directions, package boundaries,
formatting and coupling checks pass. No user-visible strings change.
In the browser, keyless: the contract suite passes on Navara and on Cesium
apart from the camera-events check that also fails intermittently before
this change; CCTV v2 (58 checks), the CCTV and director engine switches pass,
with the pack image at the same pixel as before on both engines. The mesh
contract fixture now sits at 3 km, above the terrain Navara draws there. The
director interactions script starts at its view in the link so the app's
arrival flight no longer lands after the shot, and waits for the click's
asynchronous pick; its last step, the next-shot action, fails the same way
before this change.
Port the layer probes in twenty-six browser scripts to engine primitive
lists, drawn rows and layer diagnostics. Preserve Cesium as the reference
and keep the supported Navara paths on the same observations. This
changes QA observation only, with no application behaviour change.
Review the interrupted infrastructure group and retain its readiness,
drape, selection, label and floor checks. Match both installation outline
buffers. Frame each CCTV city before reading its visible coverage. Read
Radio colours and selected identities from actual mover writes. Retain
transit's GPU upload budgets using engine-owned line primitives rather
than substituting rebuild counts. Keep native inspection where a check
specifically measures GPU uploads, loaded meshes or ground readiness.
The preceding combined B/C commit owns all shared manifests, docs,
locale catalogs and the coupling baseline; this commit changes only its
own twenty-six QA scripts. Coupling remains unchanged at 344 Cesium
renderer lines, 1294 Navara renderer lines, 9 Cesium import files, 1026
viewer lines, 2 Navara kind checks, 0 engine sniff calls, 9 extension
reads, 1 ground pass, 3 SDK loads, 4 screen-handler files and 42 engine-name
lines.
The full unit suite and the boundary, format and coupling checks pass.
Browser validation remains to be run.
Browser checks to run
GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4291
node scripts/qa-alpr-journey.mjs http://localhost:4291
node scripts/qa-alpr-journey.mjs 'http://localhost:4291/?engine=navara'
node scripts/qa-cables-overlay.mjs --url http://localhost:4291
node scripts/qa-cctv-v2.mjs --url http://localhost:4291 --fixture --terrain-globe
node scripts/qa-context-arrivals.mjs http://localhost:4291
node scripts/qa-directions.mjs --url http://localhost:4291
QA_BASE_URL=http://localhost:4291 node scripts/qa-draw-tool.mjs
QA_BASE_URL=http://localhost:4291 node scripts/qa-floor-hold.mjs
node scripts/qa-ghostmaps.mjs --url http://localhost:4291
node scripts/qa-gnss.mjs --url http://localhost:4291
node scripts/qa-height-datum.mjs --url http://localhost:4291
node scripts/qa-infra-lod.mjs --url http://localhost:4291
node scripts/qa-installation-navigation.mjs http://localhost:4291
node scripts/qa-military-names.mjs http://localhost:4291 --no-video
node scripts/qa-overpass-offload.mjs --url http://localhost:4291
node scripts/qa-radio.mjs --url http://localhost:4291
node scripts/qa-traffic-motion.mjs http://localhost:4291 --mode=osm
QA_BASE_URL=http://localhost:4291 node scripts/qa-transit-recovery.mjs
QA_BASE_URL=http://localhost:4291 node scripts/qa-transit.mjs
node scripts/qa-weather-teardown.mjs --url http://localhost:4291 --headless
for engine in cesium navara; do
node scripts/qa-enrich-ambient.mjs --url http://localhost:4291 --engine=$engine
QA_BASE_URL=http://localhost:4291 node scripts/qa-floor-verify.mjs --engine=$engine
node scripts/qa-heading-b3.mjs --url http://localhost:4291 --engine=$engine
node scripts/qa-overlay-baseline.mjs --url http://localhost:4291 --engine=$engine
node scripts/qa-sprites-b5.mjs --url http://localhost:4291 --engine=$engine
node scripts/qa-vessel-datum.mjs --url 'http://localhost:4291/?qaFeed=vessels:fixture' --engine $engine
done
The military-names driver exercises installation-polish; the transit
driver exercises transit-scenes. Use only the keyless server. Cases that
require photoreal tiles or unavailable providers remain unverified.
Move Cesium viewer, map, sky, projection, trail and sonar internals into
its engine. Drive styles, bloom, sharpening and contact sonar through
engine.render. Count camera-input suspensions across all three engines
and migrate the feature callers. Preserve Cesium's effects and input
behaviour; fix transit lens clicks on Cesium and make zero sharpening on
Navara disable sharpening, as it does on Cesium.
Parse imported files into plain records and draw them through movers,
lines, ground and imagery on both engines. Follow Cesium's KML style
precedence, preserve local KMZ images and frame the drawable records.
Open overlays use imagery when the engine has no native map-overlay API.
Run every imported-display case on fake and headless Cesium engines.
The shared import path retains these known differences from Cesium's old
DataSources: wall fills, extruded prisms and gx:LatLonQuad are undrawn;
raised polygon fills drape while outlines retain altitude; GroundOverlay
rotation, altitude and RGB tint are not applied; rhumb lines become great
circles. Ground heights use the loaded surface synchronously instead of
waiting for most-detailed sampling. Supported KMZ ground overlays now
draw on Navara too. Document the limits rather than claiming full parity.
Combine batches B and C because their disjoint path manifests cannot
place the shared boundary and format files in a later commit while
keeping B independently valid. This combined commit owns the shared
docs, package boundaries, format scope, locale catalogs and coupling
baseline. Remove five obsolete import strings from all eleven catalogs.
Make the load-time source scan include new files and skip deleted paths
so the same checks work before and after recording the moves.
Coupling: Navara renderer lines fall from 1695 to 1294, Cesium import
files from 25 to 9, viewer lines from 1177 to 1026, SDK loads from 4 to 3
and engine-name lines from 62 to 42. No count grows.
The full unit suite and the boundary, format and coupling checks pass.
Browser validation remains to be run.
Browser checks to run
GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4291
node scripts/qa-contract-suite.mjs http://localhost:4291
node scripts/qa-navara-visual-styles.mjs http://localhost:4291
node scripts/qa-navara-bloom.mjs http://localhost:4291
node scripts/qa-navara-sonar.mjs http://localhost:4291
QA_BASE_URL=http://localhost:4291 node scripts/qa-visual-effects.mjs
QA_BASE_URL=http://localhost:4291 node scripts/qa-zoom-out-guard.mjs
node scripts/qa-sky-model-lighting.mjs --url http://localhost:4291
node scripts/qa-geo-import.mjs http://localhost:4291
node scripts/qa-geo-import-regressions.mjs http://localhost:4291
node scripts/qa-geo-import-large.mjs http://localhost:4291
node scripts/qa-navara-geo-import.mjs http://localhost:4291
Use the keyless server for every check. Photoreal-only cases remain
unverified; do not enable paid tiles to run them.
Require engine.imagery for weather and wind and load the wind flow backend
from the layer manifest. Preserve Cesium's staging, frame retention, shell
ordering, wind fade and streamlines; Navara follows the same display policy.
Remove obsolete rendering modules and their three translated status keys.
Run every weather and wind display case on both the fake engine and the
headless Cesium engine, keeping the existing behavioural assertions.
Coupling: Cesium import files fall from 27 to 25, viewer lines from 1232 to
1177 and engine-name lines from 63 to 62. The explicit Cesium wind flow
backend deliberately raises renderer lines from 330 to 344 and extension
reads from 8 to 9; every other count stays unchanged.
Browser checks to run
GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4291
node scripts/qa-contract-suite.mjs http://localhost:4291
node scripts/qa-weather-compare.mjs shoot http://localhost:4291 --out=qa-shots/weather-after
Compare weather and wind against the previous capture on both engines.
engine.imagery gains what these rasters need, on every engine:
- geographic (EPSG:4326) tile schemes and a transform applied to each
decoded tile (the satellite infrared mask);
- staged source changes, which keep the drawn source until the new one
has loaded (the weather frame swap), and credit-only changes in place;
- raised layers that draw on a shell above terrain and 3D tiles, rising
with the camera, with a detail image inside a rectangle. On Cesium this
is the weather shell primitive, moved into the engine; on Navara the
pixel's ray meets the ellipsoid grown by the height.
Rain radar, satellite clouds, lightning, the wind field and the Bhote
Koshi event and locator each draw through one render/display.js on both
engines; their Navara renderers and Navara's weather surface pass go.
Cesium's pixels are unchanged in every compared scene. On Navara the
Bhote Koshi images are no longer upside down. Two engine-specific parts
stay behind one backend option each: the wind's streamlines and the
Bhote Koshi story entities.
The coupling report now counts defineGroundEffect calls, not its own
declaration. Coupling: Navara renderer lines fall from 2304 to 1695,
Navara ground passes from 3 to 1 (the Global Context pulses), engine
kind checks from 5 to 2. The contract suite passes on both engines
(Cesium 71/71, Navara 74/74).
Every layer display was imported statically from the manifest, so all of
them sat in the Cesium-mode main chunk. They are re-exported from one
module, src/app/layers/displays.js, which the manifest loads with one
import() before the catalog is built, on every engine; rendererFor stays
synchronous after that. One chunk beats per-family chunks here: every
start loads every display, and split chunks compress worse. CCTV's world
click gesture moves out of its display, which the app imported only for
it. Tests that build layers without the application await the loads.
Keyless, gzip-9: the main chunk falls from 2,113,641 to 2,066,560
bytes, below the 2,090,274 of the merge with main this branch carries.
A cold Cesium start loads about 7 KB more JS in total over four requests,
with the same time to a ready page. The budget's baseline moves to the
new size; it had never absorbed main's own growth.
Navara's ground pass cut each corridor into chunks and tested them per
screen cell, so dense corridor sets were slow (10,000 cost about 150 ms a
frame). A corridor set is now cut into pieces along its great circles,
sorted into lon/lat tiles, each with a pyramid of bins listing the
segment runs within its margin; a pixel takes its tile and the finest
bin that covers its reach and tests only those runs. At 10,000 corridors
a frame now takes 11.8 ms still (Cesium 23 ms), at 15,000 17.9 ms. A
pixel corridor's width is measured across the line, so diagonal lines no
longer draw up to 1.4 times wider.
Pixel corridors take three looks, evaluated on the GPU from a time
uniform: Cesium's PolylineGlow falloff (glow), an alpha pulse and the
annotation route's flowing dash. While a shown set pulses or flows the
engine draws continuously, as Cesium did. Traffic's jam lines take the
glow and pulse again and the Directions route its flowing dash and render
hold, so on Cesium both match the drawing before they moved to the
contract pixel for pixel. The contract suite passes on both engines
(Cesium 69/69, Navara 72/72).
src/app/layers/renderers.js now holds LAYER_MANIFEST, one entry per
layer in directory order: a display (the same render/display.js on every
engine, with the contract primitives it needs and, for CCTV and Global
Context, a Navara backend loaded by import()), a layer that draws itself
through the contract, or per-engine renderers for the layers not on a
display yet (weather, wind, Bhote Koshi, sea marks, ski pistes). A layer
is available where the engine has the primitives it needs. The Cesium
renderer table, CESIUM_BUILTIN, the registry and the Navara mode's map of
about 35 loaders go; the application loads a kind's renderers before it
builds the catalog.
The tools attach through one tree over the engine, in Cesium's order.
Each engine kind adds its own tools from one backend that reads its
extension once: the Cesium backend holds the viewer-only tools, the
Navara backend the annotation renderer, draw surface and file import
drawing.
Coupling: engine.extensions reads fall from 12 to 8, engine-name literal
lines from 73 to 66.
No code outside src/globe calls isGlobeEngine or engineFromViewer any
more (14 calls left before this). The features that still sniffed take
the engine from their caller:
- Projection and surface helpers (focus de-emphasis, icon orientation,
ground snap, the mesh floor sampler, ground picks) take the engine; the
engine view's scene carries its projector instead of registering itself
as a viewer.
- Credits go only through engine.credits, so annotations and layers
share one OSM credit state on Cesium too (it could show twice).
- The annotation board and the resolver work on the engine: camera
assists, visibility and picks go through engine.camera,
engine.projector and src/geo, without Cesium.
- The Recent Imagery box tool has one implementation for both engines,
on capture-phase canvas pointer events with the layer's engine.ground
preview; the Navara copy goes. On Cesium the preview outline is solid
now, as on Navara.
- Scene interactions take the engine, and the render governor has an
engine variant beside the Cesium one.
Remaining engine-specific features stay behind explicit seams until the
contract covers them: the draw tool's Cesium surface (stock click actions
cannot be suspended), the annotation drapes (animated materials), share
follow, Cesium post effects and cyber sonar, and data packs (meshes).
Coupling: Cesium-importing files fall from 31 to 27, `viewer` lines
from 1367 to 1234, engine sniffing calls from 14 to 0.
engine.imagery draws raster layers over the globe: Web Mercator tile
templates and single images or canvases stretched over a rectangle, with
alpha, show, a left/right split against the engine's split position and
an order. A layer's source can be replaced in place.
- Cesium puts one ImageryLayer per layer on the globe's imagery stack,
or on the shown 3D tileset's when the globe is hidden, and moves it
when the surface changes; the Recent Imagery request gate moved here.
- Navara draws them in one screen pass after tone mapping that samples up
to eight layers by geodetic position from mipmapped textures and a tile
atlas, under the other drapes and engine.ground.
- The fake engine keeps each layer's state.
The FIRMS far-zoom heat raster and Recent Imagery now draw through it, so
FIRMS' heat seam and both its backends, and Recent Imagery's Cesium and
Navara renderers and tile plan, go. Against HEAD, Cesium's pixels are
identical in all four compared scenes; Navara's orbital heat changes by a
mean of 3.8 from new mipmapping, slightly towards Cesium. The contract
suite passes on both engines (Cesium 68/68, Navara 71/71).
engine.maps.addOverlay stays a Navara-only extension for Sea Marks and
Ski Pistes until imagery has a finer tile path for oblique global views.
- Street Level: coverage sequences are ground corridor sets of up to
120, draped on terrain and tiles as Cesium's ground polylines were;
the marker and view cones are sprite movers turned to their compass
bearing on screen and culled 1 km under WGS84. The layer no longer
loads the Cesium SDK on Navara.
- Traffic: vehicles are two point mover sets (an outline disc under each
fill) scaled and faded with Cesium's NearFarScalar maths; congestion
heat lines are ground corridor sets draped on tiles. Heights, depth,
readiness and the surface key go through the engine, and Navara no
longer loads the Cesium SDK for traffic.
- Directions: the route is one ground corridor set in the annotation
route's dash pattern, maneuvers are point movers and the A/B markers a
disc and a letter sprite; the in-layer Navara branch and the Cesium
point collection go.
Known gaps against Cesium, to close in the contract: the traffic heat
lines lose their glow and pulse and the route its flowing dashes
(no animated ground fills yet), and dense Street Level coverage on
Navara is costly as ground corridors (about 80 to 190 ms a frame at
6,000 to 14,400 sequences) until corridors are binned set-wide.
Coupling: Navara renderer lines fall from 3349 to 2767, Cesium-importing
files from 51 to 32, `viewer` lines from 1591 to 1373, Cesium SDK loads
on Navara from 6 to 4.
- Mapped Installations: one display holds the single copy of the logic
the Cesium and Navara renderers each had (renderable records, painting,
the selection fill, floors, named markers, selection). Discs are point
movers, outlines ground corridors and the selection fill a ground
polygon. Cesium's behaviour wins where they drifted: named areas draw
their outlines (Navara dropped them), discs sit under the moving
contacts, and outlines and the fill pick their site.
- Global Context: the range ring is a halo and a core line on
engine.lines and the awareness logic no longer imports Cesium. The
animated pulse disc, which no primitive draws yet, sits behind one
seam, render/pulseDisc.js, with a Cesium and a Navara backend.
- Datacenters and dams: one display on movers, lines and ground sets
with Cesium's LOD, motion probe, floor sampling and card cohort;
localGeojsonCore.js keeps only the engine-free card and record code
(1381 to 329 lines).
Coupling: layer renderer lines fall from 1011/4832 to 351/3349
(Cesium/Navara), `viewer` lines from 1745 to 1591,
ScreenSpaceEventHandler files from 8 to 6, Navara ground passes from 5
to 4.
Navara lights the map tiles with the real sun and sky, and only the
landscape maps took the night ambient fill, so an OSM street map turned
black at local night where Cesium keeps it lit. Every base map now takes
the night fill; the landscape maps keep their orbital fill as well.
Daylight is unchanged.
This is why the strict acceptance's Navara pixel and first-frame checks
over Austin failed in evening runs: navara-hd, parked-release,
hidden-switch, context-loss, geo-import and scope saw a black frame, and
the soak and failure matrix waited for a first frame that never came.
At night with the fix all nine pass.
Earthquakes, air quality, GNSS Integrity, fire perimeters, Prohibited &
Restricted Airspace, Active Fires, S2 GhostMaps, cyclone advisories and
submarine cables each have one render/display.js on the globe contract;
their render/cesium* and render/navara* files go. Their discs, cells,
polygons and corridors are engine.ground sets, so on Navara they come from
the one ground screen pass with exact colours instead of nine hand-written
classification passes and lit SDK vector layers. Cesium's look stays the
reference:
- Earthquake and air-quality discs have no outline (Cesium drew none on
clamped ellipses); earthquake discs can now be picked on Navara too.
- Perimeters, drone zones and GhostMaps polygons outline their outer
ring only, as Cesium did.
- GNSS cells stay out of the pick pass; cyclone cones, tracks and centres
are culled by a port of Cesium's horizon occluder.
- Submarine cables are one ground corridor set, as Cesium batched them;
landing points, tips and stems are movers and lines.
- FIRMS glows are a sprite set and its cells a ground set. The far-zoom
heat raster, which no primitive draws yet, sits behind one seam,
render/heatField.js, with a Cesium and a Navara backend.
The shared Navara ground helpers that only these layers used go
(navaraGroundCells, the cell and ground geometry, the zone renderers), as
does the ground prototype script.
Coupling: layer renderer lines fall from 1636/6953 to 1011/4832
(Cesium/Navara), Navara ground passes from 7 to 5, Cesium-importing files
from 65 to 53, engine.extensions reads from 26 to 19.
engine.ground draws sets of polygons (with holes), discs, cells and
corridors draped on the surface, with fill and outline colours, order and
picks, written in bulk and committed. Every engine implements it:
- Cesium batches each set into one GroundPrimitive for fills and one
GroundPolylinePrimitive for outlines and pixel corridors, classified
against terrain and 3D tiles; sets that are not pickable stay out of the
pick pass.
- Navara draws them in one screen pass after tone mapping that finds each
pixel's ground from the scene depth and tests the items in its screen
cell; long rings are split into edge bins. Measured at Dubai against
SDK vector layers, the pass matched Cesium's frame time up to 10,000
polygons with a few megabytes instead of 70 to 146, drew exact colours
and outlines, and hid reliably across an engine switch, where vector
layers hidden with update({ show: false }) never hid. Dense corridors
remain its limit (10,000 cost 135 ms a frame).
- The fake engine keeps the same registry and CPU picks.
The contract suite checks it on both engines (Cesium 66/66, Navara 69/69).
Earthquakes and fire perimeters have unwired ground prototypes whose
drawing matches today's Cesium one (0.998 on Cesium, 0.976 on Navara).
docs/GLOBE-PRIMITIVES.md describes the primitives and when to use each.
- Radio: stations, clusters, the selection and its brackets are point
and sprite sets; clusters come from the layer's engine-neutral screen
clusterer with Cesium's EntityCluster rules, and clicks go through the
selection dispatcher. One style module holds the sizes and colours.
- Transit: vehicles are two sprite sets from an up-front atlas, rotated
with Cesium's projected-rotation maths. The selected vehicle's trail is
engine.lines in two lanes and 29 age bands, so trails are no longer
draped over terrain until the contract has ground corridors. The
Cesium contact trail renderer and its re-export go.
- CCTV: markers, wedges, the plane outline and the calibration gizmo
(rings, arrows, handles, drags through engine.input) draw on the
contract. The textured monitor plane and the viewshed volume, which no
primitive expresses yet, sit behind one seam, render/meshes.js, with a
Cesium backend and a Navara backend the Navara mode passes in.
Known gaps against Cesium: transit trails are not draped, and the
surveillance-lens click correction runs only where the engine reports
its style stage (Navara).
Coupling: layer renderer lines fall from 2638/8735 to 1636/6953
(Cesium/Navara), ScreenSpaceEventHandler files from 15 to 10, `viewer`
lines from 2014 to 1901.
qa-wildlife-switch.mjs serves four Salish Sea sightings (three from
iNaturalist, one obscured, and an Acartia orca) through the app's own
iNaturalist and Acartia proxies over faked upstream feeds, then on Cesium,
Navara, Cesium and Navara again checks the badge set and its positions,
picks, the density glow, the privacy cell outline and a clicked card.
Two of the switches are made from the keyboard and must keep the
selection; the last is made with the pointer on the Globe Engine picker,
whose click closes the card like any click outside it.
Cesium follows a display's sprite through a proxy Entity that mirrors the
native's readout fields, and gevRestoring was not one of them. A follow
the flights layer resumed on attach therefore read as new navigation to
the shell's tracked-entity watcher, which made the share restore of the
same attach count itself superseded and clear the selection. That is why
the aircraft was lost after a failed switch's rollback onto Cesium, and
after the retry. The proxy now shows the restoring mark while its follow
starts.
The flights renderer test runs a detach, a failed attach to a candidate,
the rollback and a retry on headless Cesium engines, and checks that the
aircraft stays tracked and no navigation is stamped.
Each layer now has one render/display.js on the globe contract, the same
factory on both engines; its render/cesium* and render/navara* files go.
Cesium's behaviour is the reference, and the logic that was written twice
is written once:
- AIS vessels: atlas chevrons and engine.lines trails. The display
registers the gev-trail pick claim itself and its trailing visibility
pass now also runs on Navara.
- Satellites: point banks per look, orbit rings as lines turned by their
model matrix, and selection and follow through engine.camera.follow.
The emulated _viewer is gone, and the tracked readout republishes card
text on Navara too.
- Space Missions: dots, reticle, trajectories and tactical orbits as
movers and lines. The layer keeps its own records instead of Entities
and DataSources, and camera work goes through engine.camera. Clicks go
through the selection dispatcher, and hover keeps Cesium's throttle. The
pad zone is its rim only: the contract has no surface fill yet.
- ALPR, bikeshare and OSM Places: one display and, for bikeshare, one
style module each; clicks through engine.input, no
ScreenSpaceEventHandler or EntityCluster. ALPR keeps Navara's horizon
cull, and OSM Places points sample the surface where Cesium clamped them.
Coupling: layer renderer lines fall from 4546/11106 to 2638/8735
(Cesium/Navara), `viewer` lines from 2209 to 2014, ScreenSpaceEventHandler
files from 20 to 15.
In the app the world overlay draws on a canvas of its own above the
globe, so letting every canvas take the pointer sent the suite's clicks
there. Only the engine's canvas takes it now. Without makeCanvas the
pick check's sprite atlas was a plain object no real engine draws, so its
sprites could not be picked; the page now hands the suite real 2D
canvases. --only=prefix,... runs a subset of the checks. With both, Cesium
passes 63 of 63 checks and Navara 66 of 66.
The layers that still had one Cesium and one Navara implementation, or
drew on Cesium only, now draw through engine.movers, engine.lines and
engine.camera on both engines, and application code holds the engine
instead of guessing it from a viewer.
- Aircraft family: flights, military and local ADS-B share one display
(src/layers/aircraft/render/display.js). The Cesium and Navara
renderers are gone. Where they differed, the display follows Cesium:
- attach and visibility tracking, live disableDepthTestDistance
changes, the tracked glyph's distance scale, model ids that change,
local ADS-B markers hidden until shown and its UAT ring as a point;
- the trail head's hidden alpha is 0.45, the contract's
depthFailAlpha scaled by the head's 0.9.
The tracked glyph stays over the fleet, as on Navara. The movers
contract reports readiness per model and reads model axes as the
glTF's own on every engine. Cesium's follow takes any native target
through a proxy entity in the ENU frame and mirrors its readout fields.
- Wildlife Sightings draws badges and the density glow as sprite sets
and its privacy cells as line outlines (no fill yet). It takes clicks
from engine.input and its card anchors to the engine's canvas and
camera, so the layer is in both engines' catalogs again and a switch
keeps the session's layer set.
- Cockpit: cockpitHost takes the engine only. The ridden aircraft is the
engine follow's target, hidden through its sprite; only the Cesium
Viewer's stock double-click is still routed around.
- About forty camera, navigation, share-link, HUD, voice, detection and
layer-lifecycle callers take the engine. isGlobeEngine and
engineFromViewer calls outside src/globe fall from 51 to 14.
The coupling baseline drops accordingly: layer renderer lines from
5239/12331 to 4546/11106 (Cesium/Navara), `viewer` lines from 2424 to
2209, Cesium-importing files from 69 to 66.
What only one engine has now travels with the engine instead of a
WeakMap in navaraMode.js: engine.extensions.navara holds Navara's view,
its SDK classes and the daylight reset, engine.extensions.cesium the
Cesium viewer. The contract documents the field as outside the contract,
and the coupling report counts its reads outside src/globe in place of
the navaraViews reads (27, unchanged).
Layers are to draw through the globe contract on both engines, so Cesium
gets back the movers and lines removed as unused:
- engine.movers draws sprite sets as a BillboardCollection (atlas cells
cropped once per set), point sets as a PointPrimitiveCollection and
model sets as one Model per instance. depthTest, disableWithinM, order,
scaleByDistance, colorBlend, unlit and minimumPixelSize follow the
contract; a model's matrix is written in place, so instances no longer
share one scratch matrix.
- engine.lines draws each line as a polyline Primitive with width in CSS
px, geodesic arcs, dashes, the tactical and arrow materials,
depthFailAlpha and modelMatrix; a rebuild keeps its show, matrix,
colour and place in order.
- The picker returns the pickId a layer wrote last, skips sets that are
not pickable and instances behind the horizon, and falls through to
what lies under them. Surface samples can exclude mover sets again.
movers and lines move from the optional capabilities into the required
contract shape, and the contract suite always runs their checks.
The app sets contact sonar on every frame (src/cyberSonarEngine.js), so a
value the contract suite sets never survives to its next frame inside the
app. qa-contract-suite.mjs reports such checks as skipped, naming their
owner, instead of failing them.
scripts/report-engine-coupling.mjs counts the engine-specific code left
outside src/globe/cesium and src/globe/navara: layer renderer lines per
engine, Cesium imports, `viewer` mentions, engine-name checks, raw Navara
reads, Navara ground passes, Cesium SDK loads and ScreenSpaceEventHandler
users. engineCoupling.test.mjs fails npm test when a count grows past
scripts/engine-coupling-baseline.json, so each step towards drawing
through the globe contract can only lower them.
scripts/qa-contract-suite.mjs runs src/globe/testing/contractSuite.mjs in
headless Chrome on the Cesium and Navara of a keyless dev server, which
until now only ran on the fake engine. The page imports the suite from the
dev server, starts at a fixed view, waits for the camera to rest and lets
the pointer through the panels. Cesium passes 57 of 57 checks, Navara 64
of 67; docs/CURRENT-STATE.md names the three that fail.
Main's camera, voice, Cockpit and layer work now runs on the globe
contract where this branch draws through it:
- The voice tilt to an angle, its pivot about the place under the
crosshair and the frame limit from orbit run through the camera rig;
src/globeInView.js measures a camera pose with src/geo instead of a
Cesium camera. The free-look height limit and the globe-in-view
recovery stay Cesium-only for now.
- Cockpit takes main's own-layer entry rule. Double-clicking the
followed track goes through cockpitHost.routeDoubleClick, so it works
on both engines.
- Space Missions' failed-entry rollback and GhostMaps' age filter run
in the engine-neutral layer code. On Cesium an expired report is
dropped in place; on Navara its product is redrawn.
- Wildlife Sightings joins the catalog Cesium-only: Navara leaves it
out, and the shell builds its card only for the real layer, so a
Navara page no longer fails to start on its stand-in.
- One instruction sentence is shorter, which keeps the Realtime prompt
within its 55,500-character budget with the Sentinel-2 basemap.
Roll back a Space Missions entry whose mission index fails to load
Thanks @gekh. Maintainer review fixes (separate commits): exit cancels the layer's own camera flights again, a failed index load shows a specific toast, tests and docs.
- releaseCameraOwnership marks the camera released before stopping, so a
flight navigation asked to preserve survives; exit also releases it, so an
inactive layer never cancels another owner's flight.
- The failure toast names the mission index only when its error is from the
current Context attempt (stats.errorAt), not an earlier one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review fixes on top of #1578:
- Exiting Space Missions cancels the layer's own camera flights again (entry,
roster preview, mission focus); only a flight navigation started after
releaseCameraOwnership() runs on.
- A failed first index load says so: "Space Missions unavailable: the
mission index could not load. Try again later." on both the Context and
Data Layers routes, instead of a generic transition toast. Translated.
- Regression tests for both; docs and changelog describe the real feedback.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
fix(street-level): without a Mapillary key, start on a keyless source
Thanks @asturksever. Maintainer review fixes (separate commits): filter-only updates keep an explicitly chosen source, keyless Street Level QA gate updated, tests and docs.
Review fixes on top of #1601:
- Only redirect a provider the params switch on to a keyless source, so a
filter-only setParams (360°, SINCE) no longer moves an explicitly chosen
keyless Mapillary to KartaView.
- Keyless QA gate: assert the new KartaView default and that the Mapillary
chip explains its key instead of switching (it used to expect KEY REQUIRED).
- Tests: Graph-proxy keyReady, filter-only updates; shared test helper.
- README and CURRENT-STATE describe the keyless default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>