Port to current main: main reserved layer-state token 0 for street-level, so
air-quality takes the next free token, 4 (registry, ledger, and the allocation
tests shift by one). Main moved source contracts into sourceComposition.js, so
air-quality registers its getSnapshot contract there. Package boundaries keep
main's lists plus this branch's air-quality modules and packages.
Prettier on the 13 new test files that failed --check; format-scope.json
now lists every file phase 2 added (src/globe/**, engineChoice, navaraMode,
cameraAngles, the QA scripts and pages, build/navaraAssets and tests).
Upstream advanced past the KartaView merge: transponder-emergency HUD
wording (formatCockpitEmergency in cockpitMath), the GNSS integrity
layer joined the Events group, and a GeoJSON/KML/KMZ/GPX file-import
control landed - all in regions this branch localizes.
- cockpitMath: upstream's formatCockpitEmergency kept alongside the
branch's formatter-injected Context scope (module stays i18n-free;
squawk/ADS-B codes and the broadcast label are data).
- cockpitInstruments: the emergency HUD line composed with t()
placeholders; fleet/feedState stay translated, the 'emergency' class
toggle kept.
- layerPanel: upstream's gnss-integrity id with msg('Events').
- package-boundaries: both sides' entries unioned.
- New upstream UI strings marked and translated, as the branch's
unmarked-string detector requires: the geo-import control's markup
(data-i18n-title/-aria-label/text), its list titles, hints and
summarize() lines (t/tn with placeholders), the aircraft emergency
labels (msg data), and the GNSS legend blurb (t with placeholders).
- es/ru catalogs: 26 new keys each (2019 messages).
npm run i18n: complete, no unmarked strings. npm test: 6739/6741 (the
one failure is the pre-existing environmental OPENAI_API_KEY leak in
devFreshDotenv). Build passes.
Navara 0.2.1's camera controller has no terrain or tile collision; its
only zoom limit, minimumZoomDistance, is a distance from the Earth's
centre, so a wheel zoom near the ground went through it (to -5.9 km).
groundFloor.js runs on the engine's preRender after the camera model
reads Navara's camera. While the camera is free (no flight, no lookAt
target) and below 20 km it samples the ground under it (tiles, else
terrain, else the ellipsoid; at most every 250 ms or 20 m), feeds
Navara's clamp (minimumZoomDistance = geocentric radius of the ground
point + 30 m, sent on changes over 10 m, reset to Navara's default
during flights and above 20 km) so the wheel stops smoothly, and lifts a
camera still below ground + 30 m straight up without camera.commanded.
It only lifts, so it cannot oscillate with the shell's ground guard,
whose lift is a flight the floor stands aside for.
The page's engine is ?engine=cesium|navara, else localStorage gev.engine,
else Cesium (src/app/engineChoice.js). Cesium mode is unchanged.
In Navara mode (src/app/navaraMode.js, loaded with import() only then,
and loading the Navara globe the same way) the scene builds the Navara
globe on the app container with no viewer; map stacks go through
engine.maps (Google 3D from the browser key, else Esri imagery); only the
shell's own data credits are registered. The layer catalog is empty and
the controls get empty layer stand-ins. The shell does not create its
viewer-bound parts (cockpit view, celestial ring, detection, tracked
readout, post-process stages); the tools install the render governor on
the engine, skip the director, annotations, drawing, imagery box, scope
mask, voice and embedded views, skip the first-run welcome, and show
"Experimental Navara engine: layers, cockpit, director and voice are
unavailable". window.__godsEyeView.viewer is null.
Navara's attribution card is kept clear of the location tray. A few
shell paths became null-safe for the missing viewer, layers or stages
without changing Cesium behaviour.
scripts/qa-engine-shell.mjs checks both modes headless: boot, viewer,
console errors, map drawn, search and fly, north/tilt/zoom/reset, orbit,
HUD, share link restore and map stacks.
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.
Image loading (photoViewer.js)
- One AbortController per open: a new open, close or unmount stops the
previous photo's first and sharper downloads (an <img> by src = '').
A collapsed viewer never falls back to the original; Panoramax's flat
fallback is the thumbnail. A superseded open resolves quietly.
- Flat HD tracked as "loading" and "shown" apart: a discarded response no
longer blocks later upgrades, and resizes while it loads ask once.
- No upgrade of the photo still shown while another opens.
- Size limits before decoding: imageSize.js reads JPEG/PNG/WebP/GIF
headers; more than 64 MP or 16,384 px a side is refused undecoded, an
<img> that decodes larger is refused and let go, flat HD needs a known
width of at most 8,192 px, and panoramas always decode with a resize.
Panoramax provider
- Retry-After parsed as seconds or an HTTP date; the browser cannot read
it here, so busy answers back off 5 s doubling to 300 s, reset once
answered.
- Headers timed, then the body by an idle timeout (15 s of silence), so a
slow link still gets its megabyte tiles.
- Cones centre only on the picture shown (onShowing), never on a late
lookup; lookups nobody waits for are aborted (shared, refcounted).
- onReopen: a GPU reset redraws in the viewer, not through the core.
- Cones draw the home z15 tile at once and add neighbours as they arrive;
a neighbour that failed is not counted as drawn.
- Coverage tiles decode in ~8 ms slices (a 2 MB z10 tile over Paris was
one ~85-200 ms task); sorted by measured length without a Map.
- Box search asks for 20, not 100 (the API returns them nearest first).
- looksLikeEmail checks every '@'; creatorOf de-duplicates with a Set.
lineCoverage.js
- A redraw while a swap still builds keeps what is on screen (the swap's
old lines and its ready new ones) and drops only the unready ones.
Tests for each (mutation-checked), two vacuous tests made real, the
cones' pictures lane and readable()'s busy/timeout paths covered; the
fixture's box search is nearest first. Docs: README filters and share
links, CHANGELOG HD wording, CURRENT-STATE.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The application shell keeps the page's globe engine and hands it to the
navigation controller, location navigation (search, city and POI flights,
orbit, globe reset), the tilt and north-up controls, share-link save and
restore, share restoration's first-gesture listeners and the HUD. The
viewer is passed only where entity tracking still lives on it (phase 1c)
and to the HUD's basemap label context (voice); every such use is null-safe.
Share links and captured shots write heading and roll as Cesium's camera
reports them (src/cameraAngles.js): the contract's [0, 2π) pose maps an
exact 0 back to Cesium's 2π, except the roll of a vertical view, so
links stay byte-identical (heading=360, roll=360) on every engine.
Source-reading tests follow the moved code with the same checks.
The startup fly-in, orbit, world-target focus, the arrival ground guard,
arrival ownership, camera sensitivity claims, landmark/viewport/globe
flights and place search (with its view bias and near-view recovery) take
the globe engine. Callers that still hold a viewer (voice, layers until
phase 1c) reach the same engine through engineFromViewer, and arrival
ownership is keyed by the engine whichever one a caller names.
The ground guard yields on camera.commanded instead of patching the
viewer camera itself; on Cesium that event hooks the same command methods
and tracking changes, so direct Cesium callers still end it. Landmark
framing reads the frustum fov from the pose's fovy and aspectRatio.
Tests that built viewer doubles now run on the fake engine (or on the
Cesium engine over the same double), with the same expectations.
The pill step read #sl-status straight after the credit wait. Tiles
requested while Cesium painted the credit can flip it back to LOADING,
which failed CI on PR #1038 (actual 'BUTTON:LOADING:true'). Wait for
coverage to settle and the pill to read ON before asserting.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Map credits (maps/credits.js) are shown through a credits capability
(engine.credits.addStatic) instead of the scene's credit display; the
shared map scene takes the engine's credits, so the Cesium engine's maps
and its credits part share one owner. Data credits (registerDataCredits,
registerDynamicCredit, show/hideOsmCredit) take the globe engine and keep
accepting a viewer from layers that still hold one; the scene registers
the data credits on its engine. The Cesium credits part reads
Viewer.creditDisplay, else the scene frame state display it aliases.
The world overlay host takes its frames (frames.postRender), camera events
(camera.moveEnd), canvas size (host.size), projection matrix
(projector.viewProjection), horizon test (projector.horizonVisible) and
render requests (render.request('world-overlay')) from the globe engine.
initWorldOverlay accepts an engine or, for callers that still hold a
viewer, resolves the viewer's engine through engineFromViewer.
Contract: camera.position(result) becomes camera.livePosition, one
engine-owned object holding the current position, so per-frame readers
and paint lanes keep a live camera position without allocating; engine
events gain raiseValue for argument-list-free frame dispatch. Steady-state
frame allocation is unchanged (worker probe, 30 chunks: 2696/9536/38593
B/frame before and after; detection 473749 -> 408085).
Contract: camera.position(result?) and camera.height() for per-frame
readers, render.request(reason?), maps.releasePhotorealTiles, sources
with a caller-owned ready provider, isGlobeEngine. engine.maps now hides
every tileset it has shown, lets the last imagery/photoreal call win, and
setPhotorealTiles(null) restores the last base imagery and its credit
instead of leaving the photoreal credit on a bare globe.
Cesium engine: reads the Viewer camera/canvas aliases, detaches bridged
events through the remover Cesium returns, raises frame timestamps
without allocating, re-aims the horizon occluder only when the camera
moved, and renders the scene directly when the governor governs another
viewer (or none yet). engineFromViewer returns the registered scene
engine of a viewer, else builds and caches one. The render governor stays
the Cesium render backend under engine.render and gains
isRenderGovernorInstalled.
The scene builds the page's Cesium engine right after the viewer
(src/app/globeEngine.js), names the startup Google tileset to it once
loaded, returns it beside the viewer and destroys it before the viewer.
The debug handle exposes it as window.__godsEyeView.engine. Nothing
consumes it yet; render holds taken before the governor is installed
apply at install.
qa-globe-contract now runs on the app's engine (self-built only when the
app has none); a throwaway engine checks creation and destroy leave the
scene untouched.
Map sources are applied through one src/maps/sceneApply.js helper shared by MapSourceController and engine.maps (live layer kept for an unchanged provider, providers cached per source). The contract gains flyTo endTransform and rectangle destinations, flyToSphere easing, drillPick width/height and pick content, with Cesium's own flyToSphere range. Input skips listeners removed during dispatch and stays inert after destroy.
createCesiumEngine wraps an existing viewer without changing it: camera, picker, projector, frames, render (through the render governor), surface, input, credits and maps, with geo values out and Cesium values in at the boundary. Map and surface code is shared with src/maps and services/groundSnap instead of copied. scripts/qa-globe-contract.mjs runs the contract suite against the engine in headless Chrome; the camera-events check now waits for the previous move to end, as Cesium's moveStart marks a move from rest.
Reverse geocoding now goes to /api/google/reverse-geocode on the app
server, so the harness stub for maps.googleapis.com never matched and a
keyed run would reach Google live. Answer both proxy routes with
ZERO_RESULTS instead.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
Conflicts resolved by keeping both sides: the Events group gains
gnss-integrity with a translated label, the cockpit aircraft line shows
main's transponder emergency through a localized formatter (cockpitMath.js
stays i18n-free, like the Context scope line), and the boundary lists keep
both the i18n modules and main's new data modules.
Main's new UI is translated into es and ru: the GNSS Integrity row, legend
and notes; geo file import (button, hints, list actions, every import
error, with numbers passed as placeholders); transponder status names.
Also formats kartaview/viewer.js so format:check passes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CHANGELOG: keep both new [Unreleased] entries (Panoramax, and the
emergency-transponder highlight from #856).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep both: Catalonia CCTV alongside the NYC DOT TMC pack in the source
imports, README layer table and CURRENT-STATE data table, and both
CHANGELOG entries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Normalize GeoJSON ground footprints, preserve explicit KML track heights,
and tessellate ground paths before native loading. Validate coordinates,
geometry types and decoded image budgets before allocating native resources;
strip loader-sensitive metadata and settle deferred failures and cancellation.
Render static pins in owned billboard collections instead of walking tens
of thousands of point entities each frame. Refine nearby ground heights in
batches, isolate imported pins from height picks, and release owned resources
and point selections on removal. Use safe ray origins above a hidden coarse
globe and retain valid mesh heights outside the current footprint.
Frame complete geometry with viewport-aware nadir arrivals, including walls
and rotated overlays; respect camera gestures and accept document file drops.
Strengthen real-loader, native 3D Tiles, paint, clock and lifecycle coverage.
Addresses both P1 and all 25 P2 findings in the follow-up review of #1122.
Preserve upstream changes, restore the reviewed Radio ownership regression,
and retain reporter raysung-2026 credit and #804 provenance.
Reconciles upstream main 81cf437 and PR #816 head 702ecc3; no force push.
Keep this branch's CHANGELOG entry above upstream's; upstream main no longer lists the GBFS station-name entry an earlier merge carried here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Seven QA scripts hard-coded macOS Google Chrome. They now share chromeExecutablePath(): PUPPETEER_EXECUTABLE_PATH when set, then macOS Chrome when installed, otherwise Puppeteer's managed browser. The cables screenshot path is built with fileURLToPath so it works on Windows. qa-overlay-baseline keeps its fixed Chrome because its baselines record the executable.
Co-authored-by: Sameh Khamis <[email protected]>
process.exit(2) after a successful fetch trips a libuv assertion on Windows and turns the exit code into 127. The four gates set process.exitCode and return instead, and discard the unread reachability response so an early exit does not wait for the server to finish the body.
Co-authored-by: Sameh Khamis <[email protected]>
The draped coverage line is about one pixel wide and can sit on a pixel
boundary: the probe hit at a fractional x while the click at the integer
pixel missed. Wait for tiles, click only at an integer pixel that picks the
line right before the click (off the centre column first), retry up to 4x.
Street level view/cameraFollow/groundClick/index, traffic footprint/viewport/ingestion, transit viewport/trails/state/rendering, FIRMS state/viewport, installations controls/viewport/state, radio queries/navigation/state, bikeshare viewport, submarine cable overlay and data/detection now use src/geo for Math, Cartesian2/3, Cartographic and WGS84 maths. Cesium stays for Rectangle, Ray, BoundingSphere, HeadingPitchRange, EasingFunction, EllipsoidGeodesic, EllipsoidalOccluder and the Ellipsoids passed into Cesium (pickEllipsoid, occluders). traffic/state.js keeps its Cesium lerp scratch (written per frame by Cesium.Cartesian3.lerp in animation.js; a geo scratch cost +4-7% there) and traffic/timing.js stays on Cesium (a test keeps it source-equivalent to model.parseRoads, outside this batch). street-level-layer, traffic-layer, installations-layer, bikeshare-layer and submarine-cables own the src/geo files.
Launches selection/replay/panel/policy/policyHelpers/state/camera, CCTV geometry/model/projection/navigation/cards/geometryQueue/state and ALPR model/visuals now use src/geo for Math, Cartesian2/3, Cartographic, Matrix3/4, Quaternion and ENU frames. launches/paths.js keeps Cesium (EllipsoidGeodesic). In camera.js, EllipsoidGeodesic, BoundingSphere.fromPoints/union and the BoundingSphere constructor stay on Cesium; the geo Cartographics passed into EllipsoidGeodesic are only read field by field and cloned there (computeProperties). The CCTV plane orientation stays a Cesium Quaternion and plane dimensions a Cesium Cartesian2 (entity ConstantProperty values). launches-layer and alpr-cameras own the src/geo files.
Moves the Cartesian3/Cartographic/Matrix3/Matrix4/Math calls of the vessel, satellite and Contacts (awareness) layers, plus flights controller/enrichment, to src/geo. The satellites-layer package boundary now owns the src/geo modules. Awareness keeps Cesium.Cartesian3.clone for the resolved subject position, which reaches the installations anchor (anchor.test pins a Cesium instance).
Move the Cartesian3/Cartographic/Matrix4/Transforms/Ellipsoid/Math calls of
the second batch (cockpit camera, context and controller; camera orientation
controls; application shell; location navigation; the annotation resolver
and engine; voice actions and pointer pick; the scene director; the embed
app) to src/geo. Behaviour is unchanged: same argument order, same `result`
semantics.
Kept on Cesium: out-of-scope classes (HeadingPitchRange, BoundingSphere,
Intersect, EllipsoidalOccluder, EasingFunction, SceneTransforms, JulianDate),
WGS84 where it goes into pickEllipsoid or EllipsoidalOccluder,
Matrix4.IDENTITY handed to lookAtTransform, and the Cartesian2 window
positions that reach scene.pickPosition: Cesium keys its per-frame pick
cache on windowPosition.toString(), which a plain object would collapse to
one "[object Object]" key.
The Contacts-subject harness in gevActions.test stubbed
Cesium.Cartographic.fromCartesian to place its subject; it now builds a real
subject position at the same coordinates with geo.cartesian3.fromDegrees.
The cockpit-controls and scenes package-boundary groups now own the src/geo
files.
Move the Cartesian3/Cartographic/Matrix/Quaternion/Transforms/Math calls of
the first batch (src/data motion, anchor, tracked-camera and icon helpers;
the root camera, locations, HUD, share-link, world-focus, ground-guard,
orbit and cloud modules; services/groundSnap) to src/geo. Behaviour is
unchanged: same argument order, same `result` semantics.
Kept on Cesium: out-of-scope classes (JulianDate, Axis, HeadingPitchRange,
BoundingSphere, Rectangle, EasingFunction, EllipsoidalOccluder with its
WGS84 and seed vector, SceneTransforms) and Matrix4.IDENTITY wherever it is
handed straight to a Cesium camera call (locations.test pins its identity).
synthesizeForwardKinematicsFix now returns a plain position, so the two
snapshot renderers clone it with the static Cesium.Cartesian3.clone instead
of the instance method. The package-boundary groups that reach these
modules now own the src/geo files.
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.