Commit Graph
990 Commits
Author SHA1 Message Date
German Khokhlov 46b3aceafc feat(alpr): render camera markers on Navara
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.
2026-10-28 21:06:08 +04:00
German Khokhlov 9d587c1d37 feat(radio): render stations and clusters on Navara
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.
2026-10-28 14:49:18 +04:00
German Khokhlov d82a6f1550 feat(bikeshare): render GBFS stations on Navara 2026-10-28 13:12:19 +04:00
German Khokhlov 1a694baf5a feat(installations): add Navara renderer 2026-10-28 12:18:03 +04:00
German Khokhlov 965a82144c refactor(installations): inject engine renderer adapter 2026-10-28 09:54:05 +04:00
German Khokhlov f4f0088814 refactor(installations): use globe camera contract 2026-10-28 09:50:17 +04:00
German Khokhlov c58bc9864b refactor(installations): make model engine neutral 2026-10-28 09:47:40 +04:00
German Khokhlov 5dd05a4a81 feat(firms): add Navara renderer checkpoint 2026-10-28 09:36:50 +04:00
German Khokhlov a9b3125a99 Extract the native FIRMS renderer and own its settle timer
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.
2026-10-27 14:40:08 +04:00
German Khokhlov a3152ab719 Add depth-classified earthquakes on Navara
Match native ground footprints, encoded alpha and source ordering on real terrain and photoreal tiles while excluding forward meshes. Cache static screen data and reduce large-disc edge work without changing GPU pixels.

Add deterministic lifecycle, source/share, foreground/horizon and three-context performance proof; restore credits for available Navara feeds and retain failure history in migration evidence.
2026-10-27 13:42:07 +04:00
German Khokhlov c89185f352 refactor(earthquakes): extract native renderer with identical ground-disc output
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.
2026-10-27 10:56:41 +04:00
German Khokhlov e8870a4766 test(migration): establish W2 native oracle and release removed overlay identities
Add the sequential W2/completion plan and deterministic earthquake ground-raster, geometry, lifecycle and strict runtime QA. Prove the comparator rejects incomplete evidence and release deleted overlay entries from the collision arbiter. Record native dev/preview acceptance and full checkpoint gates.
2026-10-27 10:48:26 +04:00
German Khokhlov 0600964249 Complete phase 3 W1 acceptance and close T16 findings
Fix Navara model display tint, overlay ordering, follow gestures and resource cleanup.
Reduce repeated aircraft work and correct fixture enrichment responses.
Strengthen dev/preview QA, preserve failure history and document verified W1 results.

Validation: 7171 ordinary tests pass, one Windows-only skip; 15 allocation probes;
37 W1 acceptance steps; contracts, shell, four performance gates; 18 identical
Cesium baseline PNGs. Known pre-existing CCTV and live-source limits are recorded.
2026-10-26 23:23:25 +04:00
German Khokhlov 766c786093 feat(launches): render on Navara 2026-10-23 01:19:57 +04:00
German Khokhlov 4baefeb8f9 refactor(launches): Cesium rendering behind a renderer interface 2026-10-23 00:32:10 +04:00
German Khokhlov 1839d62160 refactor(launches): geodesics on src/geo
Port native Vincenty geodesics and launch-path math with exact Cesium oracles.
Keep all existing launch path-test expectations unchanged.
2026-10-22 23:48:50 +04:00
German Khokhlov 6bdb995fe3 feat(satellites): render on Navara
Render satellite fleets and selected targets through mover banks and orbit lines.
Enable the celestial HUD on Navara and keep native follow explicitly in ENU.
Add deterministic interaction, share, orbit, motion and cleanup acceptance QA.
Record complete targeted acceptance and remaining shared cross-shell tile failures.
2026-10-22 23:41:14 +04:00
German Khokhlov 000ef99464 refactor(satellites): Cesium rendering behind a renderer interface
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.
2026-10-22 22:37:09 +04:00
German Khokhlov 83fbe6949f fix(navara): disable redundant sun shadows in minimal look
Minimal inherited the sun light default of castShadow=true although the photoreal tiles carry baked lighting. Use the same no-shadow sun configuration as cinematic. The focused 3-run city gate improved from C4.6/N5.1 ms (1.1087) to C4.6/N4.9 ms (1.0652) with 12000 vessels. Full Node 24 tests and allocation gates, boundaries, format, build and panel build pass.
2026-10-22 13:38:22 +04:00
German Khokhlov c8a4f3ed98 fix(vessels): upload Navara chevrons and restore click focus
Fix delayed and growing mover bank commits, reject stale hidden model picks, and route world-focus through the engine. Strengthen vessel parity and performance checks without changing thresholds.
2026-10-22 12:31:59 +04:00
German Khokhlov 69a78710cc feat(vessels): render on Navara (WIP: browser parity not yet run)
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).
2026-10-17 14:06:11 +04:00
German Khokhlov 620ada6df3 fix(globe): Navara picks rank as drawn and check model hits at their drawn radius
T11 review follow-up to ed9ea7e6. CPU hits rank as the engine draws them:
sets without a depth test over depth-tested ones, then by set order
(the tracked bank over the fleet, flights over local ADS-B), then depth —
so the CPU's top hit that checks a GPU answer is the instance drawn on
top. A model hit is no longer taken as answered: the model mover keeps
each instance's drawn diameter (after minimumPixelSize/maximumScale) and
the CPU checks the hit against its projected position within that radius
(+3 px); the CPU pick sees models at that radius too.
2026-10-17 13:26:42 +04:00
German Khokhlov 1fe8267550 refactor(vessels): Cesium rendering behind a renderer interface
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).
2026-10-17 12:47:14 +04:00
German Khokhlov 6b27b21cc4 feat(data): an own-rule selection owner keeps its whole old click handler
Vessels' click rule (sibling ownership, cyclone background, card hit
test, unkeyed records) does not map onto the plain or look-under rules:
an owner with onClick(ctx) gets every click with the top pick, no gesture
rule (its old LEFT_CLICK handler), and is another layer to the rest.
2026-10-17 12:47:14 +04:00
German Khokhlov 6eec8f6cb0 fix(qa): floor verification finds a contact's model by its icao24
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).
2026-10-17 12:12:00 +04:00
German Khokhlov 71b7177dc0 fix(globe): Navara model tints are decoded from sRGB before mixing
The instance colour (sRGB, as Cesium takes colours: the military amber
#FFB800) was mixed into the linear material colour as is, so a tinted
model drew paler and brighter than Cesium's: the military footprint gate
read 124 % (more pixels cleared the contrast threshold over water) and
the median luminance +9 %. White (flights) is the same either way, which
is why the flights gate passed. The tint is now sRGBTransferEOTF-decoded,
as the sprite sets already decode theirs.

qa-flights-engines' model gate adds the highlights: the p90 luminance of
the model pixels within ±10 % of Cesium, with the near-white pixel count
reported (10b review low 2).

Models on, near pose (computed scale ratio ~1.0000 on both):
- military (from 100 km): footprint 9,902 vs 8,610 px (115 %), median
  luminance 158.9 vs 155.3, p90 190.9 vs 184.5 — 18/18;
- flights: footprint 104 %, median 189.8 vs 196.7, p90 223.1 vs 247.8
  (near-white px 83 vs 510: Cesium's white models saturate where Navara's
  tone mapping rolls them off) — 20/20.
Models off: flights 16/16, military 15/15. Side-by-sides regenerated.
2026-10-17 11:28:57 +04:00
German Khokhlov 5ff8c5d7a6 test(qa): floor verification reads the app's ground floor and measures models where drawn
- 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.
2026-10-17 11:02:52 +04:00
German Khokhlov ed9ea7e679 fix(globe): a Navara click never takes a stale GPU pick of a neighbour
Navara 0.2.1 can answer a click with the previous gesture's pick. The
check let that answer through when its instance was drawn within twice its
pick radius of the click — the followed aircraft at the centre beside the
clicked one (the 20/24 flights checklist run: a click selected the
previously followed aircraft). A GPU answer for a sprite or point set now
has to be drawn under the click (no slack) and be the instance the CPU
pick finds on top there (lines left out: they are not in the GPU pick);
otherwise the CPU pick answers. Models, which the CPU pick does not see,
are still taken as answered. The CPU drill takes `includeLines`.

Test: 20 gestures alternating a click on A and a click on its neighbour B
answered with A's stale pick — B every time. In the app, 40 scripted
click/follow/release gestures on Navara selected the clicked aircraft
every time with and without the fix (the stale answer is rare).
2026-10-17 11:02:52 +04:00
German Khokhlov ea1e2f9c04 feat(localAdsb): render on Navara
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).
2026-10-17 06:50:15 +04:00
German Khokhlov 65e30546dd fix(globe): Navara sprites keep a full rewrite when a commit follows it
A sprite/point set uploads its instances by update range. When the camera
went far enough to move the set's origin, update() rewrote every instance
(relative to the new origin) — and a commit later in the same frame
cleared the ranges and kept only its own, so the GPU never got the
rewrite: the instances not committed again drew offset by the origin move
(a military glyph at the orbit pose drew 45 px from its point after the
global → city → orbit sequence; the CPU picks, the probe and Cesium had it
in place). Ranges now add up until three uploads them (it clears them
after the upload).

Found by the military side-by-side (11b); flights' sprites had the same
exposure since 10b.
2026-10-17 06:49:53 +04:00
German Khokhlov 0b8cb855fa refactor(localAdsb): Cesium rendering behind a renderer interface
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.
2026-10-17 05:50:30 +04:00
German Khokhlov 941b308c29 test(qa): floor verification logs each contact's probe answer and DEM prior (10b review 6)
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.
2026-10-17 05:34:41 +04:00
German Khokhlov 10d0e49ddb fix(globe): Navara aircraft models face, size and light as Cesium's (10b review 3)
The near-pose finding (Navara models faint, Cesium ~23 bright aircraft)
came from the side-by-side's frozen page clock: the fleet tick ran once per
pose, so Cesium never handed its aircraft to their models and drew 3×
glyphs, against real models on Navara. With the hand-off driven (the move
ends every 10 frames while a pose loads), both engines draw models, and
the comparison showed three real differences, all fixed:

- Orientation: Navara's glTF loader is three's GLTFLoader, which keeps
  the scene Y-up; the instance turn is now Cesium's whole axis correction
  (Y_UP_TO_Z_UP × Z_UP_TO_X_UP), not Z_UP_TO_X_UP alone. Every fleet model
  drew 90° off its heading.
- Size: minimumPixelSize is taken at Cesium's distance (Camera
  distanceToBoundingSphere: view depth less the radius at the model's own
  scale), in the mover and in ModelHandle.computedScale; the straight-line
  distance drew off-centre models up to 16 % larger.
- Colour: the instance colour tints by the set's blend amount (Cesium's
  colorBlendAmount) and its alpha is the model's opacity (Cesium: a
  model colour's alpha makes it translucent), not a blend weight; a model
  is translucent only while faded. An ambient share of the tinted surface
  (MODEL_AMBIENT 0.5, Cesium's image-based lighting) keeps a tinted
  aircraft near-white at a low sun (median luminance 139 → 190; Cesium 197).

qa-flights-engines (now --layer=flights|military) gates the near pose
with --models=on: computed scale within ±5 % per model (ratio 1.000007),
footprint against the same view with the layer off within ±20 % (1553 vs
1496 px, 104 %), median luminance within ±10 % (189.8 vs 196.7). The probe's
drawn() reports a model's computedScale. Flights models on: 19/19.
2026-10-17 05:23:50 +04:00
German Khokhlov 9dc446bd24 feat(military): render on Navara
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).
2026-10-17 05:05:40 +04:00
German Khokhlov 3a0a3b0551 test(military): the colour constants equal the Cesium.Color values they replaced 2026-10-17 04:52:33 +04:00
German Khokhlov 7c0891deda refactor(military): Cesium rendering behind a renderer interface
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).
2026-10-17 04:52:15 +04:00
German Khokhlov e007350645 fix(ui): explicit navigation releases flights when an engine leaves out military awareness
On Navara the military-awareness module has no controls, so its
releaseCameraOwnership was skipped silently and a city pill, voice
fly_to_location or reset ended the follow but kept the aircraft selected.
The trackers it would release are released directly instead, as on
Cesium (selection and follow both cleared).
2026-10-17 03:59:51 +04:00
German Khokhlov 2da7b6d0d9 fix(ui): north-up and tilt keep an engine's own follow (Navara)
Inside a Navara follow the orientation controls frame the follow's target
and retarget its viewFrom instead of moving the camera, and the release
cancels only a running flight (a cancel is a camera command, which ends
the follow). Cesium's trackedEntity path is unchanged. Updated test:
cameraOrientationControls.test.mjs (orientation navigation starts a
flight before expecting its cancel; no flight, no cancel).
2026-10-17 03:48:12 +04:00
German Khokhlov dcfb1219d5 feat(globe): a follow handle names its target
FollowHandle.target() returns the followed point, checked by the contract
on both engines. The contract's model check now waits for ready() before
looking for the model's pixels: the magenta point and sprites could fill
the region before the glTF loaded (qa-globe-contract cesium 73/73,
navara 79/79).
2026-10-17 03:48:11 +04:00
German Khokhlov ba1191b2e5 test(qa): floor verification on both engines
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.
2026-10-17 03:20:44 +04:00
German Khokhlov 132a941e9a test(qa): sprite and enrichment checks on both engines through the probe
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.
2026-10-17 03:15:12 +04:00
German Khokhlov 03a0ff3a01 test(qa): flights parity checklist on both engines, side by side
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).
2026-10-17 02:35:32 +04:00
German Khokhlov b14bf7e013 feat(flights): render on Navara
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).
2026-10-17 02:15:04 +04:00
German Khokhlov 20e9d4a0fa fix(globe): Navara sprite atlases are sampled as Cesium samples billboards
Linear, without mipmaps: Cesium's billboard atlas has none, and a mipmapped
atlas greyed small glyphs Cesium draws at full colour (a 5 px aircraft at
15,000 km: 239 against Cesium's 255; now 255 on both). Task 10b colour
parity.
2026-10-17 02:15:04 +04:00
German Khokhlov 1dd3220812 fix(voice): Navara refusals reach speech, analyst queries and stop_tracking
Review of 10a (low):
- The spoken answer to set_layer_visibility on a layer this engine lacks is
  the refusal's own sentence (why, and the switch to ?engine=cesium), not
  only "Couldn't turn on".
- analyst_query naming layers the catalog left out (no viewer) refuses with
  UNAVAILABLE_ON_ENGINE and the layer ids instead of answering zero.
- stop_tracking skips layers the engine does not have, so with nothing
  tracked it is a no-op success on Navara (it reported the absent military
  and satellites layers as failures).
Tests for each (the old code fails the last two); Cesium unchanged.
2026-10-17 01:18:12 +04:00
German Khokhlov 443c581280 refactor(data): focus projects through the engine; readout follows a tracked visual's follow
Task 10b foundation.
- focusDeemphasis projects through the engine registered for the scene it
  is given (engineFromViewer(scene).projector), as iconOrientation already
  does, else Cesium's SceneTransforms. On Cesium the engine projector is
  SceneTransforms, so nothing changes there; on Navara the aircraft
  renderer's scene adapter is registered with the engine. Test with a fake
  engine.
- refreshTrackedReadout accepts a layer's tracked visual that names its
  follow handle (gevFollowHandle): engines without tracked entities
  republish the card through it. Test.
2026-10-17 01:16:59 +04:00
German Khokhlov 4dfcba8989 feat(globe): mover sets report ready(); Navara scales by distance as Cesium
Task 10b foundation.
- MoverSet.ready(): whether what the set draws has loaded (Navara: the
  model set's glTF, a URL atlas; Cesium: every instance's Model, the cropped
  atlas; parts without it draw at once). The aircraft renderer keeps the 2D
  glyph until a model set is ready, as the Cesium path waits on Model.ready.
  Contract suite: a drawn model set is ready() (both engines); unit test of
  the neutral store.
- Navara sprites and points interpolate scaleByDistance as Cesium's
  NearFarScalar (over the squared camera distance, eased by ^0.2) instead
  of linearly in distance, so aircraft glyphs have Cesium's size at every
  range; the CPU pick radius follows (distanceScale, tested equal to
  focusDeemphasis' port of czm_nearFarScalar). The contract text says so.
2026-10-17 01:16:59 +04:00
German Khokhlov d81ebf1268 build: the panel build leaves out the initial-chunk guard
The MCP panel build inlines every dynamic import into its one script, the
Navara mode included, so the guard that keeps Navara out of the app's
initial chunks failed it (npm run build:panel was red since d483e589). The
panel config filters the guard out, as phase 4 does; a test pins it.
build:panel is green and joins the gates. Phase-4 merge review item 2.
2026-10-17 01:09:58 +04:00
German Khokhlov 2ce1c55f69 build: the QA probe stays out of production builds
src/app/qaProbe.js was statically imported by standalone/application.js
and shipped in the main chunk (+639 B gzip). It now loads with import()
behind import.meta.env.DEV at the import site: production builds drop it
(main chunk 2,890,982 -> 2,889,432 B), the dev server still installs
window.__godsEyeView.layers (checked headless). Phase-4 merge review item 1.
2026-10-17 01:09:58 +04:00
German Khokhlov 7f13ef4a87 refactor(flights): Cesium rendering behind a renderer interface
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).
2026-10-17 01:04:25 +04:00