Read ~1,800 national freeway cameras at runtime from the Freeway Bureau's
keyless MOTC 2.0 CCTV.xml table. Each published MJPEG stream maps to the
same camera server's single-JPEG path; only those exact HTTPS pairs on the
four freeway.gov.tw camera servers are registered. On by default,
CCTV_TAIWAN_ENABLED=0 skips it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Main gained three imagery providers (Google Street View, KartaView,
Panoramax), shared line-coverage, cone and photo-viewer modules, one source
lit at a time and the translated interface. Their shared modules still
carried what this branch removes, so the merge ports them onto it:
- lineCoverage.js is this branch's coverage.js made provider-neutral
(draped lines near the ground only, the selection drawn over them, key
and rate-limit blocking); sequenceCones.js is sequences.js the same way.
Mapillary, KartaView and Panoramax all build on them; Mapillary moves
back to providers/mapillary/ beside the others.
- No ground casting, mesh sampling, surface modes, FOLLOW, legend, prewarm,
orbit overview points (Panoramax's density dots included), stale-tile
swaps or water-filled point budget come back.
- Kept from main because the new providers need them: requiresMapStack and
attachMapStackController (Street View on Google 3D only), ground clicks,
openNearest with an optional provider nearestImage, openImage {frame},
reportError, the view-cone marker (now an SVG glyph), filters /
coverageLabel / sinceBasis, OPEN STREET VIEW, keyAlternatives and i18n.
- photoViewer.js treats the viewer as expanded when its wrap is full screen
or the iPhone popover, as EXPAND now is.
- The fixture browser gate keeps its 8 steps and adds one each for
KartaView and Panoramax; catalogs, docs, boundaries and format scope
follow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cesium's default Tycho-2 sky box is six 1024² JPEGs stretched 3–6× around
the whole-globe view, so space read as an over-compressed JPEG (HAL-627).
The scene now draws its own sky in the Scene#skyBox slot: the 9,096 stars
of the Yale Bright Star Catalogue as point sprites sized in CSS pixels
(magnitude → width and opacity, B−V → colour), over a soft, star-free
Milky Way cube map built from ESO/S. Brunier's panorama (CC BY 4.0). Both
layers share one ICRF→Earth-fixed rotation at the scene clock, with the
TEME rotation filling in until Cesium's IAU 2006 tables load.
Assets total 482 KB, built by scripts/build-bright-stars.mjs and
scripts/build-milky-way.mjs. Frame time and the idle render governor are
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AIS sends ship types as ITU-R M.1371 codes, so vessels_in_area grouped
"70", "74" and "79" as three types, and type: "tanker" matched nothing on
feeds that send codes. Codes now read as the family the vessel cards already
show (CARGO, TANKER, TUG), for rows, group_by and the type filter. The code
table moves to src/data/vesselTypes.js, which has no i18n import, so the
server-side tools can use it; vesselLabels re-exports it.
A destination with no letters ("0", "---") now counts as unknown.
Live, Singapore: "most by type: CARGO (3), TANKER (3), PASSENGER (1)"; "how
many tankers near Singapore" found 3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
render/navara.js: one always-on-top engine.movers chevron bank (atlas of
every chevron icon at 32 px), MMSI pick ids, trail on engine.lines,
camera reads through the shared Navara layer view (extracted from the
aircraft renderer to src/layers/shared/render/navaraView.js, + camera
pickEllipsoid and canvas clientWidth). Registered in navaraMode.
QA: vessel qaDrawn; probe selected() falls back to getSelectedInfo;
qa-w1-layers vessel section; qa-vessels-engines side by side; --engine
for qa-vessel-cards/datum; qa-flights-engines military near pose 100 km.
Unit tests on the fake engine pass; browser runs NOT done (see progress).
Reconcile #880 with current main. Delegate the view-state builder while keeping enabled-only layers, disabledLayerCount, provenance, context semantics, and existing voice behavior. Add parity and roster-read regressions; retain package export, boundaries and changelog fragment.
Preserve original GEV voice, layer and packet contributor credit.
Carry reviewed #816 onto current main without conflicts; preserve i18n updates, replace obsolete changelog edit with a fragment, and add a constructor-wiring regression alongside the owner and rail tests.
Credit: raysung-2026 (report), asturksever (review), quanticsoul4772 (Windows diagnosis).
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).
getNearby returns the contact's label as `id` (the callsign when it has
one) and its key as `icao24`; the drawn rows are keyed by icao24, so the
model lookup missed and every contact was measured at its sprite. Under a
3D model that sprite is hidden and keeps the pre-floor height by design
(T7): the "buried at -27 m" contacts on Cesium (the geoid at AUS) were
those hidden sprites, at HEAD and on 7c7422e2, whose script measured every
sprite. The Cesium report also gives each contact's model state and the
floor cell the layer reads.
AUS, 100 s after enabling flights, both engines: 3 low contacts, 0 buried;
N3752 / DAL848 drawn by their models at 130.2 / 135.4 m over a 123.5 /
128.6 m mesh, the DEM-prior cell warm (123.5 / 128.6 m).
- The DEM prior column read a fresh import of src/data/groundFloor.js, a
module the app does not use for its layers (they read the scene's
surface services), so it was null for every contact. It now reads
window.__godsEyeView.surfaceServices.groundFloor. The terrain proxy
answers 200 and the AUS cells resolve within one or two polls (123-128 m);
the "proxy 503" reading in the task-11 report was this misread.
- A contact a 3D model draws is measured at the model's translation (the
probe's drawn() reports it now): its sprite is hidden and keeps the
pre-floor height by design (T7), so measuring the sprite reported a
buried aircraft that is not drawn.
Task 12b. src/layers/localAdsb/render/navara.js implements the Local
ADS-B renderer interface of 12a on Navara:
- markers: the shared aircraft renderer's glyph sets in magenta #ff4fd8
(class silhouette and scale, ×3 near / ×0.5 far, always on top), drawn
under every sprite layer as Cesium draws the entities; each frame writes
what Cesium's CallbackProperties gave (position, screen rotation,
visibility) and the 2,000 km display distance;
- the 978 MHz UAT ring: its own sprite set, a white ring atlas tinted
#ffb3ef, Cesium's 24 px point plus its 1.5 px outline (27 px), the same
distance scale and distance, pickable as its aircraft;
- models (the Flights GLBs, magenta MIX tint), trail and head: the shared
aircraft renderer's; the camera never follows a local aircraft.
Registered by registerNavaraLayerRenderers (import()); the Navara catalog
now offers flights, military and local ADS-B.
- ?qaFeed=local-adsb:fixture: the local receivers' answer in the server's
format (src/bench/w1Feeds syntheticLocalReceivers: 40 aircraft near JFK,
10 on 978 MHz UAT) through the layer's own decoder-feed session
(sources.localAdsbFeeds, null by default: the layer makes its own).
- qa-w1-layers --layer=local-adsb: a click selects a marker and shows its
card with the receiver line, the camera does not follow, Escape leaves
the selection (it never answered Escape), empty map releases it.
qa-w1-layers local-adsb: Cesium 12/12, Navara 13/13, --compare parity
(global pose: the 2.27 px camera offset only). qa-flights-engines
--layer=local-adsb: 14/14 (fills exact; UAT rings side by side in
docs/migration/phase3/localAdsb).
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.
On Navara each contact row now carries the surface probe's source and tile
size and the coarse DEM floor (cachedGroundFloor). The buried contacts
(SWA4871 60.5 m and AAL2592 -27 m against a 121-126 m mesh) have no DEM
prior (null: the terrain proxy answers 503 here) and the mesh floor sampler
latches nothing without one, so their sprite heights stay the barometric
fallback; the same rows bury on Cesium when the same contacts are low.
Register the keyless TranStar snapshot catalog as a live CCTV source,
synthesize frame URLs from validated numeric camera ids, and keep the
nearest cameras to Houston's metro cores under the existing catalog ceiling.
Task 11b. src/layers/military/render/navara.js: the shared Navara aircraft
renderer with the military's sprite slot (SPRITE_LAYER_ORDER 'military',
under flights) and jet.glb's nose offset; registered by
registerNavaraLayerRenderers (now a table of lazily imported factories,
flights and military), so the Navara catalog offers military and the main
chunk stays without three/Navara.
- The follow passes between aircraft layers on Navara as on Cesium: the
follow-handle → tracked-handle map and the "follow being started" handle
are shared by every aircraft renderer of the page, so a layer whose follow
another layer took reads the new owner's tracked handle (and its
gevSelectionOrigin) — before, it read a bare follow handle and reported
'programmatic'.
- trackById on a selected-but-not-followed contact follows it again (as
flights since 10b; on Navara any camera command ends a follow).
- qaDrawn (the probe's drawn()) for military, shared with flights
(src/layers/aircraft/qaDrawn.js).
- ?qaFeed=military:synthetic[:count]: seeded readsb rows (1 000 by
default, 150 near JFK, 12 on the ground, 8 types), the first 25 hexes
shared with the synthetic civil flights.
- qa-w1-layers: sections 4–6 read the --layer's module (were 'flights');
section 5 keeps the queries beside the voice answers (the voice block
overwrote them, so --compare never compared queries); for military: the
follow passes military → flights → military, and flights leaves the 25
shared aircraft to military while it is on (9 975 / 10 000 / 9 975).
qa-w1-layers --layer=military: Cesium 25/25, Navara 26/26, --compare parity
(queries included; the known 2.3 px camera offset at the global pose only).
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).
Main now takes release notes as one fragment per pull request (#787). The browser asks Google through /api/google/geocode, so the no-geocoder-request check now watches that route.
qa-floor-verify --engine=cesium|navara: on an engine without a viewer the
camera pin, the tile readiness, the mesh probes (engine.surface
sampleHeightDetailed, tiles only, excluding the layer's mover sets) and the
sprite census (the probe's drawn()) go through the engine. The Cesium path
is unchanged. Live AUS traffic: Cesium FAIL as on 7c7422e2 (13 contacts at
the -27 m geoid fallback); Navara FAIL with 1 of 15 low contacts under the
mesh (68.6 m against 121.4 m). Task 10b, checklist item 7.
The QA probe gains drawn(layerId): what a layer draws per object (glyph
image, scale, shown, screen rotation, alpha, position; its 3D model's
scale, shown, ready), from the flights layer's qaDrawn(), which reads the
renderer's handles, so it is the same on every engine.
qa-sprites-b5 and qa-enrich-ambient take --engine=cesium|navara: without a
viewer they read the layer through the probe and move the engine camera;
the military rows/cluster are skipped where the engine has no military
layer. Cesium verdicts unchanged (sprites 9/9; enrich 9/2, the same E8/E10
failures as on 7c7422e2); Navara: sprites 7/7 (flights rows and 3D cluster),
enrich 9/2 (the same two). Task 10b, checklist item 7.
Task 10b:
- qa-w1-layers (flights): 3 focus (the selected object is the focus target,
at the followed centre ≤ 3 px), 4 tracking (held ≤ 3 px for 5 s; wheeled
in, ≥ 149 m), 5 the layer's queries (tracked info, contacts, nearby,
detectable; compared by --compare), 6 share links (the followed object
restored on the same and on the other engine). Clicks only on points the
canvas takes (the Navara attribution card covered the old empty point).
--compare reports the global pose's 2.3 px as the cameras' (Cesium
projects its own nadir 2.27 px off centre at 15,000 km).
Cesium 19/19, Navara 20/20, parity.
- qa-flights-engines (new): the same frozen synthetic fleet at the global,
city, orbit and near (JFK, oblique) poses on both engines, UI hidden;
side-by-side JPEGs (docs/migration/phase3/flights, models off and on),
the same objects in the frame, screen points ≤ 2 px, glyph fill colours
(brightest pixel at the glyph) within 3. 12/12 with models off and on.
- qa-labels: on an engine with flights but not military/satellites, the
deterministic 12,000 / 5,200 field is all aircraft, so detection is
gated on Navara too: 14/14 (Cesium 14/14 unchanged).
Windows runs npm.cmd through a shell, and passing an argument array with `shell: true` is deprecated in Node 24 (DEP0190). Every `npm run doctor` and every Pinokio install on Windows printed the warning. npmProcessSpec now takes the npm arguments and, on Windows, joins them into the command string; the arguments are fixed subcommands (`--version`, `ci`). Unix behavior is unchanged.
Task 10b. src/layers/aircraft/render/navara.js implements the aircraft
renderer contract on the engine contract alone (no three, no Cesium):
- Fleet glyphs: banks of engine.movers sprite sets (one per distance scale
and depth mode the handles ask for; flights: one, 'always', Cesium's
NearFarScalar), growing by doubling, from a glyph atlas of every
aircraftIcon() kind at the fleet (64 px) and tracked (192 px) rasters
(data/aircraftIcons: AIRCRAFT_ICON_KINDS, aircraftIconCells,
aircraftIconAtlasLayout, drawAircraftIconAtlas). Sprite handles keep the
Billboard field vocabulary as setters; a frame's writes commit once at
the end of the frame listener that made them (a microtask outside one).
- Models: banks of model mover sets per glTF and look; handles report
ready from MoverSet.ready() and computedScale from minimumPixelSize kept
on the CPU (radiusM in the spec, the class's nativeRadiusM); poses are
Cesium's HPR frame (modelMatrix) turned by Navara's glTF axes
(Z_UP_TO_X_UP: its loader is already Z-up; chosen side by side with
Cesium among six candidates).
- Tracked glyph: a sprite whose position/colour/rotation callbacks are read
every frame in preRender; follow = engine.camera.follow with the tracked
readout's card as its label (gevFollowHandle); current() maps the follow
back to the tracked handle, also while follow() raises followChanged.
- Trail and live head: engine.lines (alpha 0.85, depth-fail 0.4, geodesic)
in the gev-trail pick namespace.
- View: camera/scene adapters over engine.camera.pose() (registered with
the engine so shared helpers project through it), frustum and horizon
tests, frame number, engine frame events.
flights/render/navara.js registers it for 'navara' from the Navara mode's
import(); the Navara catalog now offers Live Flights.
Also: tracking a contact that is selected but no longer followed follows it
again (a camera command ends a Navara follow; on Cesium a search flight
releases trackedEntity): trackById/select re-follows instead of only
re-publishing. Behaviour change on Cesium only in that case.
Tests: the renderer on the fake engine (contract, atlas, banks, per-frame
commits, growth, tracked callbacks, follow label, model pose, trails) and
the flights logic over it (fleet, click-select, follow, re-follow after a
camera command, release, no set left behind).
The flights records and the mesh-floor sampler kept identical copies of the equirectangular distance; both now import src/data/approxDistance.js. The changelog entry moves to changelog.d (#787), and CURRENT-STATE notes the antimeridian behaviour.
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).