Commit Graph
2842 Commits
Author SHA1 Message Date
Sameh Khamis 21f284f79c Move Ctrl+wheel and trackpad pinch zoom into the Cesium renderer
installTrackpadPinchZoom and its pinch scaling move unchanged from
src/app/viewer.js to src/render/cesium/ctrlWheelZoom.js, where the input
port's ctrlWheelZoom will turn it on and off. The application scene
imports it from there; the application/viewer export no longer carries
this undocumented helper.
2026-11-05 18:34:10 -08:00
Sameh Khamis 60a3a2808e Share a scene's camera-input holds beyond the dispatcher
The take-over of the camera controller's enableInputs moves, unchanged,
to src/render/cesium/cameraInput.js. acquireCameraInput(scene) gives every
user one set of counted holds per scene, so the renderer's input and
camera ports can hold camera input alongside the dispatcher; the
controller gets its own property back when the last user releases. The
dispatcher acquires its share once its handler is bound. The input guard
names the one module allowed to write the flag, and the new render files
join the format scope.
2026-11-05 18:29:38 -08:00
Sameh Khamis 468590485b Give the Cesium renderer the contract's listener rules and error channel
Frame-phase and draw-set ready listeners now run in subscription order,
skip one removed during delivery, defer one added during it, and hand a
throw to the renderer's error listeners (or reportError) instead of into
Cesium's render loop. The renderer gains on('error' | 'contextLost' |
'contextRestored'); Cesium cannot restore a lost context, so it never sends
contextRestored. Dispose clears every list. renderer.listeners joins the
Cesium checks.
2026-11-05 18:18:28 -08:00
Sameh Khamis 3e48e453d0 Share the stub's pointer routing as an engine-neutral input router
The stub's lease and press routing moves to src/render/input/router.js,
unchanged, so a real renderer can feed it its own engine's events. The
router also keeps the behaviour holds (telling the renderer when one turns
off or on, unless it lacks it) and ctrlWheelZoom, and dispatch says who an
event went to.
2026-11-05 18:09:38 -08:00
Sameh Khamis 3837e453ca Keep the render governor's holds on the frame port
The governor no longer writes the Cesium scene's render mode itself: each
owner holds the renderer's frame port once, requests go to it, and the
port is the one place the loop's mode is set. The port now also keeps
the loop from redrawing for scene time alone, which the governor did.

The application installs the governor as soon as its renderer exists and
holds the loop continuous ('boot') until its tools are up, where the
governor used to be installed, so boot draws as before. The hidden-tab
suspension goes through the port too, and the application's renderer now
has the whole frame port.
2026-11-05 17:24:34 -08:00
Sameh Khamis 48f06c68c8 Complete the Cesium frame port
The frame port now does everything the contract asks of it on Cesium:
preUpdate says whether the tick draws (Cesium's own render test, read
live until the tick is over), holds are counted per reason, and suspend,
target fps, renderNow and diagnostics are real. Releasing the last hold
asks for one more frame.

whenSettled also waits until Cesium stops redrawing on its own. Each
request or worker task Cesium completes queues a redraw (a terrain
picker build after the camera moved, say), which no loading flag shows,
so it now waits for quiet frames and then a short stretch in which the
loop draws nothing by itself. It is bounded by its timeout, names a
stopped loop, and ends on destroy.

The app renderer still leaves out the members that steer the loop:
the render governor and the page's visibility handling own those
settings until they move onto the port.

The new frame.prediction check compares the preUpdate answer with what
each tick drew.
2026-11-05 17:11:25 -08:00
Sameh Khamis bdc7692292 Complete the Cesium query port
The Cesium renderer now has every QueryPort member: picks and drill
picks, depth and globe picks, rays, projection, occlusion, heights now
and streamed, clamping and ray casts. Capabilities come from what the
scene supports (depth textures for pickPosition, sampleHeight,
finestHeights and rayCast), in both the conformance and app renderers.

- Picks skip unpickable and hidden items, and report each item once. A
  pick under an unpickable item stops at anything that hides the rest.
  drillPick's limit counts after that filtering, so 0 gives none.
- Height queries and ray casts see through sets not drawn with
  includeInHeightQueries, and through the sets a caller excludes, by
  their items' pick ids. The list is built once per change to the sets.
- `tiles` samples and casts run with the globe hidden for the call;
  `terrain` casts use the globe alone. A sample reports whether it came
  from tiles, terrain, the ellipsoid or a set.
- A position clampToSurface cannot read is a miss, not a failed batch.
- Terrain on an ellipsoid is height 0. Before, sampling it threw, for
  want of tile availability.

whenSettled also waits for icon images, and fails when one cannot load.
The conformance checks that pick or query settle first, because a
renderer answers from what it has loaded; the stub settles at once.
The Cesium renderer now passes 14 checks in the browser, and its
visual references are unchanged.
2026-11-05 16:22:48 -08:00
Sameh Khamis 92fd2b0c2e Run the conformance logic suite on the Cesium renderer
The conformance page can now run the logic checks against the Cesium
scene renderer as well as the stub. CESIUM_CHECKS lists the checks of
the ports Cesium already has; each phase 2 migration adds its own, so
the suite shows how far the renderer is through the contract. All eight
listed checks pass in the browser, and the stub still passes its 69.

A harness frame is one tick of the render loop, observed through
frame.on('preUpdate'). It asks for no frame itself, so a check's own
requestRender is what makes a tick draw, and a loop that stops fails
the check after five seconds instead of hanging the run. The harness
frees each WebGL context on destroy.

FrameTime.dt is now measured rather than fixed at 1/60, and renderOnce
honours timeoutMs. A scene renderer made without the models capability
has no draw.models.
2026-11-05 16:04:02 -08:00
Sameh Khamis a5143f4467 Share the Cesium ports between the app and conformance renderers
The frame, draw, camera, query and capture ports move out of the
conformance scene renderer into src/render/cesium/ports.js, unchanged.
The app's transitional renderer, now src/render/cesium/appRenderer.js,
builds the same ports over the full viewer; it still leaves out globe,
whenSettled and setTime, which would misreport photoreal tiles and stop
the app clock.

Disposing the ports now settles a pending renderOnce and detaches the
draw sets, so a model that loads after destroy is destroyed rather than
added to a destroyed collection. A viewer whose ports fail to build is
destroyed with its credits.

Checked in a browser: the app boots with one credits element, and all
eleven conformance scenes match their references exactly.
2026-11-05 15:50:48 -08:00
Sameh Khamis 03f2e2a9b9 Add the ratchet that keeps Cesium inside the renderer
Outside src/render/cesium/, only the 236 files on cesiumAllowList.json
may reach Cesium, and each must still do so, so porting a file removes
its entry. A file reaches Cesium by importing it or the Cesium adapter,
by name or by path, or by being handed the namespace as a parameter,
binding or property. Files added to the list since origin/main fail the
check, once the list is on main. The renderer contract, stub and
conformance suite must stay Cesium-free, and runtime code in a language
the check cannot read fails it.
2026-11-05 15:10:04 -08:00
Sameh Khamis 8d7f82f110 Create the application scene through a renderer plugin
createApplicationScene takes a renderer plugin, defaulting to Cesium,
which builds today's viewer and credits exactly as before. Its renderer
is transitional: it has only the members it really has, with read-only
capabilities, and a test pins the contract members still to come. Code
not yet ported reaches the viewer through cesiumViewerOf, never through
a renderer member. A plugin whose renderer has no viewer is refused and
destroyed, since the application still runs on the viewer. The result
now includes the renderer.

Not yet checked in a browser.
2026-11-05 14:51:42 -08:00
Sameh Khamis bcd24c458b Check the src/geo tests' formatting
The geo, celestial and quaternion tests join the format check, as the
runtime modules beside them already do.
2026-11-05 14:41:35 -08:00
Sameh Khamis 6060c2cee8 Convert voice action positions and distances through src/geo
Voice actions read degrees from ECEF positions with toLonLatH and
measure distances with src/geo, at the sites that only did maths. A
position that converts to NaN now reads as unknown, never as NaN
coordinates. The analyst test gives its subject a real ECEF position
instead of patching Cesium. Sites that keep a Cesium Cartographic, or
use the camera or picks, wait for the renderer ports.
2026-11-05 14:33:32 -08:00
Sameh Khamis bf758a0abb Convert share link angles and positions through src/geo
Share links convert degrees, radians and the flight destination with
src/geo. The hash and the destination are bit for bit the same as
before; the flight itself still goes through the Cesium camera.
2026-11-05 14:28:10 -08:00
Sameh Khamis e32988ae5e Match Cesium's fromDegrees exactly and keep toEcef in the stub
fromRadians divides by gamma as Cesium does, so geodetic to ECEF is now
bit for bit Cesium's, and the test checks it exactly. Its tests also
cover three height bands, points inside the Earth and NaN input. toEcef
reads the renderer contract's Position, so it moves to the stub's maths.
The ellipsoid docs pair each conversion with Cesium's.
2026-11-05 14:28:10 -08:00
Sameh Khamis 52d82b453c Add quaternions to src/geo
Ports Cesium's axis-angle, product, rotation-matrix and heading-pitch-roll
quaternions, with rotations given as frame axes. They match Cesium bit for
bit, including heading-pitch-roll frames on the polar axis. The model
convention is documented: at heading 0 the forward axis points east.
2026-11-05 14:15:13 -08:00
Sameh Khamis fd1c09dbbc Add the sun and moon to src/geo
A port of Cesium's Simon 1994 sun and moon positions, its TEME to
pseudo-fixed rotation and the JulianDate time handling, with epoch
milliseconds as the only input. Results are identical to Cesium's,
tested with exact equality from year -500 to 3000 and across every
leap second. Times outside the model throw a RangeError, where
Cesium's debug build throws.
2026-11-05 14:10:48 -08:00
Sameh Khamis 5652236a27 Read the agent world packet's camera through src/geo
The world packet converts the eye position with toLonLatH instead of
Cesium's Cartographic, so the agent-world-packet group no longer needs
cesium. Degree conversions use precomputed factors, as Cesium.Math does,
so results stay bit for bit the same.
2026-11-05 14:01:57 -08:00
Sameh Khamis 9f165e68f3 Move the stub's geodesy into a renderer-neutral src/geo
WGS84, ECEF and geodetic conversions, surface normals and east-north-up
frames, as plain {x, y, z} objects, exported as ./geo. A test checks
them against Cesium over random points and the poles. The stub's maths
re-exports them and keeps only the camera angle conventions.
2026-11-05 13:49:53 -08:00
Sameh Khamis fefdf242ee Settle imagery, world data and sky capabilities in the renderer contract
- setFrames takes a timed source (URL frames, or video tiles in
  segments) on the scene clock by default or its own; frameReady fires
  once each time the shown frame is drawn.
- drapeOn is 'surface' | 'objects' | 'all' plus lit; imagery.host() and
  onHostChange go, and the drape rule Core derives is written out.
- ImagerySpec.fetchTile answers bytes, missing or defer on every
  renderer; tileTransform, admit and canvas imagery need imageHooks, and
  add() throws for them without it.
- Generic names for the default renderer's world data: worldDataToken,
  the photoreal fallback route, and world terrain and imagery.
- clouds and water capabilities, with globe.setClouds and setWater.
2026-11-05 13:26:14 -08:00
Sameh Khamis e9c0dc5405 Settle leases, styles, credits and controls in the renderer contract
- claimPointer takes a grab filter, asked on each down: a lease takes
  only the presses it grabs, and wheel, pinch and every other press stay
  with the camera and everyone else. Each event of a press goes where its
  down went, per button, so a claim never takes a press under way.
- Core owns the styles: effects.defineStyle passes a GLSL (and optionally
  WGSL) full-screen pass, with the shader interface written out.
- Credits are plain data ({ text, href, logoUrl, inline }) that Core
  renders; CreateOptions.creditContainer goes.
- controls.coast { drag, zoom } in seconds replaces the inertia trio; the
  stub's defaults are the default renderer's, converted.
- The frame order is the default renderer's own: a move made by follow or
  in preUpdate reaches changed on the next frame.
2026-11-05 13:11:59 -08:00
Sameh Khamis 14f06df735 Settle the draw decisions in the renderer contract
- lodGroup links items across sets as one thing at different detail;
  picks name the member shown and report the group. displayRange is
  half-open.
- An orbits capability: the renderer propagates two-line elements with
  SGP4 on the set's motion clock. The stub propagates with satellite.js.
- Height queries see draw sets only with includeInHeightQueries (was
  excludeFromHeightQueries); HeightSample.source gains 'set'.
- courseGlide is a capability; courseBlendMs eases the course on every
  renderer.
- Arc segments climb at climbMps; a negative minDtS stays for the
  aircraft warm-up.
- The star catalogue is optional, a hidden column row keeps its motion,
  and draw.clampState() reports the drape fallback.

Checks cover the climb and warm-up, lodGroup ranges, orbits against SGP4
(with and without motionSegments), hidden rows resuming, and sets not
answering height queries by default.
2026-11-05 12:50:58 -08:00
Sameh Khamis 2fc99e7c19 Define the renderer contract, with a stub renderer and a conformance suite
The renderer contract is what the application asks of a 3D renderer, so a
renderer other than Cesium can draw the same application. It is nine ports
(frame, globe, draw, camera, query, input, effects, credits, quality) plus
the renderer itself, one strict .d.ts per port in src/render/contract/.
PORTS lists every member at run time, and a TypeScript-compiler test fails
when the list and the types disagree on one member. An optional member
exists only with its capability; a required one answers null or empty
without it. Nothing in the application uses the contract yet.

The stub renderer (src/render/stub/) implements every port in memory and
draws nothing. Its camera is a port of the default camera's maths: views,
lookAt, flights with their height arc and easing, rectangle framing on the
geodesic midpoint, the view rectangle with its horizon and pole cases,
changed thresholds, follows, and flights that finish inside the call.

The conformance suite (src/render/conformance/) runs 59 checks against
any renderer through the contract alone, in Node or in a browser page. The
camera checks compare traces recorded from Cesium's own Camera, headless on
a virtual clock (scripts/record-camera-goldens.mjs), to 1 mm, 1e-9 and
1e-3 px. The other checks cover listener rules on every subscription, the
frame loop, draw sets, picks, motion clocks, heights, leases, imagery,
styles, credits and quality knobs. The stub passes all of them but the
model check, which needs a glTF asset.

Reference scenes draw one scene per primitive family, a city and a
calibration path from orbit to the street, from contract calls only and
with no network. /dev/conformance/ draws them on the Cesium scene renderer
(src/render/cesium/sceneRenderer.js), records references through a
dev-server-only, loopback-only endpoint, and compares captures with
pixelmatch's antialias detection plus a solid-pixel count. Measured on
Cesium: every MSAA setting passes, and a dropped 4 px point, a dropped 2 px
line and a 1 px shift of the view fail. visual.js states the limits.

The package exports ./render/contract, ./render/stub and
./render/conformance. Documented in docs/RENDERER-CONTRACT.md,
docs/CODE-BOUNDARIES.md and docs/CURRENT-STATE.md.
2026-11-04 14:21:48 -08:00
Sameh Khamis fb30287898 Draw the last native world labels through the label overlay
The Directions A and B letters and a data pack's media label were the two
Cesium text labels left, kept by an allow-list in noCesiumLabels.test.mjs.
Both now publish entries to the shared world-overlay host, and the
allow-list is gone, so any Cesium label in src is a test failure.

Directions publishes the letters to the directions-markers source; the
markers keep only their points. A clicked endpoint's letter hangs over the
surface that was clicked. One placed without a click stays hidden until its
ground-floor cell resolves, then requests a render, so a still camera shows
it; a cell that settles with no height falls back to the ellipsoid. Each
position is computed once.

A pack resource handle gains label(entry). The application passes its
shared overlay host to SceneDirector through dataPacks.overlayHost; without
one a media pack fails to load rather than drawing a native label. Each
labelled resource takes a source from a pool and returns it on dispose, as
the host never removes a source, so two presentations of one pack never
clear each other's labels and repeat loads add no sources. A label written
after dispose is ignored. The media label is protected, so it always draws
as before.

Live (Denver, keyless globe): a clicked A and a programmatic B both anchor
on their dots over 1.58 km ground. Documented in docs/CURRENT-STATE.md and
docs/DIRECTOR-DATA-PACKS.md.
2026-11-04 12:30:31 -08:00
Sameh Khamis ad8adc85f4 Replace Cesium monkey-patches with hooks and keep internals in one module
The arrival ground guard replaced camera.flyTo, flyToBoundingSphere,
setView, lookAt and cancelFlight with wrappers so a direct caller would stop
it. It now remembers the pose it started on and, before each retry, yields
if a flight is under way or the camera has moved across the ground, down or
around. A straight rise is not a move: Cesium's terrain collision lifts a
buried eye that way as tiles load, which is the case the guard exists for.
This also catches owners the wrappers missed (a pose written directly,
lookAtTransform), and no camera method is replaced.

Recent imagery and weather overwrote provider.requestImage (weather also
provider.credit) to ration tiles. They now state that policy as a hook with
withTileRequestHook(provider, { requestImage, credit }) from
src/render/cesium/tileRequests.js, which hands Cesium a provider delegating
to the original and leaves the original untouched.

Every private Cesium field Core reaches goes through a named helper in
src/render/cesium/internals.js: the main and offscreen pick views and their
command extents, a billboard's collection, a cluster's point collection,
the widget's render-loop latch, the WebGL context and the current camera
flight. internals.test.mjs fails when the installed Cesium stops declaring
one of them, and a source scan fails on private access anywhere else. The
cyber sonar's primitives.update wrap stays until the effects work.

Live (Austin, keyless globe): a buried eye is lifted to 253 m over a 133 m
surface; a setView or flyTo by another caller makes the guard yield; radar
loads its tiles through the hook. Documented in docs/APPLICATION.md and
docs/CURRENT-STATE.md.
2026-11-04 11:15:35 -08:00
Sameh Khamis d51ea4007b Ask for heights through one service per scene
About 20 sites called scene.sampleHeight, globe.getHeight,
scene.clampToHeight or the MostDetailed variants directly, each with its own
idea of a miss (undefined, NaN, a throw). They now ask heightsFor(scene) in
src/queries/heights.js:

- now(point, { against, exclude }) answers from what is loaded, or null;
  against is 'surface' (what is drawn) or 'terrain' (loaded terrain only).
- clampNow(position) moves a position onto the drawn surface, or null.
- batch() and clampBatch() wait for the most detailed tiles: null per
  missed point, a rejection when the whole query fails.
- supported() reports { surface, clamp } without allocating.

Cesium still answers now() synchronously, so nothing changes on screen and
nothing is slower: in the live app 1,000 calls cost the same through the
service as direct (terrain 0.5-0.7 ms, surface 2-6 ms a call, run-to-run
noise larger than the difference). Every site already treated a miss as
"hold the last value or fall back"; a renderer that can only answer from a
cache or asynchronously can now plug in at one place.

- A Bhote Koshi evidence point whose terrain sample misses stays at its
  fallback elevation instead of reading the miss as sea level.
- The mesh floor sampler skips a scene without depth-texture support
  instead of spending its budget on probes that throw.
- The world annotation probe's trace reads hit/missed.
- A source scan forbids direct height calls outside the service.

The service is exported as ./queries/heights and documented in
docs/APPLICATION.md.
2026-11-04 09:24:30 -08:00
Sameh Khamis b51f1bf7c5 Take pointer input through one dispatcher per scene
Every layer and tool bound its own ScreenSpaceEventHandler and picked the
same click again: with the usual layers on, one click ran about 26
scene.pick and 2 drillPick passes (120-215 ms in NYC with 18 layers). Pointer
input now comes in through src/input/dispatcher.js, one handler per scene.
A click is picked lazily and at most once per kind of query, and every
subscriber reads the same ClickEvent (pick, hit, id, ownerId, gesture,
drill, ground). The same click now costs 3 picks and 2 drills, 17-70 ms.

- Ambient onClick subscribers run only while no tool holds the pointer,
  checked once per event; tools subscribe with their lease. Moves and
  releases always reach ambient subscribers, so hover and drags end cleanly.
- Camera input and the viewer's stock select/track clicks are ref-counted
  holds (suspendCamera, suppressStockClicks). The dispatcher owns the
  controller's enableInputs, so a Cesium flight that ends no longer turns
  input back on under a drag or the cockpit.
- The draw tool, imagery box, CCTV gizmo, cockpit, directions and every
  selecting layer use it; bindTrackingClickGesture is gone (the gesture is
  on the event). A source scan forbids new handlers, enableInputs writes and
  stock-click removal outside the dispatcher.
- The sensor-style pick correction transit had now applies to every layer.
- Radio's 8-pick near-miss ring is one 17x17 windowed drill.
- A missed pick passed to pickContact is no longer picked again, and a
  touch tap's contact search shares the click's drill.
- A gizmo drag whose release Cesium never reported ends on the next press
  instead of leaving the camera held.

The dispatcher is exported as ./input with the pointer-lease functions, and
documented in docs/APPLICATION.md.
2026-11-04 08:48:51 -08:00
Sameh Khamis 6035de88e2 Delete the billboard and model mirrors; models become layer-held records
Aircraft draw from GPU icon sets, so the billboard mirror, the billboard
treatment and the aircraft visual-state module had no callers left. 3D models
are now records the layer holds under the contact's id (modelRecord.js):
show and placement are written through showModel/placeModel and read back
from the record; renderer-measured values stay on record.model.

The tracked-model click check only matches when a tracked model exists, so
an id-only pick is never mistaken for a click on the tracked plane.
2026-11-03 22:36:27 -08:00
Sameh Khamis f008120b4c Draw FIRMS detections from a GPU icon set 2026-11-03 22:09:57 -08:00
Sameh Khamis 00d1bee127 Draw CCTV cameras from a GPU icon set 2026-11-03 21:47:16 -08:00
Sameh Khamis 89b07fddfd Draw live AIS vessels from a GPU icon set 2026-11-03 21:16:19 -08:00
Sameh Khamis fe32e47bdf Share the aircraft 3D-model regime between flights and military 2026-11-03 20:55:09 -08:00
Sameh Khamis 9fde0a2a59 Draw military flights from the shared GPU icon set
The flights icon planning and presentation move into
`createAircraftIcons` (src/data/aircraftIconLayer.js), and the motion
regime both layers dead-reckon from into src/data/aircraftMotionRegime.js.
Flights keeps only its grounded-floor pass on top.

Military now draws every contact as a row of that icon set, like
flights: the billboards, the cull positions and the rotation-pass
signature go. Tracking hides the row and untracking re-plans it from
the tracked course. Queries read the drawn row position and judge the
horizon as the shader does. Snapshots that arrive before init or after
destroy are ignored, and per-contact lookups find no contact then
instead of throwing.

With about 120 live military contacts, preRender p95 drops from
0.7-0.8 ms to 0.2 ms on the same views. Render time and frame gaps are
unchanged or slightly lower.
2026-11-03 20:30:43 -08:00
Sameh Khamis 2c4c66c710 Draw civil flights from an icon set moved on the GPU
Every flight is now a row in the layer's icon set. Its motion segment
follows the same regime `_deadReckon` picks (interpolate between fixes,
warm up backward from the oldest, coast forward from the newest), and
one shared `_motionRegime` makes that choice for both. The shader moves
every untracked contact between polls, so the 12 Hz fleet tick no
longer touches each billboard. It now only re-plans expired rows,
floors grounded contacts, advances focus while needed, and runs the 3D
model regime.

Grounded contacts the floor lifts are pinned to a hold at the floored
position. Their nose glides on the GPU. Contacts within 50 km re-floor
every tick, and farther ones take turns about once a second. Low
contacts are judged for the horizon at no lower than 12 m above the
ellipsoid, as before. Below hover speed a contact's nose holds. A row
whose fleet model is drawn keeps its focus updated, because the model
reads it. Untracking re-plans and re-floors the icon from the tracked
course.

On the same views with about 6,700 OpenSky flights, frame gaps at p95
drop from 28-40 ms to 17 ms, and the fleet tick from 16-37 ms to about
2 ms at most.
2026-11-03 19:40:39 -08:00
Sameh Khamis 5f36eb0e0d Add columnar icon sets drawn by an instanced Cesium primitive
Icon rows live in typed-array columns: one packed motion segment (hold,
lerp or arc, with a course glide), style, colour and pick ids per row.
The primitive draws every row in one instanced call, dead-reckons in the
vertex shader, matches the billboard treatment the fleet layers use, and
picks and drill-picks like a billboard. Cyber sonar covers icon sets.
2026-11-03 18:35:12 -08:00
Sameh Khamis 7204d66f0b Keep installation and local GeoJSON pick state off Cesium entities
Installation selection fills and outlines record the installation they
belong to in the layer's installationMarkers store instead of an
installationId expando, and the click handler resolves them from there.
Named-marker points keep their { installationId } pick payload, which is
Core's own object, so sibling layers treat a point click as before; the
marker remembers its record id for visit() instead of reading the point.

The local GeoJSON package tracks the entities each layer loaded in a
per-instance set instead of a __localLayerId tag, and passes the stem
base to registerEntityContext as targetPosition instead of hanging it on
the entity; FIRMS does the same, and voice targeting reads the context
record. QA scripts find a layer's features by their Core context id.
2026-11-03 14:57:31 -08:00
Sameh Khamis c5c0803901 Answer earthquake, CCTV pick and tracked sprite reads from Core
Earthquake analyst records come from the events the layer drew, gated on
its own enabled flag, instead of reading each entity's position and
property bag back; the discs no longer carry the bag. CCTV coverage rays
and projection planes record their camera id in a layer pick store
instead of a cctvCameraId entity property, with the same ownership checks.

The tracked flight, military and local ADS-B billboards record their size
and distance scaling as the entity record's sprite, which the close-range
camera guard and the screen annotations' tracked footprint now read in
place of the billboard's property getters.
2026-11-03 13:32:53 -08:00
Sameh Khamis 66869b09ef Read aircraft model placement from Core's mirror
Flights, military and local ADS-B now write their models' show and world
matrix through setModel in src/data/modelMirror.js, and read them back
through modelState: the fleet and tracked handoffs, the tracked visual and
trail anchors, detection anchors and modelAnchorWorld. A matrix is copied
into Core's record and into the model's own matrix, so the shared scratch
can be passed directly and models still never share one matrix object.

What Cesium measures (ready, computedScale) is still read from the model,
and colour stays live as on billboards, because cyber sonar writes it too.
Test seams mirror the models they install, as they do billboards.
2026-11-03 12:58:50 -08:00
Sameh Khamis 47f7eeb00e Read vessel, CCTV and FIRMS billboard state from Core's mirror
The three layers now add their billboards through addBillboard and write
the mirrored fields through setBillboard, so selection, cards, queries,
focus, view thinning and the FIRMS horizon cull read what was pushed from
Core instead of from Cesium. Vessels and FIRMS state their sprites' own
sizes (32 px ship artwork, the glow canvas at twice the core size), which
the mirror needs. A vessel helper, iconPosition, replaces the repeated
mirror-or-visual position lookups, and the vessel visibility pass writes
show only when it changes, as CCTV and FIRMS already did.

A billboard never given a position now has none in the mirror rather than
the origin.
2026-11-03 11:14:33 -08:00
Sameh Khamis 1cfff5803a Read flight and military billboard state from Core's mirror
Flights and military now add their billboards through addBillboard and
write show, position, scale, rotation, size and distance scaling through
setBillboard, which keeps Core's copy in src/data/billboardMirror.js.
Their reads go to billboardState instead of the Cesium billboard, so the
layers no longer read back what they pushed to the renderer.

A mirrored billboard needs an explicit width and height, because unset
Cesium reports the loaded image's size. Colour is not mirrored yet: the
cyber-sonar theme writes it too and tells the writers apart by the live
value, so it moves with the effects port.
2026-11-03 10:56:08 -08:00
Sameh Khamis a40e5c334c Keep cable pick info and the prewarm flag off Cesium objects
Submarine cables wrote each pick's info both to a per-load WeakMap and to a
__gevTeleGeography expando, always together; one cablePicks store replaces
both and resolves the same objects. The voice view-target prewarm marks its
viewer in a WeakSet instead of a __gevViewTargetPrewarmInstalled property.
2026-11-03 10:01:12 -08:00
Sameh Khamis f86dcc9179 Keep aircraft, installation and viewshed state off Cesium objects
The aircraft layers' model spec key, IR boost and belly offset and the
billboards' large-icon flag move into aircraftVisuals, one store shared by
flights, military and local ADS-B. Installation markers' last drawn height
and selection move into installationMarkers. The CCTV viewshed QA tag is now
the viewshedCameraId record field, which qa-cctv-v2 reads through
entityRecords. Test fixtures drop a placement flag nothing reads.
2026-11-03 09:54:41 -08:00
Sameh Khamis 42b17ffab6 Keep the context id in Core's entity records instead of on picked objects
registerEntityContext stamped __gevContextId on the entities, primitives and
carriers it registered, and selection and pick resolution read it back. It
is now the contextId field of the entity's Core record.
2026-11-03 09:40:15 -08:00
Sameh Khamis 07c8d7a17c Keep ALPR marker state in a layer store instead of on Cesium entities
The ALPR layer's private per-entity fields (record, clamp, canvas and display
anchors, wedge, native appearance, pick position, shown, surface check) move
from gevAlpr* expandos into alprMarkers, a store made by the new
createEntityStore(). The shared entity records are built on the same factory.
2026-11-03 08:24:33 -08:00
Sameh Khamis 95c0070412 Keep tracked-entity state in Core records instead of on Cesium entities
Flights, military, satellites, installations, local ADS-B and ALPR hung
gevTrackedId, gevDisplayPosition, gevVisualPosition, gevSelectionOrigin and
gevLabelModel on the Cesium entities they draw, and the tracked readout,
follow camera, cockpit, share links and awareness read them back. They now
live in src/data/entityRecords.js, a Core-owned record per entity: layers
write entityRecord(entity), readers use readEntityRecord(entity). Nothing
changes on screen. This is the first slice of the renderer plan's state
mirror (P1).

The module is exported as ./data/entity-records so hosted code can read the
same records, and window.__godsEyeView.entityRecords gives the QA scripts
the same access from the page.
2026-11-03 08:09:00 -08:00
Said Turksever ea69bb4ce2 Merge pull request #1435 from Rohilalala/fix/d-military-positionless-rows
fix(military): don't treat adsb.lol's position-less rows as a partial snapshot
2026-11-03 15:44:49 +00:00
Said Turksever 3afbd0c638 Merge branch 'main' into fix/d-military-positionless-rows 2026-11-03 15:28:59 +00:00
Said Turksever 3ad3cc2ab7 Merge pull request #1308 from bilawalsidhu/codex/overpass-admission-review-fixes
fix(overpass): keyless gate behind a reverse proxy, loopback admission headroom, per-phase arrival budgets
2026-11-03 15:27:19 +00:00
Said Turksever 34906c9abb Merge branch 'main' into codex/overpass-admission-review-fixes 2026-11-03 15:07:08 +00:00
Said Turksever e5d15f24d5 Merge pull request #114 from Tom-Neverwinter/prereq-10-osm-overlays
feat: OpenSeaMap / OpenSnowMap overlay layers (#10)
2026-11-03 15:02:24 +00:00