Navara now matches Cesium where the review found it diverging: a flight
holds the camera inputs, camera.changed counts turns as Cesium does,
render requests made during a frame survive it, follow pinches zoom,
ground widths follow the pixel ratio and Cyber sonar reaches glyph caches
and models. On Cesium, a staged imagery swap keeps loading tiles, setView
replaces a running flight, and a cleared weather detail goes away.
The contract gains MoverSet.failed, maps.terrainRevision,
render.maxTextureSize and surface.sampleTerrainMostDetailed on every
engine, so layers retry failed models, key road heights by terrain,
size weather textures to the GPU and place Bhote Koshi cards on detailed
terrain.
Engine switches keep the live view, follows, focus, layer options and
cards: a pick hands over the exact pose, applies no address options and
pans no carried-over card; a history visit still applies its entry.
Layers put GhostMaps pins, cable landings, OSM Places and directions on
the measured surface without per-frame sampling, draw the Cockpit contact
dot, hide disabled CCTV on attach, and keep ALPR running outside secure
contexts. KML imports draw raised polygon fills, rotated and coloured
ground overlays and north-referenced icon headings, and report files
with nothing to draw.
QA scripts read the contract instead of Cesium internals (test:track is
109/109 again), wait for the camera to rest before clicking, and give
cold switches and parked engines realistic time. Docs follow the code.
Remove code that nothing in the app, the runners or CI uses:
- the one-off migration compare, look and bench scripts and the vector
ground prototype they measured, with the bench tables in the docs;
- the unused Navara-named shared render modules (polygons, ground picks,
mover bank, ground ring);
- the Cesium-shaped aircraft and vessel test renderers, and the trail,
engineFromViewer and horizon-occluder helpers only they kept alive.
Their tests now run on the production displays over the fake engine;
- Navara's unread diagnostics, fallback ports, dead exports and
parameters, stale effect-chain anchors and the unread capability flags.
The QA scripts share one keyless guard and readiness wait, the session
harness reuses the strict harness's browser flags and block list, and the
harness honours QA_ANGLE and QA_RESERVED_PORTS. The bikeshare fixture
keeps only what its scenario reads. The docs name the engine picker where
it is (VISUAL PRESETS) and drop dated run results.
No behaviour changes on either engine. Navara layer renderer lines fall
from 189 to 0 and viewer lines from 891 to 849.
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.
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.
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.
- 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.
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.
- 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.
- src/geo drops the ported debug argument checks and the tests that
bitExact already covers; package boundaries accept a directory entry,
so src/geo/ is listed once per group.
- Cesium no longer carries movers and lines nobody draws with: they are
optional engine capabilities. The fake engine and the Cesium camera use
the shared cameraMath; unused contract tables, clock.ticked, the
'finest' surface detail and Navara click statistics are gone.
- ALPR keeps one overlay core and one presentation model for both
engines, bikeshare one station bookkeeping, the Navara ground effects
one definition helper; image atlases, view centres, mover banks and
colours are shared.
- The legacy switch route, the clock offset and rate controls and other
unused exports are removed.
- The acceptance runs the Navara draw, geo import and scope checks;
scenarios use the harness switch and page helpers; the main chunk
check keeps the size budget only and runs once in CI.
Navara draws its transparent scene after the post effects into a target without the scene depth, so stems and pack polygons and images painted through 3D tiles. A scene-depth test now drops each of their fragments behind the depth Navara packs after its opaque pass; they stay in the transparent pass, so their look in the open is unchanged (scripts/qa-edge-occlusion.mjs compares it with the previous build). Wind flow was already hidden and is kept as a guard in the same scenario.
Preserve the camera, map source and layer selection across engine sessions. Restore the previous engine if startup fails and retain local history navigation.
Separate native scene ownership from shared ALPR source and selection state,
and add bounded Navara sprites with the existing direction/card overlay.
Preserve marker artwork, picking, floor retries and source attribution.
Fix lost post-render wakeups and redundant mobile rail mutations, and keep
HUD typewriter dimensions stable so static views settle without extra frames.
Add parser-fed dev/preview and legacy evidence, including retained failures.
Common W2 terrain and combined performance gates remain open.
Preserve native clustering, selection and direct-action playback through renderer ports. Record both-engine dev/preview, legacy, contract, shell and build evidence with remaining W2 limits.
Keep fire normalization, LOD, anchors, cards and selection portable while the native renderer owns ground cells, sprites, input and scene disposal. Cancel superseded/removed settle timers and unregister the original sprite collection.
Preserve all ten native raster pairs exactly in dev and preview; record source/click/focus/lifecycle, legacy Metal and full regression evidence with strict comparator sensitivity checks.
Keep refresh, cancellation and label policy engine-neutral; inject the original native entities at composition. Prove static geometry, analyst records, owner disposal and last-good refresh behavior. Native dev and QA-preview pass 18 checks each; all eight baseline PNGs remain byte-identical.
* chore: remove four modules nothing in the app uses
src/app/sourceSlot.js re-exported src/sources/sourceSlot.js, which every
caller imports directly. src/data/flowTiles.js and src/data/issPass.js were
reached only by their own tests. src/data/localGeojson.js wrapped
localGeojsonCore.js with the app's services, which the catalog already
passes itself. The local GeoJSON layer tests now build layers the way the
catalog does, so they keep covering localGeojsonCore.js; only the test of
the wrapper's own default host goes. Boundary lists, format scope and docs
drop the removed paths.
* docs: changelog for #1403
Delete three modules that no entry point, package export, test or script
imports:
- src/ui.js, a StyleManager subclass that pre-selected the standalone
catalog. Application controls are built through src/app/controls.js
and src/standalone/controls.js.
- src/standalone/ui.js, a bare re-export of StyleManager.
- src/data/localLayers.js, a list of default infrastructure, cable and
FIRMS layer instances. The catalog builds these layers itself.
With src/ui.js gone, the import-direction check keeps a single
compatibility exception (server/providers/local.js to
server/standalone/key-setup.js), and its test now asserts that a
src/ui.js importing standalone code is rejected. The boundary and
format registries and the docs that named these files are updated.
Move native points, synchronous orbit primitives, tracked entities, follow and picking into the Cesium renderer. Use appClock for satellite physical epochs and isolate the unchanged CelestialRing ephemeris. Synthetic core/dense and ISS/MEO/GEO snapshots match the baseline exactly; all gates pass. The pointer-ownership source assertion follows scene.pick into render/cesium.js; registry and import-boundary assertions now reflect the migrated satellite layer.
src/layers/vessels/render/cesium.js holds the Cesium objects the layer
built itself, one for one: the translucent BillboardCollection of
chevrons (centred, screen-aligned, never depth-tested), the horizon
occluder, window projection, canvas size, viewer.trackedEntityChanged,
and the trail (data/trailRenderer's entity polyline, kept on Cesium: the
engine.lines Primitive path did not draw below the photoreal mesh in
round 6 — the plan's T13/T15 blocker; flights does the same).
The logic imports no Cesium: geo math, Rgba WHITE, the chevron artwork in
icons.js, clicks through the engine's selection dispatcher (own-rule
owner, same rule). Registered as CESIUM_LAYER_RENDERERS['ais-live-vessels'];
src/layers/vessels joins the boundary rule.
W1 replay vs 7c7422e2: vessels/flights/military/localAdsb counts, known
objects, screen points, requests and shot sha256 identical.
qa-vessel-cards --data synthetic 2/2 and qa-vessel-datum (?qaFeed=
vessels:fixture) 4/4, the same as 6eec8f6c.
Tests moved with the code (same meaning): cameraHandoff (the click body
is handleClick), inputOwnership (the guard precedes the click rule's
read of the pick), renderers.test (vessels in the extracted list),
importDirections (an unmoved example layer is satellites), the
ownership services fixture (renderer service).
Task 12a. The Local ADS-B logic (index.js, models.js) no longer imports
Cesium. src/layers/localAdsb/render/cesium.js holds what it built, one
for one: the CustomDataSource of marker entities (billboard with
per-frame position/rotation/visibility CallbackProperties, the 2,000 km
DistanceDisplayCondition, the 978 MHz UAT ring as a PointGraphics), the
model PrimitiveCollection (models load, pose and leave through the shared
aircraft renderer's model part, adopting that collection), the selected
aircraft's trail (data/trailRenderer, as before) and live head segment,
and the camera reads. Registered for 'local-adsb' in the renderer
registry, created at init(viewer, {engine}).
- Clicks go through the engine's selection dispatcher (drill 8) instead of
the layer's ScreenSpaceEventHandler, with the layer's own rule kept as is
— a new `lookUnder` owner kind: its marker on top, or under picks no
layer owns and under trail lines, is selected (and re-selected);
another layer's contact on top, or empty map, releases only its own
selection; a trail line alone does nothing; no gesture rule. To the
other owners it is another layer, as its pick-owner registration made
it before. (The dispatcher's `drillThrough` would have taken the marker
from under another layer's contact, which the old handler never did.)
- models.js reads the camera through the renderer's view (frustum and
horizon tests, ground snap owner) and draws through renderer.models
(loadFleet/add/remove/destroy/setPose: the same MIX-tinted glTF options,
the same HPR frame with the Flights nose offset). The shared aircraft
renderer gains a loadModel seam (default Cesium.Model.fromGltfAsync).
- Cesium math → src/geo; the model tint is an Rgba equal to the old
Cesium.Color; check-import-directions: src/layers/localAdsb/ joins the
rendered layers.
Tests moved with the code, same meaning:
- index.test: the services fixture creates the layer's Cesium renderer and
the selection dispatcher; the click harness scripts the picks on a fake
engine registered for the fake viewer (was a fake ScreenSpaceEventHandler)
and the app's pick owners (trails, a Flights contact) are registered in
the pick registry (was an injected picking service).
- cockpitModels.test: the GLB loads are the aircraft renderer's loadModel
calls (Cesium.Model.fromGltfAsync by default), each asynchronous:false.
- trackingClickGesture.test: the dispatcher's empty click goes to the
plain owners (filter(plain)).
- renderers.test: local-adsb has an extracted Cesium renderer.
New: the dispatcher's look-under rule beside a plain owner (7 click cases).
W1 replay (flights, military, localAdsb) vs a fresh 7c7422e2 run: counts,
known objects, screen points, tracking poses, requests and every shot's
sha256 identical.
Task 11a. The military flights logic no longer imports Cesium: it draws
through the shared aircraft renderer (src/layers/aircraft/render/cesium.js)
with the military's parameters in src/layers/military/render/cesium.js
(sprite-order slot 'military', jet.glb's 180° nose offset), registered for
'cesium' in the renderer registry and created at init(viewer, {engine}).
The same pure move as flights in 10a:
- Fleet: the BillboardCollection and its sprite-order slot (enable keeps
restoreSpriteOrder, as before); sprites stay Billboard handles.
- Models: fleet/tracked/preload glTF loads (asynchronous:false, MIX tint at
MODEL_COLOR_BLEND_AMOUNT, IR boost as `unlit` → the UNLIT CustomShader),
the model collection, setPose. The per-class specs keep their own nose
offset: setPose takes an optional offsetDeg (default the layer's), on
both aircraft renderers (same arithmetic, test added).
- Tracked: the ENU billboard Entity with its position/colour/rotation
callbacks (#FFD166, transparent amber once the model owns the visual),
viewFrom, followed through viewer.trackedEntity + the one exact tracked
frame; refocusTrackedById through follow.refocus.
- Trail: the entity trail and the floored live head segment.
- View: camera/scene reads, frame number, frustum and horizon tests,
render requests, frame events.
- Clicks and Escape go through the engine's selection dispatcher (the
ScreenSpaceEventHandler and keydown listener are gone); the cross-layer
untrack watches the renderer's follow. Same rules: cockpit clicks are
inert, the tracked visual/model is a no-op, another layer's pick is not
empty space, an unowned pick is.
- Colours are engine-neutral Rgba (MIL_ICON_COLOR #FFB800,
TRACKED_ICON_COLOR #FFD166, AMBER_TRANSPARENT), NearFarScalar →
nearFarScale; Cesium math → src/geo.
- check-import-directions: src/layers/military/ joins the rendered layers
(cesium only in render/cesium*.js).
- The military registry and the flights' duplicate suppression are
untouched.
Source-text tests updated with the moved code, same meaning:
- militaryAwareness.test: military's hasContact guard reads fleet.visible().
- trackingClickGesture.test: the military gesture rules are the
dispatcher's (selection gesture before select, click classifier before
the empty-space clear), military answers the empty click with a
user-origin clear — the same assertion flights already had.
- ownership.test: the services fixture names selection and renderer; the
app-composed services build the layer's Cesium renderer.
- renderers.test: flights and military have extracted Cesium renderers.
New tests: the military logic over a plain-data renderer and a fake engine
(amber fleet, click-select, #FFD166 tracked glyph, no-op on the tracked
visual, empty-click and Escape release, the keyboard left on destroy); the
plain-data renderer moved to src/testSupport for both aircraft layers;
setPose with a per-model offset.
W1 replay vs 7c7422e2 (the 10a reference run): every layer's counts, known
objects, screen points, tracking pose after 2 s, requests and the
city/global/orbit shots (sha256) identical. qa-military-names (it checks
the installations layer): the same verdict as 7c7422e2 (the zoom-markers
assertion fails on both). Main chunk 2,890,763 B (−2.9 KB).
Task 10a. The flights logic no longer imports Cesium: every Cesium object it
built moved one for one into the shared aircraft renderer
(src/layers/aircraft/render/cesium.js; contract in render/contract.js), with
the flights' parameters in src/layers/flights/render/cesium.js, registered
for 'cesium' in the renderer registry and created at init(viewer, {engine}).
- Fleet: BillboardCollection + sprite-order slot; sprites stay Billboard
handles (field vocabulary documented for the Navara renderer).
- Models: fleet/tracked/preload glTF loads (asynchronous:false, MIX tint, IR
boost as `unlit` → the UNLIT CustomShader), the model collection, setPose
(same HPR frame with airplane.glb's 180° offset).
- Tracked: the ENU billboard Entity with its position/colour/rotation
callbacks, viewFrom, followed through viewer.trackedEntity + the one exact
tracked frame (kept as is; engine.camera.follow on Cesium would also end
the follow on any camera command, a behaviour change).
- Trail: the entity trail (data/trailRenderer, unchanged: its Primitive
predecessor failed below the mesh) and the live head segment.
- View: camera/scene reads, frame number, frustum and horizon tests,
projection, render requests, frame events, evidence render-loop hold.
- Clicks and Escape go through the engine's selection dispatcher (the
ScreenSpaceEventHandler and keydown listener are gone); the cross-layer
untrack watches the renderer's follow.
- Colours are engine-neutral Rgba (src/layers/shared/color.js), bit-equal to
the Cesium.Color constants they replace; NearFarScalar → nearFarScale.
- Cesium math in the logic → src/geo.
- check-import-directions: cesium only in render/cesium*.js and three/Navara
only in render/navara*.js for flights, aircraft and shared; a layer's
Navara renderer only through navaraMode's import().
Source-text tests updated with the moved code, same meaning:
- readLayerSource reads a layer's render/*.js and, for the aircraft layers,
aircraft/render/*.js (GLB-load, tracked-entity, CallbackProperty checks).
- modelScale.test: _normalBillboardScaleByDistance reads nearFarScale(...).
- spriteOrder.test: flights' enable calls fleet.restoreOrderOnEnable(); the
renderer wires it to restoreSpriteOrderOnEnable('flights', viewer).
- trackingClickGesture.test: the flights gesture rules are the dispatcher's
(selection gesture before select, click classifier before the empty-space
clear); flights answers the empty click with a user-origin clear.
- militaryAwareness.test: flights' hasContact guard reads fleet.visible().
- ownership.test: the services fixture names selection and renderer.
New tests: color, the Cesium aircraft renderer (contract, collections, pose,
loads, frustum/horizon, tracked entity, follow, trail head, adopted
collections), the flights logic over a plain-data renderer and a fake engine
(fleet, click-select, no-op on the tracked visual, empty-click release), the
boundary rule.
W1 replay vs 7c7422e2: counts, known objects, screen points (0 px), tracking
poses (0 m), requests and shots (same sha256) identical. Main chunk
2,890,982 B (+6.2 KB, +0.21 %: the renderer indirection).
src/time/clock.js is the app's own time in ms since the epoch: by default
Date.now exactly (read at each call), with freeze/offset/rate/reset for QA
and tests, and one frame time latched per engine frame (beginFrame). The
contract gains engine.clock { now, frameTime, ticked }, in the fake engine
(a controllable wall clock, fake.advanceTime, fake.appClock), the contract
suite (one frame time for every listener of a frame, latched before
preUpdate, never decreasing), Cesium and Navara.
Cesium latches on the viewer clock's onTick, which CesiumWidget runs right
before scene.render, so scene listeners the app added before the engine
existed read the same frame time; the subscription appears on first use
and destroy removes it. The viewer clock itself is untouched: in the app
it never animates (shouldAnimate false, multiplier 1, its time frozen at
boot), and its time still drives Cesium's lighting as before. Navara
latches in the engine's handler of the view's preUpdate, and the look
reads the engine clock's frame time for the sun, so the app clock is the
one time source on Navara too.
src/time is a portable root in check-import-directions: no engine
packages, no browser globals.
The import-direction check now tells dynamic imports apart
(analyzeModule(source, { dynamic: true })) and rejects any static import
or re-export of src/globe/navara/** from outside that directory, so the
Navara engine can only be a lazily loaded chunk of the app.
Navara's prebuilt workers load their wasm by file name next to
themselves, which the bundler cannot see; a build-only Rollup plugin
copies the missing ones (draco decoder, font worker) into assets/ when
the bundle includes Navara, and adds nothing to a build without it.
Engine events get their own documented semantics (single registration,
branded instances); the fake follows Cesium's roll sign, angle ranges,
synchronous zero-duration flights, flyToSphere defaults and
computeViewRectangle. The contract adds viewProjection, horizonVisible,
flightActive, lookAtTransform/transform, zoomIn/zoomOut, input modifiers,
terrainHeight and clampToHeight, with geometric suite checks. Engine
import rules now follow each engine graph transitively.
src/globe/contract.js defines the engine-neutral GlobeEngine capabilities
(JSDoc), createEngineEvent with Cesium.Event semantics and
assertGlobeEngine. src/globe/testing adds a geo-based fake engine for shell
tests and an engine-agnostic contract suite that the fake passes in Node.
check-import-directions keeps the contract free of cesium, three and
@navaramap, and each engine directory free of the other engine.
An engine-neutral port of the CesiumJS 1.138 core maths gev uses: angle
maths, the WGS84 ellipsoid and geodetic coordinates, 2D/3D vectors, 3x3/4x4
matrices, quaternions and local east-north-up / heading-pitch-roll frames.
Formulas and operation order match Cesium, so outputs are bit-identical
(golden tests pin the centre, pole, -0 and quaternion branches).
- Values are plain-data instances of module-private constructors (own
enumerable fields only), so geo's hot functions keep their own V8 hidden
classes; matrices are plain column-major arrays Cesium accepts.
- Cesium's debug argument checks are ported with the same conditions, order
and DeveloperError messages, gated on DEBUG (src/geo/debug.js): on in Node
and the dev server, folded away by vite build, as Cesium's Build strips
them. A release-parity test compares DEBUG=false geo with Build/Cesium.
- The import-direction check forbids cesium and @cesium/* in src/geo;
CODE-BOUNDARIES.md and THIRD_PARTY_NOTICES.md document the port.
A view is a camera, data layers, visual style, map imagery and an aircraft,
military aircraft or satellite to follow, written in the share-link format
the app already restores. gods-eye-view/view builds, encodes and decodes one,
and share links take their style names from it. open_in_gods_eye_view now
builds its link from a full view: an area or camera, layers, style, map and
something to follow.
Move the composition of the default tool services from the Node MCP
server into gods-eye-view/tools/services, a portable export. Given a
fetch that resolves the sources' /api paths, it builds the same services
in a browser as on the local MCP server, so other surfaces can compose
the same catalog. Queries still read only their services.
Define tools that answer questions from the app's data once, independent
of the surface that exposes them. A tool declares its arguments and the
services it reads; applications compose tools with their own services and
optional call interceptors. Queries reuse the layers' portable source
factories, so they run wherever an application routes /api requests.
The first queries are get_earthquakes, get_active_fires and
get_recent_launches, with a shared area argument (place name, bounding
box, or point and radius) and capped lists.
gods-eye-view/tools/mcp adapts a catalog to the Model Context Protocol
with a small JSON-RPC handler and a stateless HTTP transport, adding no
dependencies. npm run mcp serves Core's tools over stdio to local clients
such as Claude Code, reading from a running app. Both exports are
portable and checked by check:boundaries; the app does not import them.