Every detection paint projected, faded and bracketed every contact, even
when nothing it reads had changed since the paint before. Live Flights
moves the fleet every 80 ms, so at 60 fps about seven paints in ten
rebuilt the same paths.
Each paint now compares the view and every contact (same objects, same
coordinates, same value in each field the sweep reads) with what the
last sweep was built from, and on a match strokes the kept paths and
places callouts from the kept candidates. Nothing has to announce a
change. Solve paints, the Cyber sonar sweep, Global Context echoes and
depth-tested contacts are always swept.
Also in the sweep: the camera distance without Cesium's argument
checks, the identity hash worked out once per identity, and each
layer's identity sets looked up once per run of objects.
Co-Authored-By: Claude Fable 5.2 <[email protected]>
- a vehicle drawn as a model keeps its selected card and stays in the detection overlay
- transit and rail models drop the 1.5 m floor lift their icons carry
- vessel models are clipped below the waterline
- models switch to an unlit white look under NVG/FLIR and back
- unknown transit modes keep their icon
- aircraft model specs are cached per set; no key-array copies per frame
Vessels, Transit and Rail turn nearby icons into fleet models at the
aircraft layers' distances. A vessel is drawn at its AIS length and beam
(static dimensions now reach vessel rows), standing at the sea surface; a
transit vehicle or train stands on its road or rail, raised by its type's
elevation, and is painted in its mode or category colour where the model
marks it.
Flights, Military Flights and Local ADS-B draw each aircraft with the fleet
model its ICAO type, class or emitter category selects, scaled to the type's
length, built from the up-front loaded bytes. The eight third-party aircraft
models and their attribution are removed; every shipped model is now the
project's own.
121 GLBs under public/models/fleet (about 2 MB, MIT like the code) with
catalog.json and types.json. src/data/fleetModels.js picks a model by ICAO
type, aircraft class, emitter category, AIS ship type and size, GTFS route
type, transit mode or rail category, and sizes it life size; the set loads
once, up front. Trail anchors are measured from the meshes.
Reviews the 474 strings that 27 pull requests added since the last
review, in all twelve catalogs, and fixes what the review confirmed:
wrong agreement when a placeholder takes a neutral value, plural-less
counts that read "1 sightings", terms that differ from the glossaries,
and typography the language guides set. Also applies most of the
findings earlier reviews left open.
zh-Hant is converted from zh-CN again. The new strings had been
translated separately with Taiwan vocabulary. The zh-Hant guide lists
two more characters OpenCC picks wrongly.
Three tests expected the old German and Spanish wording.
startPageLocale, followPageLocale and switchLocale start, follow and switch
the page's language as the standalone application and the globe picker do,
for an application that offers the language elsewhere and passes
localePicker: false. The standalone application uses them.
Selecting a vessel asks Open Vessel Photos (a free, keyless API) for its
photo by MMSI, once per MMSI. The CONTACTS view shows it under the vessel's
name and MMSI with the photographer, the photo's open licence and a link to
the vessel's page; only images on Open Vessel Photos' own host are shown, and
an answer about another MMSI is no photo. The vessel source exposes the lookup
as getEnrichment({kind: 'photo'}) and fetchOpenVesselPhoto for applications
with their own vessel source.
The activation probe raycast only the photoreal tiles. On a globe map the ray
missed, or ran on through the Earth and met a primitive thousands of
kilometres away, and the monitor plane stood at the camera's full range:
about a kilometre wide and hundreds of metres up. A hit at or beyond the range
now counts as none, and on a globe map the plane also clamps short of the
globe's terrain along the axis, or flat ground at the mount's ground while
that terrain is still loading, whichever is nearer.
The preview settled when the image had loaded, so the panel's <img> took the
src while it was still decoding and then faded in over 180 ms: every camera
click lingered on an empty box. The preview now settles once the frame is
decoded (a rejected decode still shows the loaded frame, and a decode that
never settles shows it after one second), and the fade is gone.
The points-of-interest keys (Q/W/E/R/T while a city is expanded) are a
second bare-key document listener, and it did not look at modifiers
either: Alt+R or a delivered Ctrl+R picked the fourth POI and flew the
camera there. It now leaves Cmd, Ctrl and Alt chords to the browser like
the application shortcuts; Shift still counts as the key. The docs and
the changelog entry say so.
A failed request for the view in between left its error set, and the
early return for the already drawn view never cleared it, so the drawn
features showed with an error state.
The browser shared one partition-index build per entry but ran it under
the first view's signal. A camera move cancels the old view before the
next one starts, so the next view was handed the same build, already
cancelled, and drew nothing (the view layer takes an AbortError for its
own cancellation) until the camera moved again. EPC hit this on its
first view after any quick move. The build now belongs to no view: each
view stops waiting on its own signal, and the build runs on for the next
view and every view after it.
When Remove switched a layer off and then its destroy failed, the
manager published no visibility change, so the selection still listed
the entry as on: the link shared it as on and a reload restored it on
while its row read off. A failed Remove now drops the entry from what is
on and remembered whenever its layer ended up off.
A share's entries joined the library after the stored ones, and the
selection kept its first 64, so a link opened into a full library never
showed what it shared and the next hash write dropped its lib=. Now the
oldest entries the share leaves off make room for it, and their layers
are removed with them.
Browse counted an entry as added only while its layer was registered,
but an entry this instance cannot serve (switched off by the operator,
or its server unreachable) stayed in the selection and held one of the
64 places. A library full of them looked empty and refused every Add.
Such entries now show as added, so they can be removed one by one or
with Remove all.
The live refresh had three faults. Its data age counted from when a
fetch finished, so with the manager ticking every cacheSeconds the next
tick found the data just short of due and data refreshed every other
tick. It switched the layer off and on to redraw, which removed the old
features before the new ones were built, closed an open feature card and,
if the rebuild failed, left the layer on but empty. And the tick the
manager runs right after enable refetched after a failed first load, so
a second failure switched the layer off instead of showing UNAVAILABLE.
The local GeoJSON layer gains replaceFeatures(): it builds the new set
while the old one stays drawn, removes the old source only once the new
one is in the scene, keeps a selected feature that is still in the new
set (same id, or same place and label), and on a failed rebuild puts the
old features, records and selection back and rejects. The library's
refresh uses it, counts age from the request, allows a little slack on
the tick, and does nothing until a first load has succeeded.
The application shortcut listener read the key alone, so copying with
Cmd+C or Ctrl+C toggled the CCTV layer and opened the nearest camera,
Cmd+F opened the layers panel and Cmd+1 to 6 changed the visual style.
Chords are now left to the browser. Shift is not a chord, a held key
still repeats, and Escape dismisses search either way.
The core layer lifecycle imported the whole Data Library manifest
contract only to check the lib- prefix, so the layer-lifecycle package
owned a feature module. The namespace rule (prefix, entry id pattern,
the two id helpers) now lives in src/data/libraryLayerIds.js, which the
lifecycle owns; the manifest contract imports and re-exports it, so
library callers are unchanged.
Imagery outlines fetched Natural Earth's 1:50m countries (about 3 MB)
from the natural-earth-vector repository's master branch on GitHub at
run time: a moving, rate-limited source whose failures silently removed
the outlines. Core already ships the pinned 1:10m countries pack, so
the outlines now read that, once, the first time an outline needs it.
The share encoder wrote only registered entries. Until availability
loaded nothing was registered, so any hash write in the meantime (a
camera move during start-up) deleted lib= from the address bar, and an
entry this instance could not serve (its proxy unreachable) was dropped
for good: copying the link or reloading lost those entries. The URL now
carries what the page opened with until the library starts, and every
entry that is on afterwards, served here or not.
Every restore wrote the selection it applied to browser storage, so
opening a link that switched no library entry on stored every saved
entry as off, and the next bare visit restored them off. Core layer
state never writes a recipient's preferences from a share. The library
now keeps what is remembered as on apart from what a share switches on
for the session: a share's entries still join the library, but only a
local restore and the viewer's own choices change what is remembered.
Remove dropped an entry from the selection and reported success even
when the manager could not disable or destroy its layer, leaving a
registered, possibly visible layer that Browse, share links and saved
state no longer knew. Remove now keeps the entry and reports failure,
so its row and Remove all still reach it and a later Remove can retry.
Add registered and switched on an entry before the selection was cut to
its first 64, so a 65th was dropped from the selection but stayed on:
Browse showed it as not added, share links left it out and Remove all
missed it. Start-up registered a share's entries the same way past the
cap. Add now refuses an entry a full library cannot keep, and start-up
registers only what the selection keeps.
Paging stopped once maxFeatures were read before it noted that the
upstream had more, so a page holding exactly the cap with
exceededTransferLimit set read as the whole dataset. FEMA USA
Structures (2,000 features, one page) never showed PARTIAL in a dense
view. Stopping at the cap or the last page with more upstream now marks
the load truncated.
The browser's partition index kept whether it was complete only to
schedule a rebuild and handed the partitions on without it, so a view
counted unread partitions from an index that might be missing some and
could read as complete, even empty, over an area it had never indexed.
A view over an incomplete index now reads PARTIAL until a rebuild
completes.
Only a failed fetch() marked a footer read as worth retrying. A body cut
off after its headers arrived (a reset, the proxy's 20-second timeout)
was taken for an unparseable file: the partition was left out and the
index still read as complete, so the server cached it without that
partition for the entry's cacheSeconds (24 hours for EPC). Such a read
is now retried like any fetch failure; an oversized body is still final.
Moving from view A to B started B's request; moving back to A before it
finished returned early because A was already drawn, so B's request ran
on and its features replaced A's while the camera was at A. Returning to
the drawn view now cancels the request in between.
A share restore skipped every entry whose settled state already matched
the share, but a layer still switching on for an earlier share reads as
off until it settles. Opening a second share in that window left the
first share's layer to come on anyway. The restore now compares with the
manager's effective visibility (the newest request), so the newer share
supersedes the earlier one.