Compare commits

..
Author SHA1 Message Date
German Khokhlov 3822ee9f00 i18n: keep 並 before 道路, 道歉 and 道理 in zh-Hant
The 並道 → 併道 correction replaced the substring anywhere, so a zh-CN
string where 并 means "and" and 道 starts the next word would turn into
併道路 or 併道歉. It now skips 道路, 道歉 and 道理. The three current
entries (lane merging) convert as before; zh-Hant.json does not change.
2026-11-13 20:07:44 +04:00
German Khokhlov 4c5fbd3223 Changelog fragment for #1699 2026-11-13 18:47:10 +04:00
German Khokhlov 5d28d614e0 i18n: generate zh-Hant from zh-CN
zh-Hant is the zh-CN text in Traditional characters, but contributors
still translated it by hand from `--missing zh-Hant`, and a model asked
for Traditional Chinese writes Taiwan vocabulary (影片, 伺服器, 載入).

`npm run i18n -- --zh-hant` now rewrites zh-Hant.json from zh-CN.json
with OpenCC s2tw (opencc-js 1.4.2, cn -> tw, characters only), then the
fixed list of characters it picks wrongly. The check that `npm test`
runs fails when zh-Hant differs from that conversion, naming the key,
and `--missing zh-Hant` points to the command instead of printing a
template.

The conversion reproduces every one of the 3346 entries on main. 25
keys were out of zh-CN order and move; no value changes.

The converter loads on first use, so a copy of the script without
node_modules (stale-index.test.mjs) still extracts messages.
2026-11-13 18:46:27 +04:00
396 changed files with 2056 additions and 21554 deletions

No files matched your search

+1 -1
View File
@@ -176,7 +176,7 @@ VITE_AIS_LIVE_LABEL_MAX_ROWS=900
AISSTREAM_API_KEY= AISSTREAM_API_KEY=
# Optional AISStream subscription filters. # Optional AISStream subscription filters.
# AISSTREAM_BOUNDING_BOXES=[[[-90,-180],[90,180]]] # AISSTREAM_BOUNDING_BOXES=[[[-90,-180],[90,180]]]
# AISSTREAM_MESSAGE_TYPES=PositionReport,StandardClassBPositionReport,ExtendedClassBPositionReport,ShipStaticData,StaticDataReport,AidsToNavigationReport # AISSTREAM_MESSAGE_TYPES=PositionReport,StandardClassBPositionReport,ExtendedClassBPositionReport,ShipStaticData,StaticDataReport
# Feed watchdog: silence (ms) after which the feed is REPORTED stale; the socket # Feed watchdog: silence (ms) after which the feed is REPORTED stale; the socket
# is recycled at 2.5x that. Default 120000. The watchdog self-arms only for the # is recycled at 2.5x that. Default 120000. The watchdog self-arms only for the
# default worldwide subscription — a narrow filter above can be legitimately # default worldwide subscription — a narrow filter above can be legitimately
+2 -2
View File
@@ -1,13 +1,13 @@
# Data Sources & Attribution # Data Sources & Attribution
God's Eye View's **code** is [MIT](LICENSE)-licensed. **The MIT grant covers the source code only — it does NOT extend to third-party data or visual assets.** Every third-party source keeps its own license and terms. This file documents the live and bundled data sources. The bundled 3D models are the project's own procedural fleet models, MIT-licensed with the code ([`public/models/README.md`](public/models/README.md)). God's Eye View's **code** is [MIT](LICENSE)-licensed. **The MIT grant covers the source code only — it does NOT extend to third-party data or visual assets.** Every third-party source keeps its own license and terms. This file documents the live and bundled data sources; bundled 3D-model provenance is recorded in [`public/models/README.md`](public/models/README.md).
How to read this: How to read this:
- **The non-permissive datasets are carved out, not omitted.** Some bundled data (e.g. TeleGeography, CC BY-NC-SA) isn't MIT-compatible. Rather than hide it, we **bundle it with a clear license carve-out** so the app works out of the box — but it stays under the provider's terms. - **The non-permissive datasets are carved out, not omitted.** Some bundled data (e.g. TeleGeography, CC BY-NC-SA) isn't MIT-compatible. Rather than hide it, we **bundle it with a clear license carve-out** so the app works out of the box — but it stays under the provider's terms.
- **If your use doesn't fit a dataset's license, remove that dataset.** Most importantly: TeleGeography is **NonCommercial** — commercial users must delete it (or license it from TeleGeography). It's one self-contained folder. - **If your use doesn't fit a dataset's license, remove that dataset.** Most importantly: TeleGeography is **NonCommercial** — commercial users must delete it (or license it from TeleGeography). It's one self-contained folder.
- **Attribution is shown in-app** and listed here. Keep it intact. The required Google/Cesium credit renders on the on-globe credit line (bottom-left, `#cesium-credits`), and every per-layer credit below is registered into the expandable **"Data attribution"** lightbox on that line (`src/data/dataCredits.js` → `viewer.creditDisplay.addStaticCredit`). Both stay visible in clean-view and recording modes. - **Attribution is shown in-app** and listed here. Keep it intact. The required Google/Cesium credit renders on the on-globe credit line (bottom-left, `#cesium-credits`), and every per-layer credit below is registered into the expandable **"Data attribution"** lightbox on that line (`src/data/dataCredits.js` → `viewer.creditDisplay.addStaticCredit`). Both stay visible in clean-view and recording modes.
- **The bundled 3D models are the project's own.** Every model under `public/models/` is a procedural fleet model generated by the project maintainers and covered by the repository's MIT license; [`public/models/README.md`](public/models/README.md) describes the set. - **Bundled model attribution lives beside the model files.** [`public/models/README.md`](public/models/README.md) records each shipped model's creator, source, license, and modification status.
--- ---
+4 -3
View File
@@ -56,6 +56,7 @@ In short: the MIT grant does not extend to any third-party data. See
DATA_SOURCES.md for the full per-source license + attribution summary, and DATA_SOURCES.md for the full per-source license + attribution summary, and
remove any dataset whose terms don't fit your use. remove any dataset whose terms don't fit your use.
The 3D models under public/models/ are the project's procedural fleet The 3D models under public/models/ are also third-party works and are NOT
models, generated from code by the project maintainers, and are covered by licensed under MIT. They remain under their individual licenses. See
the MIT license above like the source code. See public/models/README.md. public/models/README.md for the author, source, license, modification notice,
and attribution covering each model file.
+1 -1
View File
@@ -60,7 +60,7 @@ Start with the included data sources, then add your own. Each layer is a separat
- **📡 Contacts:** A 250 km roster of everything near your target — step through live aircraft and drop into any cockpit. - **📡 Contacts:** A 250 km roster of everything near your target — step through live aircraft and drop into any cockpit.
- **🎯 Click-to-track anything:** Camera locks on, draws a fading trail, surfaces full metadata — and a tracked fire or vessel hands you off to the nearest live camera in one click. - **🎯 Click-to-track anything:** Camera locks on, draws a fading trail, surfaces full metadata — and a tracked fire or vessel hands you off to the nearest live camera in one click.
- **🖊️ Voice whiteboard:** Speak annotations onto the world — real boundary polygons, marks, and routes. - **🖊️ Voice whiteboard:** Speak annotations onto the world — real boundary polygons, marks, and routes.
- **🛫 3D fleet:** 122 procedural models drawn life size by type — airliners, business jets, helicopters, drones, ships, buses, trams and trains — and every contact swaps from glyph to 3D model as you close in. - **🛫 3D hangar:** Real per-class aircraft models — 787, ATR-72, Citation, Bell 206, MQ-9 — and a tracked contact swaps from glyph to 3D model as you close in.
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir. - **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir.
- **🟩 Detection overlay:** Screen-space bounding boxes and IDs on everything in view. - **🟩 Detection overlay:** Screen-space bounding boxes and IDs on everything in view.
- **🎖️ Military HUD:** Tactical heads-up display with intelligence-style telemetry. - **🎖️ Military HUD:** Tactical heads-up display with intelligence-style telemetry.
@@ -1,6 +0,0 @@
- When the globe stops drawing after a render error or a lost WebGL context,
an alert dialog says so instead of leaving a frozen or black globe behind
working controls. Retry restarts drawing (Reload after a lost context), and
Reset view reopens the app without the view restored from the address bar.
Cesium's "Rendering has stopped" panel no longer appears in the app
(German, @gekh, #1579).
-12
View File
@@ -1,12 +0,0 @@
- Let an application add its own Cockpit subjects through
`services.cockpitSubjects`. A provider supplies the subject's position,
heading and HUD (in the page's language, via `getLocale` and
`onLocaleChange` from `./i18n`); the camera can follow it without the
ground clamp, anchored exactly on it, and a generic HUD adds source rows, a
banner, a veil (off under reduced motion) and a bottom progress line. The
Rail and Transit layers expose read and hold APIs so a selected train or
vehicle can be ridden, and `./layers/rail/paths` is exported; that export
and the read APIs are experimental. `docs/APPLICATION.md` shows a minimal
provider. GEV itself registers none, and entry to the built-in aircraft,
satellite and vessel views is unchanged; a provider's subject takes entry
when it was selected after them (Denis Groshev, @ZloyS, #1686).
+4
View File
@@ -0,0 +1,4 @@
- Generate the Traditional Chinese catalog instead of translating it:
`npm run i18n -- --zh-hant` writes `zh-Hant` from `zh-CN` with OpenCC `s2tw`
and its character corrections, and `npm test` fails, naming the key, when
`zh-Hant` is edited by hand (German, @gekh, #1699).
-8
View File
@@ -1,8 +0,0 @@
- Draw every 3D model from the project's procedural fleet set: 121 models
(about 2 MB, MIT like the code) chosen per vehicle by type and drawn life
size, replacing the eight third-party aircraft models. Flights, Military
Flights and Local ADS-B pick a model by ICAO type, class and emitter
category; Vessels, Transit and Rail now draw models too, under the same 3D
toggle and distances, a ship at its AIS length and beam and a tram, bus or
train in its mode or category colour. The whole set loads once when a layer
starts (samehkhamis, #1714).
@@ -1,3 +0,0 @@
- Draw a vessel whose feed sends no length at its ship type's typical size, so
a cruise ship or container ship no longer shows as its type's smallest model
(samehkhamis, #1715).
@@ -1,5 +0,0 @@
- Collapse the CCTV panel when the layer is switched off. It stayed open with
an empty frame and a meta line that still described the last camera; the
meta line of a disabled layer now reads the catalog count. Contacts, Space
Missions and Clear selected data layers leave the panel as it is
(asturksever, #1723).
@@ -1,5 +0,0 @@
- Draw airport ground vehicles and ground stations as what they are instead of
the generic plane. An airport vehicle (type `ZVEH`) is a fire truck, pushback
tug, van or car by its callsign, a ground station (`GRND`) is a new 11.8 m
radio mast, and one with no altitude stands on the ground instead of at the
10 km airborne default (samehkhamis, #1729).
@@ -1,4 +0,0 @@
- Draw airport ground vehicles as real shapes instead of stacked boxes: the
car is an operations SUV, the van a high-roof panel van, the fire truck an
airport crash tender with turrets and the pushback a low tug with a glazed
cab, all on the ground at their built size (samehkhamis, #1730).
@@ -1,3 +0,0 @@
- Draw aircraft models in their own mesh colours instead of a 94% flat
white (amber for military) over the model; the tracked and emergency
colours now tint the mesh without hiding its paint (samehkhamis, #1731).
@@ -1,4 +0,0 @@
- An aircraft that reports no ground track (parked or taxiing, from adsb.lol
or a local receiver) now faces its reported true heading instead of north,
so one taxiing south no longer looks like it is moving backwards
(samehkhamis, #1732).
-11
View File
@@ -1,11 +0,0 @@
- Draw aids to navigation as buoys instead of ships: a buoy, beacon, RACON,
light or mark from the vessel feed (or any AIS station numbered 99MIDxxxx)
is now the fleet set's buoy of its IALA mark (cardinal, port or starboard
hand, safe water, isolated danger or special mark), standing still with no
heading, and its icon is a disc rather than a chevron. Its card and readout
show its kind of mark with no speed or heading. AISStream now subscribes to
aid-to-navigation reports and names each by its kind of mark and its full
name; the feed lists every ship ahead of any aid, and `vessels_in_area`
counts ships only. A virtual aid (a mark broadcast where nothing physical
stands) keeps its disc, is never drawn as a buoy, and its card says it is
virtual; vessel sources may mark any record `virtual` (samehkhamis, #1733).
@@ -1,10 +0,0 @@
- Draw transit vehicles as their kind, not one of five: trolleybuses, cable
cars, funiculars, gondolas and monorails are modes of their own, with their
own icon, colour, card word and fleet model. A vehicle record may carry its
route's GTFS `route_type` (`routeType`), which settles its mode before the
feed's route hints and picks the finer model (a coach for 200, a high-speed
set for 101). Prague's trolleybuses and Petřín funicular, and Bucharest,
Cluj and Turin's trams, trolleybuses and metro lines, now read as such from
the route ids in each operator's routes.txt. Le Havre's funicular, which its
routes.txt types as a bus, is a named correction that outranks the route
type (samehkhamis, #1734).
-5
View File
@@ -1,5 +0,0 @@
- Move Visual to the left rail, after Data Layers, Utilities and Authoring, and
lead the right column with Context; an open Context or CCTV panel takes the
column's full height. A switch in each panel header moves that side's panels
over their rail to the screen edge, and the scroll wheel at a screen edge
steps through that side's panels (manjunath22466, samehkhamis, #1735).
@@ -1,9 +0,0 @@
- Fix three time readings on the world timeline. A scrub near now goes live
only within two minutes of now, however long the track: with a layer whose
source covers years, any time in the last few weeks used to snap to live.
Fire cards and fire details count a detection's age from the end of the
24 hours shown, and from now only when live, so a window that ended months
ago reads "new 3h" rather than "new 105d". Weather and satellite cloud
frames stay shown, and the latest reads late, for at least one and a half
of the feed's own frame spacings, so a feed whose frames come an hour apart
no longer goes blank for half of every hour (samehkhamis, #1736).
-7
View File
@@ -1,7 +0,0 @@
- Let a host application decide where live contacts are drawn.
`createApplicationCatalog({ placement })` takes a placement provider whose
`layer(kind)` hands Flights, Military Flights, AIS Vessels, Transit and Rail
a `place(id, wallMs)` (and optionally `observe(id, fix)`, `forget(id)`):
each layer tells it every live fix it accepts and draws a contact where it
places it. Without one, or for a contact it does not place, every layer
moves as before (samehkhamis, #1737).
-122
View File
@@ -230,125 +230,3 @@ starts and switches the language with the `./i18n` entry (`startPageLocale`,
`localePicker: false` to `createApplicationControls` instead of editing the `localePicker: false` to `createApplicationControls` instead of editing the
markup. Node tests that import application modules register the component markup. Node tests that import application modules register the component
loader with `node --import=<core package>/build/register-vue`. loader with `node --import=<core package>/build/register-vue`.
### Cockpit subjects
Core's Cockpit rides a tracked aircraft, the tracked satellite and the
selected AIS vessel. An application adds kinds of its own with
`services.cockpitSubjects` on `createApplicationControls`: a list of
providers, checked once when the controls are built
(`src/cockpitSubjectProviders.js`). A malformed provider, or one with a member
that throws when read (a getter), is left out with a warning and the rest are
kept. A provider's subject takes entry when it was selected after every
built-in subject present (a tracked aircraft or satellite, a selected vessel),
whose own fixed order is unchanged, and needs the provider's `layerId` on; the
provider then holds its subject, hands the camera a frame and the generic HUD
a view model on every update, and releases it on exit. Provider methods are
synchronous and are called on the provider, so a class instance's methods keep
their `this`; one that throws or returns a promise is treated as having
failed. What a method returns may carry getters: one that throws when read
makes a candidate none, a frame malformed (the view ends) and a HUD view keep
the last; a worded member (labels, entry face, look) that throws keeps its
last good words.
```js
import { getLocale, onLocaleChange } from 'gods-eye-view/i18n';
import { pointAtS } from 'gods-eye-view/layers/rail/paths';
const words = {
en: { view: 'Ride view', exit: 'Exit ride view', exitText: 'EXIT RIDE' },
de: {
view: 'Fahrtansicht',
exit: 'Fahrtansicht verlassen',
exitText: 'VERLASSEN',
},
};
/** Ride the Rail layer's selected train; `rail` is its layer instance. */
const ride = {
kind: 'example-ride',
layerId: 'rail',
entryFace: { icon: 'train', label: 'RIDE' },
// A function is read again when the page's language changes.
labels: () => words[getLocale()] ?? words.en,
candidate() {
const train = rail.getSelectedTrain();
return (
train && {
id: train.key,
name: train.label,
selectedAt: train.selectedAtMs,
}
);
},
// Only a train bound to its line can be ridden: read() needs its path.
isEntryAllowed: () => Boolean(rail.getSelectedTrain()?.path),
hold: (id) => rail.setCockpitSubject(id),
read(nowMs, ctx) {
const train = rail.getCockpitSubject();
if (!train?.path) return null; // ends the view
const point = pointAtS(train.path.line, train.path.s);
return {
id: train.key,
latitude: point.lat,
longitude: point.lon,
heightM: train.position.railHeightM, // null stands it on the shared floor
name: train.label,
headingDeg:
train.path.direction < 0 ? point.bearingDeg + 180 : point.bearingDeg,
speedMps: train.motion.reportedSpeedMps ?? train.motion.derivedSpeedMps,
// The layer already moves the train smoothly: sit on it.
anchor: 'exact',
eye: { upM: 3.5 },
};
},
hud: (frame) => ({ title: frame.name }),
release(id, { restore }) {
rail.setCockpitSubject(null);
if (restore) rail.focusTrain(id);
},
};
createApplicationControls({ /* ... */ services: { cockpitSubjects: [ride] } });
```
The `./i18n` entry gives `getLocale()` and `onLocaleChange()`:
Core translates only its own strings, so a provider words its labels, entry
face and HUD for the page's language (labels may be functions, as above; the
HUD is read again on a change). `gods-eye-view/layers/rail/paths` is the
Rail layer's pure path module (`createRailPath`, `pointAtS`, `projectOnPath`)
for a provider placing a train along its line. The frame, HUD view and
candidate fields are documented in `src/cockpitSubjectProviders.js`; the Rail
and Transit read APIs in `src/layers/rail/cockpitSubject.js` and
`src/layers/transit/cockpitSubject.js`. A snapshot read again while nothing in
it changed is the one read before, so `read` may take one on every update; a
train's `path.line` is a frozen copy of its line, for `pointAtS` and
`projectOnPath`. The `layers/rail/paths` export and the
Rail and Transit read APIs (`getSelectedTrain`, `getSelectedVehicle`,
`getCockpitSubject` and the snapshots they return, `path.line` among them)
are experimental and may change without notice.
A candidate's `selectedAt` is a token that changes each time the user selects
the subject, even the same one again, so entry counts it as selected then; a
new `id` under the same token is the same selection (a train's selection
moving to another feed's copy of it). The Rail and Transit snapshots give one,
`selectedAtMs`: when the user last selected the train or vehicle by a click or
`focusTrain`/`focusVehicle`. It is null after a deselection and for one
selected only by a hold, since a hold is not the user's selection.
A frame with `heightM: null` stands on the shared floor, or where it last
stood while that floor is cold. If no height has been known since entry, the
view waits, showing ACQUIRING SURFACE, for at most five seconds
(`COCKPIT_GROUND_WAIT_TIMEOUT_MS`), then ends and releases the subject.
`hold(id)` returns `true` once it holds the subject, or `false` to refuse
entry; any other answer, `undefined` included, refuses entry too, with one
warning. A refused hold, and one that throws or returns a promise, is followed
by `release(id, { restore: false })`, again once a returned promise settles,
so `release` must be harmless for a subject it does not hold and when called
more than once.
Leaving the view does not track again an aircraft that was tracked before
entry, as with the bridge and orbital views: when the user leaves by the exit
control, the map goes back to the ridden subject through
`release(id, { restore: true })`.
+25 -237
View File
@@ -365,25 +365,6 @@ The source factories are exported at `layers/directions/source` and
`layers/recent-imagery/source`. Configuration is applied at construction time; `layers/recent-imagery/source`. Configuration is applied at construction time;
this does not introduce live source replacement. this does not introduce live source replacement.
Application catalog composition also accepts an optional `placement`, a
host-supplied placement provider (`src/data/placementProvider.js`): an object
whose `layer(kind)` returns, for `aircraft`, `military`, `vessels`, `transit`
or `rail`, either null or `{ place(id, wallMs), observe?(id, fix),
forget?(id) }`. While live, each of those layers tells it every fix it accepts
(time, position, the height it draws the fix at, reported speed, course and
ground state) and draws a contact where `place` puts it: position, height
(the layer's own when none is given), course and speed. A null answer, a
provider that throws, a kind it leaves out, or no provider at all keeps the
layer's own motion, unchanged; following the world clock, the layers never
ask it. Transit and rail vehicles a provider places are advanced every frame
in view while their placed position changes; one it holds still lets the
render loop idle and is asked again each second, moving on once its placement
does. A placed transit vehicle is drawn at its placed point, and a placed
train where it is placed rather than on its bound railway line. Live vessels
a provider places move on between refreshes: redrawn in place at most every
250 ms, with a frame asked for each second while the provider places any
drawn vessel, and none once the layer is off.
Application catalog composition accepts an optional `streetLevelProviders` list. Application catalog composition accepts an optional `streetLevelProviders` list.
An explicit list replaces the default Mapillary provider; an empty list removes An explicit list replaces the default Mapillary provider; an empty list removes
all imagery providers. Without that option, Mapillary is registered only when all imagery providers. Without that option, Mapillary is registered only when
@@ -646,36 +627,6 @@ panel that holds its row, and `set_panel_open` reaches it as
`utilities-panel` or by "utilities", "draw", "drawing" or "import". On a phone `utilities-panel` or by "utilities", "draw", "drawing" or "import". On a phone
it sits collapsed in the Layers sheet under Data Layers. it sits collapsed in the Layers sheet under Data Layers.
## Side rails and the screen edge — November 14, 2026
Visual (`#pp-toggles`, the display settings, rail item `palette`) is the last
panel of the left column, after Data Layers, Utilities and Authoring, and
opens from the left rail like them (`PanelLayoutController`, falling back to
the right column in a host without a left stack). Context leads the right
column, followed by CCTV, Street Level and the other right panels. An open
Context or CCTV panel takes the column's full height; the other right panels
keep the content-sized layout and its hysteresis band. With nothing open, a
rail's icons and their names show as two glass pieces side by side; once a
panel opens, the icons stay in place and a highlight slides to the open one.
Each collapsible panel header has an edge switch (`UiPanelEdge.vue`,
`src/ui/panelEdge.js`): it moves that side's panels over their rail to the
screen edge, with the rail as a row of icons above the open panel, and back.
The choice is per side, kept in `localStorage` (`gev.panelEdge`), and applies
on the desktop shell only: phones and Cockpit keep their own layouts and do
not show the switch.
Scrolling the wheel at a screen edge (`src/ui/railEdgeScroll.js`, from the
edge to just past the rail, at most 84 px) steps that side's rail to its next
or previous panel instead of zooming the globe, wrapping round at either end.
One gesture is one step, trackpad coasting included, with at least half a
second between steps; a step that comes too soon is held until then and is
dropped if the pointer leaves the strip. Panels, controls, dialogs and the
world timeline in the strip keep their own wheel. The strip is off on phones
and touch screens, while the rails are hidden (clean view, recording), and it
follows the rails' sides in a right-to-left language. On a phone, Visual is in
the More sheet with the dock's Visual Presets, as before.
## God's Eye View in conversations — October 2, 2026 ## God's Eye View in conversations — October 2, 2026
Tool answers that can be shown in God's Eye View include a view: camera, layers, Tool answers that can be shown in God's Eye View include a view: camera, layers,
@@ -1755,7 +1706,7 @@ Applications supply the existing floor/snap, geoid, picking, sprite, camera,
trail, focus, readout, context, aircraft presentation and render services. trail, focus, readout, context, aircraft presentation and render services.
The factory never constructs a second application registry. Configure a source The factory never constructs a second application registry. Configure a source
before initialization; replacing it while initialized is rejected. Model loads before initialization; replacing it while initialized is rejected. Model loads
use the supplied asset resolver, including the up-front fleet set load. Enrichment receives use the supplied asset resolver, including the preload path. Enrichment receives
an abort signal and cannot update a later lifecycle after destruction. Existing an abort signal and cannot update a later lifecycle after destruction. Existing
camera, terrain floor, trail, selection and measured model-size policies remain. camera, terrain floor, trail, selection and measured model-size policies remain.
@@ -2257,83 +2208,6 @@ the subject (tracking stopped, Satellites off) or tracking another target
ends the view without refocusing. `src/ui/cockpitOrbitalView.test.mjs` ends the view without refocusing. `src/ui/cockpitOrbitalView.test.mjs`
covers entry, camera, zoom, look, HUD and exit. covers entry, camera, zoom, look, HUD and exit.
### Application subjects
An application can add its own first-person subjects beside the aircraft,
vessel and satellite: `services.cockpitSubjects` to `createApplicationControls`,
a list of subject providers (`src/cockpitSubjectProviders.js`, checked once by
the coordinator; a malformed one, or one with a member that throws when read,
is left out with a warning and the rest are kept). A provider names
its layer, an entry face and labels in the page's language, and supplies four
operations: `candidate()` (what entry would ride now), `hold(id)`,
`read(nowMs, ctx)` (a camera frame each update) and `hud(frame, ctx)` (a HUD
view model), with `release(id, {restore})` on exit. Entry needs the provider's
layer on, no Context change in flight and the provider's own
`isEntryAllowed(candidate)`; voice stays aircraft-only and is refused, with
the reason, while a provider's view is in use.
The built-in kinds keep their fixed entry order (a tracked aircraft, then the
tracked satellite, then the selected vessel), so without providers entry is
unchanged. A provider's candidate takes entry only when it was selected after
every built-in subject present, by when the Cockpit first saw it or saw it
selected again (its `selectedAt` changing): a train picked after an aircraft
was tracked takes entry, and an aircraft tracked after it takes it back.
Providers' candidates are ordered most recently selected first, then by
priority; one first seen together with a built-in subject comes after it. The
first in that order decides alone: when its gate is shut there is no entry
rather than the next subject's.
A frame places the eye `eye.upM` above `heightM` (the shared floor where the
frame gives none, or where it last stood while that floor is cold; with
neither, the camera stays where it is and the HUD reads ACQUIRING SURFACE for
at most `COCKPIT_GROUND_WAIT_TIMEOUT_MS`, after which the view ends and the
subject is released: no height stands in for a floor never read), slews toward `headingDeg` at the provider's rate, and may
turn off the floor clamps (`groundClamp: false`, for a subject inside a
structure) or keep the anchor exactly on the subject (`anchor: 'exact'`, for a
layer that already moves it smoothly; a frame with no `speedMps` is always
exact, since an inertial anchor runs on at the frame's speed); `cameraIntervalMs: 0` updates the
camera every rendered frame. A frame that is null, malformed or for another
subject ends the view (`restoreTracking: false`), as an aircraft tracked
meanwhile and the provider's layer turning off do; the exit control asks the
provider to restore its subject. The generic HUD shows the provider's kicker,
title, meta line, speed with its unit and tag, readout cards, always-visible
source rows, banner, route card and Live Signals rows, a veil that darkens the
picture under the HUD (with passing light streaks, which stop under reduced
motion), arrow-key look-around when the provider asks for it, and a progress
line along the bottom edge (`src/cockpitProgress.js`: close ticks merge,
labels never overlap). Core translates none of the provider's text: a host
words it for the page's language (`gods-eye-view/i18n`:
`getLocale`, `onLocaleChange`). Its entry face, labels and look may be
functions, read again when they are shown and on a change of language, as the
HUD view is.
The Rail layer offers a read API for such a view: `getSelectedTrain()` and
`getCockpitSubject()` (a snapshot of the train: where it is along its line,
which way it runs (`pathBinding.js` learns it), its rail height, speed as
reported or derived, position source, feed credit and stops, and
`selectedAtMs`, when the user last selected it by a click or `focusTrain`,
even again; null after a deselection and for a train only a hold selected),
`setCockpitSubject(key)` (its marker, 3D model and card hidden, the selection kept, its
feed held, clicks and Escape left to the view; the held copy stays the one
drawn of its train while held, another feed's copy hidden, and it keeps
moving, still selected, when its category is turned off) and
`focusTrain(key)` (false for a train the layer does not draw); its pure path
module is exported as `gods-eye-view/layers/rail/paths`. That export
and these read APIs are experimental and may change without notice. A snapshot read again while nothing in it changed is
the one read before, and its `path.line` is a frozen copy. Transit
offers the same for a vehicle (`getSelectedVehicle`, `getCockpitSubject`,
`setCockpitSubject`, `focusVehicle`): a held vehicle keeps playing when it is
out of the camera's view, and its sprite, 3D model, card, trail and detection
box are hidden; released, it is out of play until a visibility sweep finds it in
view. Its snapshot's `position.groundHeightM` is the floor where playback draws
it (the drawn marker's height while it is placed, else its segment's two
reports' floors at the playback fraction), not the newest report's, and null while that floor is unknown. Rail's `path.direction` is the
way the drawn train runs: once playback has placed it, a report that runs back
turns it only when playback reaches that report. `docs/APPLICATION.md` (Cockpit subjects) shows a minimal provider. `src/ui/cockpitSubjectView.test.mjs`,
`src/cockpitSubjectProviders.test.mjs`, `src/cockpitProgress.test.mjs`,
`src/layers/rail/cockpitSubject.test.mjs` and the Transit lifecycle tests
cover them.
## Context coordination ## Context coordination
Context controls own Contacts/Space Missions state, entry and exit transactions, Context controls own Contacts/Space Missions state, entry and exit transactions,
@@ -2355,17 +2229,6 @@ shown without a fade once it is decoded (or after one second if its decode never
settles), so a new camera's preview is blank only while its frame loads. Disposal settles), so a new camera's preview is blank only while its frame loads. Disposal
releases listeners, subscriptions, image handlers, summary timers and chip-hide timers. releases listeners, subscriptions, image handlers, summary timers and chip-hide timers.
The panel opens when a camera becomes active and collapses when the layer is
switched off, from its own switch or anywhere else. The panel chrome applies
the collapse, so a panel the desktop accordion only folded has its saved and
shared state closed too. Clear selected data layers and Context isolation
(Contacts, Space Missions) switch the layer off without the user asking: the
panel and its share preference stay as they are, and the layer comes back to an
open panel. Only the switch-off isolation makes counts (`suspendsLayer`, true
while `_clearLayersOutsideContextMode` switches the layer off): the user
switching CCTV off inside Contacts closes the panel as anywhere else. A panel opened by hand while the layer is off stays open, and its
meta line reads the catalog count, not the last active camera.
## UI disposal ## UI disposal
UI disposal releases the CCTV subscription, command-dock observer, legacy drag UI disposal releases the CCTV subscription, command-dock observer, legacy drag
@@ -2557,9 +2420,8 @@ is validated against Cesium 1.138.0 and must be revalidated on upgrades.
`scripts/qa-anime-sky.mjs` checks asset readiness, visible Normal sky and `scripts/qa-anime-sky.mjs` checks asset readiness, visible Normal sky and
Anime band coverage against a Vite dev server. Anime band coverage against a Vite dev server.
`scripts/qa-sky-model-lighting.mjs` compares a lit fleet airliner `scripts/qa-sky-model-lighting.mjs` compares a lit `airplane.glb` against
(`public/models/fleet/aircraft/a320.glb`) against black and white environment black and white environment maps in isolated scenes.
maps in isolated scenes.
## Model atmosphere on Apple Metal ## Model atmosphere on Apple Metal
@@ -2580,53 +2442,6 @@ future driver fix restores the effect with no code change; iOS/iPadOS platform
detection is the backstop for when no probe context can be created. Applied detection is the backstop for when no probe context can be created. Applied
during viewer construction, before any tile builds a draw command. during viewer construction, before any tile builds a draw command.
## Render failure notice
When the globe stops drawing, the scene chrome says so instead of leaving a
black globe behind working controls. `installRenderFailureNotice()`
(`src/ui/renderFailure.js`) listens for Cesium's `renderError` and the canvas'
`webglcontextlost`, logs the error, and publishes a notice that
`RenderFailure.vue` shows as an alert dialog with focus on its first action.
The app's viewer uses `showRenderLoopErrors: false`, so Cesium's dead-end
"Rendering has stopped" panel no longer appears; `applicationViewerOptions()`
keeps it by default for other callers.
- **Retry** restarts the render loop. The notice stays until a frame starts
and ends without an error: Cesium raises `postRender` after a failed frame
too.
- **Reload**, after a lost context, reloads the page; the address keeps the
view.
- **Reset view** opens `appRootUrl()`, without the hash or `/share/<code>`
path; the query, such as `?lang=`, is kept.
Pitfalls:
- An error thrown outside Scene's error handling (a frame listener, `onTick`,
a resize) stops the loop without a `renderError`. The notice wraps the
widget's `render()` and `resize()` and raises it as one, once even with
`rethrowRenderErrors`.
- A throwing listener leaves its Cesium event mid-raise, with listener changes
only queued. The wrapper finishes those raises first (`finishRaise()` over
`frameEvents()`), so a layer turned off or on meanwhile behaves.
- A render error marks the loop stopped while its next frame is still queued.
The wrapper keeps the loop marked running, so a Retry or a shown tab
(`src/app/tools.js`) before that frame starts no second loop.
- After a lost context, later render errors are only logged, so the notice
keeps saying why. The listener neither prevents nor restores the loss.
- When the globe draws again, focus goes back where it was, or to the globe's
canvas, which the map's keys read as the map; the canvas is focusable only
until focus leaves or the globe is pressed. Focus the user moved stays put.
- The notice sits above floating panels such as the Québec camera viewer, and
its buttons are 44 px tall on touch screens.
An inline conversation panel never reloads or navigates its host's page: it
offers Retry after a render error only, and no notice after a lost context,
where the panel runtime's own notice applies (`src/app/globePanelRuntime.js`).
Its timer (`keepPanelRendering()`) draws only while the render loop is on and
logs nothing itself, so each render error is logged once, by the notice. An
error a timer frame throws is caught there: it has already been raised as a
render error.
## Map Source control ownership ## Map Source control ownership
Map Source controls own chip listeners, source-state subscriptions and selection Map Source controls own chip listeners, source-state subscriptions and selection
@@ -3474,8 +3289,7 @@ reads `NASA FIRMS · LIVE · updated …`. A source may instead answer the 24 ho
to a set time, naming its end as `at` (epoch ms): the row then reads to a set time, naming its end as `at` (epoch ms): the row then reads
`NASA FIRMS · 24 h to MM-DD HH:MM UTC`, `getStats()` reports `at`, and `NASA FIRMS · 24 h to MM-DD HH:MM UTC`, `getStats()` reports `at`, and
`get_active_fires` says "in the 24 hours to" that time and reports it as `get_active_fires` says "in the 24 hours to" that time and reports it as
`window_end`, and card and detail ages count back from that time instead of `window_end`. The dev proxy never sets `at`.
now. The dev proxy never sets `at`.
The FIRMS CSV parser rejects impossible acquisition calendar dates before the proxy filters records to the trailing 24 hours, while retaining valid UTC acquisition times. The FIRMS CSV parser rejects impossible acquisition calendar dates before the proxy filters records to the trailing 24 hours, while retaining valid UTC acquisition times.
FIRMS ages round to whole hours before choosing a unit, so a 47.6-hour age reads "2d", not "48h"; with detections filtered to 24 hours, this shows mostly in the `STALE · cached {age}` stats line. FIRMS ages round to whole hours before choosing a unit, so a 47.6-hour age reads "2d", not "48h"; with detections filtered to 24 hours, this shows mostly in the `STALE · cached {age}` stats line.
@@ -3792,22 +3606,20 @@ This is the current runtime/source-of-truth snapshot for the project.
> 24.09 m aft (70 % of the model's rendered envelope, so inside it), 2.22 m > 24.09 m aft (70 % of the model's rendered envelope, so inside it), 2.22 m
> below centre (6 %), and 2e-9 m off the centreline. > below centre (6 %), and 2e-9 m off the centreline.
> - **The tracked trail attaches to REAL HULL, aft and below (2026-08-23):** > - **The tracked trail attaches to REAL HULL, aft and below (2026-08-23):**
> `FLEET_TRAIL_ANCHORS` (`src/data/fleetTrailAnchors.js`, generated by > `MODEL_TRAIL_ANCHOR_NATIVE` (`src/data/modelVisualAnchor.js`) holds, per GLB
> `scripts/measure-fleet-trail-anchors.mjs`) holds, per fleet aircraft and > and in RAW glTF coordinates (see the transform-chain entry below), the point
> helicopter model and in RAW glTF coordinates (see the transform-chain entry > of the hull's CENTRELINE PROFILE (its y = 0 slice) closest to the aft-belly
> below), the point of the hull's CENTRELINE PROFILE (its centreline slice) > AABB corner. It is NOT the corner: a bounding-box corner is empty
> closest to the aft-belly AABB corner. It is NOT the corner: a bounding-box > space, 4.80 m off the nearest triangle on `airplane.glb` and 6.14 m on
> corner is empty space (4.80 m off the nearest triangle on the airliner > `jet.glb`, so the trail ended in mid air beside the aircraft. The head segment
> asset this was first measured on, 6.14 m on the jet), so the trail ended in
> mid air beside the aircraft. The head segment
> is drawn from a point behind the aircraft to this anchor, so the anchor must > is drawn from a point behind the aircraft to this anchor, so the anchor must
> stay aft (83–96 % of each aft extreme) or the segment enters at the tail and > stay aft (83–96 % of each aft extreme) or the segment enters at the tail and
> stops inside the fuselage. `fleetModels.test.mjs` reads the POSITION BUFFERS — > stops inside the fuselage. `modelScale.test.mjs` reads the POSITION BUFFERS —
> real vertices and triangles, because accessor min/max cannot tell a corner > real vertices and triangles, because accessor min/max cannot tell a corner
> from a surface — and re-derives on-hull, aft, lowest-at-its-station, > from a surface — and re-derives on-hull, aft, lowest-at-its-station,
> on-centreline, and the construction itself. Re-measure, never re-guess. > on-centreline, and the construction itself. Re-measure, never re-guess.
> - **The trail anchor rides CESIUM'S transform chain, not a hand-rolled one > - **The trail anchor rides CESIUM'S transform chain, not a hand-rolled one
> (2026-08-23 regression fix):** `FLEET_TRAIL_ANCHORS` stores RAW > (2026-08-23 regression fix):** `MODEL_TRAIL_ANCHOR_NATIVE` stores RAW
> glTF coordinates and `modelAnchorWorld()` assembles > glTF coordinates and `modelAnchorWorld()` assembles
> `modelMatrix × components.transform × axisCorrection` — the same chain > `modelMatrix × components.transform × axisCorrection` — the same chain
> `ModelSceneGraph` renders with, built from Cesium's own exported > `ModelSceneGraph` renders with, built from Cesium's own exported
@@ -3842,7 +3654,7 @@ This is the current runtime/source-of-truth snapshot for the project.
> ENVELOPE, and between them the drawn start slides along the segment so the > ENVELOPE, and between them the drawn start slides along the segment so the
> visible length grows CONTINUOUSLY from zero. Three earlier cuts are recorded > visible length grows CONTINUOUSLY from zero. Three earlier cuts are recorded
> because their shape matters: testing segment LENGTH against the radius HID > because their shape matters: testing segment LENGTH against the radius HID
> real trail (58 m aft of an airliner's 34.41 m envelope gives a 33.98 m > real trail (58 m aft of `airplane.glb`'s 34.41 m envelope gives a 33.98 m
> segment, suppressed, though ~23.6 m of it is open air); CLIPPING at the > segment, suppressed, though ~23.6 m of it is open air); CLIPPING at the
> envelope and keeping only what lay outside stopped a moving trail visibly > envelope and keeping only what lay outside stopped a moving trail visibly
> SHORT of the aircraft, since a bounding sphere encloses a lot of empty space > SHORT of the aircraft, since a bounding sphere encloses a lot of empty space
@@ -5018,9 +4830,8 @@ activeCameraCardEnabled: true })` may publish it through the retained
> - **Flights 3D/cockpit hardening:** the destination-direction cue is an > - **Flights 3D/cockpit hardening:** the destination-direction cue is an
> inline SVG rather than a remotely loaded Material Icons ligature, so a > inline SVG rather than a remotely loaded Material Icons ligature, so a
> blocked or late font can no longer expand the literal word `navigation` > blocked or late font can no longer expand the literal word `navigation`
> across the cockpit. A grounded aircraft stands its fleet model's scaled > across the cockpit. The grounded `airplane.glb` belly offset is calibrated
> clearance (the catalog's `clearance`, measured below the model's origin) > to the current asset's measured Y-up bounds (`0.063` native units).
> above the ground.
> Contact Previous/Next and Live Signals aircraft handoffs now > Contact Previous/Next and Live Signals aircraft handoffs now
> re-seed the first-person camera anchor at the selected flight immediately; > re-seed the first-person camera anchor at the selected flight immediately;
> bounded feed-correction smoothing remains scoped to the same aircraft. > bounded feed-correction smoothing remains scoped to the same aircraft.
@@ -5285,7 +5096,9 @@ Shipped locales: **en** (source and fallback), **ar**, **de**, **es**, **fr**, *
- **Gate.** `npm run i18n` (and `src/i18n/catalog.test.mjs` under `npm test`) - **Gate.** `npm run i18n` (and `src/i18n/catalog.test.mjs` under `npm test`)
extracts every message and fails on missing, empty, unused, extracts every message and fails on missing, empty, unused,
placeholder-mismatched, still-English (plural forms included) or placeholder-mismatched, still-English (plural forms included) or
wrongly-shaped plural entries. wrongly-shaped plural entries. `zh-Hant` is generated from `zh-CN`
(`npm run i18n -- --zh-hant`: OpenCC `s2tw` through `opencc-js`, then a
fixed list of character corrections), and the gate fails when it differs.
- **Not translated by design:** layer ids, share-link and hash parameters, API - **Not translated by design:** layer ids, share-link and hash parameters, API
fields, voice tool schemas, provider names, feed data (callsigns, vessel and fields, voice tool schemas, provider names, feed data (callsigns, vessel and
station names) and attribution. station names) and attribution.
@@ -5918,26 +5731,7 @@ Helsinki, the Netherlands, Norway, South East Queensland, Rio de Janeiro
Ontario — Toronto (TTC surface vehicles; the subway publishes no Ontario — Toronto (TTC surface vehicles; the subway publishes no
positions), MiWay, Hamilton HSR, Durham Region, YRT/Viva, Guelph, positions), MiWay, Hamilton HSR, Durham Region, YRT/Viva, Guelph,
Burlington, Barrie and Kingston. Mode silhouettes Burlington, Barrie and Kingston. Mode silhouettes
identify buses, trolleybuses, trams, cable cars, subways, monorails, trains, identify buses, trams, subways, trains, ferries and unclassified vehicles.
funiculars, gondolas (aerial lifts), ferries and unclassified vehicles.
A vehicle's mode comes from, in order: the feed's fleet-number hint
(`vehicleMode`, which knows a bus standing in on a streetcar route); the feed's
correction for a route its own routes.txt mistypes (`routeCorrections`: Le
Havre's funicular `8-FUNI`, which LiA's routes.txt types as a bus); the GTFS
`route_type` of its route, when its record carries one as `routeType` (basic
0–12 or extended 100–1799, each extended type taken as the basic mode it is a
kind of: 200 coach is a bus, 101 high-speed is rail, 405 is a monorail); the
feed's route-id hint (`routeMode`); and the feed's default (`transitModeFor`,
`src/data/transitFeeds.js`). The GTFS-Realtime decoder carries no route type, so
a record from the bundled proxy is classified by the hints alone. Route hints
read from each operator's routes.txt cover Prague's trolleybus lines and Petřín
funicular, and Bucharest, Cluj-Napoca and Turin's trams, trolleybuses and metro
lines (and Turin's Sassi–Superga rack tramway, typed a funicular in its
routes.txt). Le Havre's funicular is a correction, not read from routes.txt: its
route hint and its correction both say funicular. Vilnius publishes no route ids in its positions, so
its trolleybuses read as buses. Retained history classifies its epochs the same
way, so a trail joins the live vehicle.
Retained transit history uses the same outlier quarantine as live observations and Retained transit history uses the same outlier quarantine as live observations and
preserves the active live bracket. Rejected reports do not publish route or mode preserves the active live bracket. Rejected reports do not publish route or mode
@@ -6003,10 +5797,9 @@ External halos are 1/1.5 px in normal, 1.25/1.25 px in CRT and 2/2 px in sensors
final unpadded display size. Each mode has an outline with a front (a nose, a final unpadded display size. Each mode has an outline with a front (a nose, a
bow or a chamfered cab) for a vehicle with a known course and a symmetric bow or a chamfered cab) for a vehicle with a known course and a symmetric
outline for one without, which would otherwise sit at rotation 0 and point outline for one without, which would otherwise sit at rotation 0 and point
north; a vehicle switches to the pointed glyph when a course arrives. Eleven modes, two sizes, three north; a vehicle switches to the pointed glyph when a course arrives. Six modes, two sizes, three
profiles and the two outlines cap the raster cache at 132 variants. Normal mode colours are bus `#5EF08A`, profiles and the two outlines cap the raster cache at 72 variants. Normal mode colours are bus `#5EF08A`, tram
trolleybus `#B6F04A`, tram `#FFC24A`, cable car `#FF8F4A`, subway `#FF4538`, monorail `#FF6FB8`, rail `#FFC24A`, subway `#FF4538`, rail `#D9A6FF`, ferry `#5FD6FF`, and unknown
`#D9A6FF`, funicular `#E8C98A`, gondola `#7FA8FF`, ferry `#5FD6FF`, and unknown
`#D8DDE5`. Selection keeps the mode colour. Sensors reuse each normal silhouette's exact `#D8DDE5`. Selection keeps the mode colour. Sensors reuse each normal silhouette's exact
outer path, filled opaque white without interior panel lines, with an opaque outer path, filled opaque white without interior panel lines, with an opaque
`#05080C` halo. Only the external padding grows; the unpadded silhouette remains `#05080C` halo. Only the external padding grows; the unpadded silhouette remains
@@ -7499,9 +7292,8 @@ pins and share links already supply complete boxes.
normal four-samples-per-250-ms budget. Retry timestamps more than the maximum normal four-samples-per-250-ms budget. Retry timestamps more than the maximum
30-second backoff ahead of the current clock are ignored. A smaller rollback 30-second backoff ahead of the current clock are ignored. A smaller rollback
can retain a bounded retry delay of up to 30 seconds. can retain a bounded retry delay of up to 30 seconds.
- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Every model is one of the project's procedural fleet models (`public/models/fleet/`, MIT, 122 GLBs, about 2 MB in all; `public/models/README.md`), chosen per aircraft by `aircraftModelSpec` (`src/data/fleetModels.js`): the first of its ICAO type's callsign rules in `types.json` (`aircraft_callsign`) its callsign matches, else its type's entry, else the model of its `classifyAircraft` class for a type the map lacks, else its ADS-B emitter category's, else the default airliner. It is drawn LIFE SIZE: the type's published length over the model's built length (`src/layers/flights/modelSpec.js`, shared by Flights, Military Flights and Local ADS-B), and a grounded aircraft stands the model's scaled clearance above the ground. An airport ground vehicle (feed type `ZVEH`) is a fire truck (`FIRE…`), pushback tug (`PUSH…`, `TUG…`), van (`DEICER…`, `SQUID…`, `SQB…`) or else a car by its callsign, and a ground station (`GRND`) is an 11.8 m radio mast; these models (catalog category `other`) are drawn at their built size, and such a contact with no altitude stands on the ground instead of at the airborne default (`src/layers/flights/records.js`). `classifyAircraft` still picks the 2D icons. The whole set loads once, up front, when a layer that draws it starts (`loadFleetModelSet`, which logs `[Fleet] N/122 models (X MB) loaded in N ms`); models are built from those bytes, never fetched mid-session, and an aircraft stays its billboard until the set has loaded. Every aircraft model draws its mesh's own colours (`aircraftModelBlend`, `src/data/aircraftModelLighting.js`): Cesium's `ColorBlendMode.HIGHLIGHT` with white, in Flights, Military Flights and Local ADS-B alike; the tracked colour (cyan, or amber in Military Flights) and the emergency red multiply into the mesh, so its paint still shows. IR boost draws them flat white (`MIX` at `1.0`). Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit. - Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit.
- Vessels, Transit and Rail draw fleet models under the same DISPLAY rail `3D` toggle, at the same altitude ceiling, caps and add/keep radii as the aircraft (`FLEET_MODEL_POLICY`, `src/layers/flights/policy.js`; `createFleetModelLayer`, `src/data/fleetModelInstances.js`). A vessel's model follows its AIS ship type and is drawn at its reported length and beam (AIS static dimensions A+B and C+D, now carried on vessel rows as `length` and `beam`), standing at the sea surface, heading by true heading then course. An aid to navigation is not a hull: a vessel record whose type names a buoy, beacon, mark, RACON or light, or whose MMSI is an aid-to-navigation station's (`99MIDxxxx`), is `isAidToNavigation` (`src/data/vesselTypes.js`) and is drawn as the set's `buoys` model of the IALA mark its type names (`aidToNavigationModel`, `src/data/fleetModels.js`: cardinal N/E/S/W, port or starboard hand, preferred channel by its side, safe water, isolated danger, special mark; a beacon, light, light vessel, RACON or reference point with no mark of its own is the special mark), else a red can (`DEFAULT_AID_MODEL`), at its built size and facing north whatever heading it reports. A type is an aid's when it is one of the 32 kinds an aid report names or uses a word for one (buoy, beacon, cardinal, safe water, RACON, lighthouse and so on); a vessel that works on aids (`Buoy Tender`, `Buoy Layer`) is not one. Its icon is a bone-white ringed disc (`AID_TO_NAVIGATION_STYLE`, `src/data/vesselLabels.js`) instead of a chevron, never rotated, and its card, selected card and HUD readout (`src/layers/vessels/cards.js`) show its kind alone, whole, in that colour: no speed or heading, since a fixed mark has neither. AISStream subscribes to `AidsToNavigationReport` (message 21) by default, and the server names each report's aid type code (0 to 31) as its kind (`aidToNavigationTypeName`: `20` is `Cardinal Mark N`) rather than reading it as a ship type, and its name runs on into the report's `NameExtension`. A virtual aid (the report's `VirtualAtoN` flag: a mark broadcast where nothing physical stands) carries `virtual: true` on its row, and on its vessel source record (the live-source contract's `virtual`, `src/sources/live/contract.js`, false unless a source says true; any source may set it). It keeps its disc but never gets a model (`vesselModelSpec` returns null for it, and the vessel layer gathers no model item for it), and its card and readout add `VIRTUAL` after its kind. `aisStreamRows` (`server/providers/vessels/ais-store.js`) puts every vessel ahead of every aid, so a capped request (the layer asks for 12,000 rows) never gives up a ship for a buoy. The `vessels_in_area` tool (and the brief built on it) counts ships only: it leaves aids out of its rows, counts, breakdowns and speed stats. A transit vehicle's model follows its route's GTFS `route_type` when its record carries one and that type is a kind of the vehicle's mode (`types.json` `transit.route_types`: 200 a coach, 101 a high-speed set, 106 a regional DMU, 710 a double-decker), else its layer mode (`transit.modes`: bus, trolleybus, tram, cable car, subway, monorail, rail, funicular, gondola or ferry; a vehicle of `unknown` mode keeps its icon); a fleet hint that overrules the route (a bus on a tram route) leaves the type unused. A monorail and a gondola are raised by their `transit.elevation` lifts. A train's model follows its rail category and mode (rail, metro or tram). The bundled GTFS-Realtime proxy carries no route type, so its vehicles draw their mode's model; a source that adds `routeType` reaches the finer ones. Both stand on the road or rail top they are drawn on, without the 1.5 m `GROUND_FLOOR_LIFT_M` their icons carry, and are painted in their mode or category colour where the model's `_TINT` attribute marks it. A vessel model is clipped at its waterline (one clipping plane per model, at the model origin's horizontal plane), so its hull below the water never shows through a plain globe, which Cesium does not depth-test models against. Under the NVG/FLIR styles (the Flights `irBoost` parameter) these models render unlit flat white, as aircraft models do. Each vehicle's icon stays the visual until its model is loaded, ready and placed; a model is picked as its icon is, and a vehicle drawn as its model keeps its selection card and its place in the detection overlay. - **Aircraft models skip Cesium's dynamic environment map (2026-10-24).** Flights, Military Flights and Local ADS-B pass `environmentMapOptions: AIRCRAFT_ENVIRONMENT_MAP_OPTIONS` (`{ enabled: false }`, `src/data/aircraftModelLighting.js`) to every drawn model. Otherwise each `Model` renders the sky around it and reads it back from the GPU (`readPixels`) when it is first placed, and again whenever it moves more than 1 km, the scene's atmosphere settings change, or (under sunlight dynamic lighting) an hour of scene time passes (`DynamicEnvironmentMapManager`, Cesium 1.138). With the class colour supplying 94% of the surface, the models are lit from Cesium's default spherical harmonics instead, with no specular environment map, so the sky's model-lighting cube map no longer reaches aircraft. `src/data/aircraftModelLighting.test.mjs` pins the option at every drawn model call; the two preload calls only warm the glTF cache and are never drawn.
- **Aircraft models skip Cesium's dynamic environment map (2026-10-24).** Flights, Military Flights and Local ADS-B pass `environmentMapOptions: AIRCRAFT_ENVIRONMENT_MAP_OPTIONS` (`{ enabled: false }`, `src/data/aircraftModelLighting.js`) to every drawn model. Otherwise each `Model` renders the sky around it and reads it back from the GPU (`readPixels`) when it is first placed, and again whenever it moves more than 1 km, the scene's atmosphere settings change, or (under sunlight dynamic lighting) an hour of scene time passes (`DynamicEnvironmentMapManager`, Cesium 1.138). The models are lit from Cesium's default spherical harmonics instead, with no specular environment map, so the sky's model-lighting cube map no longer reaches aircraft. `src/data/aircraftModelLighting.test.mjs` pins the option at every drawn model call; every model is built from the fleet set loaded up front, so there is no separate preload.
- The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged. - The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged.
- **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui/aircraftDisplay.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design. - **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui/aircraftDisplay.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design.
- **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed. - **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed.
@@ -8431,11 +8223,7 @@ clock drives all enabled observation rows. Earlier/Later use the sorted union of
non-suspended products' advertised times; each product selects its nearest frame non-suspended products' advertised times; each product selects its nearest frame
at or before the requested UTC time, never a future frame or an interpolation. at or before the requested UTC time, never a future frame or an interpolation.
The maximum gap is 30 minutes for radar, regional infrared and lightning, and The maximum gap is 30 minutes for radar, regional infrared and lightning, and
3 hours for hourly global infrared, but never less than one and a half of the 3 hours for hourly global infrared. A missing eligible frame hides that product
feed's median frame spacing over its latest frames, so a regional feed whose
frames come an hour apart holds each for 90 minutes; the latest frame reads late
after the same floor (20 minutes for radar and regional infrared, 45 for
lightning, 4 hours for global infrared). A missing eligible frame hides that product
and reports the gap; rows show actual displayed UTC time with synced/nearest labels. and reports the gap; rows show actual displayed UTC time with synced/nearest labels.
Manifest rollover preserves the requested history time, even when frames expire. Manifest rollover preserves the requested history time, even when frames expire.
+2
View File
@@ -104,6 +104,8 @@ npm run i18n -- --prune # drop entries whose English no longer exists
Changing the English changes the key: the old translation goes unused and the new text shows as missing. Add the translation to every `src/i18n/locales/<code>.json` (or ask a translator), prune, and run `npm test`; it fails until every language is complete. Changing the English changes the key: the old translation goes unused and the new text shows as missing. Add the translation to every `src/i18n/locales/<code>.json` (or ask a translator), prune, and run `npm test`; it fails until every language is complete.
Do not translate `zh-Hant`: it is generated. After `zh-CN`, run `npm run i18n -- --zh-hant` (see [zh-Hant.md](./translations/zh-Hant.md)).
To see a language, open the app with `?lang=ru`, or use the language picker (the globe button in the bottom-right corner). To see a language, open the app with `?lang=ru`, or use the language picker (the globe button in the bottom-right corner).
An empty translation is reported, and so is one left as the English, plural forms included. When English is right on purpose (a code such as CRT, a product name), list it under the language in `src/i18n/same-as-english.json`. An empty translation is reported, and so is one left as the English, plural forms included. When English is right on purpose (a code such as CRT, a product name), list it under the language in `src/i18n/same-as-english.json`.
+2
View File
@@ -36,6 +36,8 @@ npm run i18n -- --prune # drop entries the code no longer uses
Changing English text changes its key: the old translation becomes unused and the new text shows as missing. Translate it and prune. Changing English text changes its key: the old translation becomes unused and the new text shows as missing. Translate it and prune.
Do not translate `zh-Hant`: it is generated. Translate `zh-CN`, then run `npm run i18n -- --zh-hant` (see [zh-Hant.md](./translations/zh-Hant.md)).
## Fixing a translation ## Fixing a translation
Edit `src/i18n/locales/<code>.json` and run `npm run i18n`. To see your change, open the app with `?lang=<code>`. Edit `src/i18n/locales/<code>.json` and run `npm run i18n`. To see your change, open the app with `?lang=<code>`.
+12 -4
View File
@@ -2,16 +2,24 @@
Catalog: `src/i18n/locales/zh-Hant.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md). Catalog: `src/i18n/locales/zh-Hant.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
`zh-Hant` is the Simplified Chinese catalog (`zh-CN`, credited to abiyeenzo, #914, and EDIT-Aeterna, #979) written in Traditional characters with OpenCC character conversion. Wording and terminology are the same as in `zh-CN`: change a term in [zh-CN.md](./zh-CN.md) and the `zh-CN` catalog first, then convert. Only the script differs. **Do not translate or edit this catalog: it is generated.** `zh-Hant` is the Simplified Chinese catalog (`zh-CN`, credited to abiyeenzo, #914, and EDIT-Aeterna, #979) written in Traditional characters. Wording and terminology are the same as in `zh-CN`; only the script differs. To add or change an entry, edit [zh-CN.md](./zh-CN.md) and the `zh-CN` catalog, then run:
After `s2tw` conversion, fix the characters OpenCC picks wrongly: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道, 發佈 → 發布, 公佈 → 公布, 控制檯 → 控制台, and 周 → 週 where it means a week (1 週/秒). ```sh
npm run i18n -- --zh-hant # rewrite zh-Hant.json from zh-CN.json
```
`npm test` fails when `zh-Hant.json` differs from the conversion of `zh-CN.json`, naming the entry, and `npm run i18n -- --missing zh-Hant` points to this command instead of printing a template.
The command converts with OpenCC `s2tw` ([opencc-js](https://github.com/nk2028/opencc-js), `cn` → `tw`), which changes characters only. Never `s2twp`: it also swaps in Taiwan vocabulary (影片, 伺服器, 載入, 圖示), which this catalog does not use. After the conversion it fixes the characters OpenCC picks wrongly here: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道 (not in 並道路, 並道歉, 並道理), 發佈 → 發布, 公佈 → 公布, 控制檯 → 控制台, and 周 → 週 where it means a week (1 週/秒). The list is `ZH_HANT_CORRECTIONS` in `scripts/i18n.mjs`; when the conversion picks another wrong character, add the pair there and run the command.
An entry `zh-CN` keeps in English on purpose needs the same line under `zh-Hant` in `src/i18n/same-as-english.json`.
## Style ## Style
- Follow [zh-CN.md](./zh-CN.md) for wording, punctuation, spacing, units and the glossary, written in Traditional characters. - Wording, punctuation, spacing, units and the glossary are those of [zh-CN.md](./zh-CN.md), written in Traditional characters.
- Relative-time placeholders may already include 前. In `LIVE · updated {age}`, the caller supplies the complete relative age; do not append another 前. - Relative-time placeholders may already include 前. In `LIVE · updated {age}`, the caller supplies the complete relative age; do not append another 前.
- The picker displays 中文(繁體) / Chinese (Traditional); explicit Hans tags select Simplified Chinese. - The picker displays 中文(繁體) / Chinese (Traditional); explicit Hans tags select Simplified Chinese.
## Plurals ## Plurals
`Intl.PluralRules('zh-Hant')` uses only `other`. Keep the measure words of the `zh-CN` entry: 「{count} 架飛機」、「{count} 艘船」. `Intl.PluralRules('zh-Hant')` uses only `other`. The measure words come from the `zh-CN` entry: 「{count} 架飛機」、「{count} 艘船」.
+1 -1
View File
@@ -26,7 +26,7 @@
with the sources: src/materialSymbolsSubset.test.mjs fails when a source with the sources: src/materialSymbolsSubset.test.mjs fails when a source
names a glyph this list is missing, because a missing glyph does not draw names a glyph this list is missing, because a missing glyph does not draw
a box — the ligature never forms and the element renders the literal word. --> a box — the ligature never forms and the element renders the literal word. -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,align_horizontal_left,align_horizontal_right,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,bug_report,category,chevron_left,chevron_right,close,close_fullscreen,dark_mode,delete,directions_boat,draw,east,eco,egg_alt,flare,flight,handyman,layers,layers_clear,light_mode,local_fire_department,location_on,more_horiz,movie,my_location,navigation,normal,on,open_in_full,palette,partly_cloudy_day,pause,pentagon,pest_control,pets,play_arrow,public,question_mark,radar,radio,raven,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,set_meal,settings,skip_next,skip_previous,streetview,timeline,traffic,tune,umbrella,upload_file,videocam,view_in_ar,visibility,visibility_off,water_drop,zoom_in_map" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,bug_report,category,chevron_left,chevron_right,close,close_fullscreen,dark_mode,delete,directions_boat,draw,east,eco,egg_alt,flare,flight,handyman,layers,layers_clear,light_mode,local_fire_department,location_on,more_horiz,movie,my_location,navigation,normal,on,open_in_full,partly_cloudy_day,pause,pentagon,pest_control,pets,play_arrow,public,question_mark,radar,radio,raven,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,set_meal,settings,skip_next,skip_previous,streetview,timeline,traffic,tune,umbrella,upload_file,videocam,view_in_ar,visibility,visibility_off,water_drop,zoom_in_map" rel="stylesheet" />
</head> </head>
<body> <body>
<div class="gev-view" data-gev-view="scene-chrome"></div> <div class="gev-view" data-gev-view="scene-chrome"></div>
+8
View File
@@ -32,6 +32,7 @@
"@vue/test-utils": "^2.5.1", "@vue/test-utils": "^2.5.1",
"flag-icons": "7.5.0", "flag-icons": "7.5.0",
"happy-dom": "^20.14.6", "happy-dom": "^20.14.6",
"opencc-js": "1.4.2",
"prettier": "3.9.6", "prettier": "3.9.6",
"puppeteer": "^25.16.0", "puppeteer": "^25.16.0",
"sharp": "^0.35.4", "sharp": "^0.35.4",
@@ -2976,6 +2977,13 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/opencc-js": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/opencc-js/-/opencc-js-1.4.2.tgz",
"integrity": "sha512-EdcspgdROiCCZDM+jZR6Pa670xW+EW2QhctPBQ8gRjg6lrx4IHEl+2s+kt5izk/TkxVRgdWuFgaGkaEdIUfdaw==",
"dev": true,
"license": "MIT AND Apache-2.0"
},
"node_modules/pako": { "node_modules/pako": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+1 -1
View File
@@ -85,6 +85,7 @@
"@vue/test-utils": "^2.5.1", "@vue/test-utils": "^2.5.1",
"flag-icons": "7.5.0", "flag-icons": "7.5.0",
"happy-dom": "^20.14.6", "happy-dom": "^20.14.6",
"opencc-js": "1.4.2",
"prettier": "3.9.6", "prettier": "3.9.6",
"puppeteer": "^25.16.0", "puppeteer": "^25.16.0",
"sharp": "^0.35.4", "sharp": "^0.35.4",
@@ -298,7 +299,6 @@
}, },
"./sources/rail-service": "./src/sources/railService.js", "./sources/rail-service": "./src/sources/railService.js",
"./layers/rail/source": "./src/layers/rail/source.js", "./layers/rail/source": "./src/layers/rail/source.js",
"./layers/rail/paths": "./src/data/railPaths.js",
"./sources/map-features": "./src/sources/overpassFeatures.js", "./sources/map-features": "./src/sources/overpassFeatures.js",
"./server/providers/mapillary": { "./server/providers/mapillary": {
"node": "./server/providers/mapillary.js" "node": "./server/providers/mapillary.js"
+17 -36
View File
@@ -1,39 +1,20 @@
# Bundled 3D Models # Bundled 3D Model Attribution
Every 3D model shipped in this directory is one of the project's procedural The model files in this directory are third-party visual assets. They are not
fleet models under [`fleet/`](fleet/): aircraft, helicopters, ships, buoys, covered by the repository's MIT source-code license; each remains available
transit and rail vehicles, and a few other airborne and ground objects. They under the license listed below.
are generated from code by the project maintainers, contain no third-party
artwork, and are covered by the repository's [MIT license](../../LICENSE)
like the source code.
| Path | What it holds | | File | Original work and creator | Source | License | Project modifications |
|---|---| |---|---|---|---|---|
| `fleet/catalog.json` | Every model by name: its file, category, built length, beam and bounds (metres, in Cesium's Z-up model frame: x along the length with the nose toward −X, y across the beam, z up), the clearance below its origin, and its 2D icon class. | | `airplane.glb` | “boeing 747” by [zairiq-123](https://sketchfab.com/zairiq-123) | [Sketchfab model](https://sketchfab.com/3d-models/boeing-747-9b16672038ba48f98e6d80a159044ed9) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Substantially modified and optimized for God's Eye View, including geometry/material simplification and coordinate/orientation preparation. The former 24× runtime calibration is baked into the mesh; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses glTF +Y-up with its nose toward local −X. Geometry remains uncompressed so the aircraft asset does not compete with photogrammetry for Draco worker capacity. |
| `fleet/types.json` | Which model draws which vehicle, and at what length: ICAO aircraft and helicopter type codes, ADS-B emitter categories, AIS ship-type and length ranges, GTFS route types, transit modes and rail categories, with the defaults. | | `jet.glb` | “Private Jet” by [Nick the Name](https://sketchfab.com/Nick_The_Name) | [Sketchfab model](https://sketchfab.com/3d-models/private-jet-cbdd1de6ced9461e950eafaa302cc82b) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Repackaged as a glTF binary for the project's military-flight visualization. Imported hierarchy transforms are baked into the meshes; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses meter scale, glTF +Y-up, and the shared nose −X convention. Existing materials are preserved. |
| `fleet/aircraft/`, `fleet/helicopters/`, `fleet/ships/`, `fleet/buoys/`, `fleet/transit/`, `fleet/other/` | The models, one GLB each (EXT_meshopt_compression and KHR_mesh_quantization, vertex colours, no textures). | | `bell206.glb` | “Bell 206 JetRanger” by [terran4627](https://sketchfab.com/terran4627) | [Sketchfab model](https://sketchfab.com/3d-models/bell-206-jetranger-d2f7ba1d671549d4b26aaf834139a1dd) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
| `c172.glb` | “Cessna 172” by [e737](https://sketchfab.com/e0057537) | [Sketchfab model](https://sketchfab.com/3d-models/cessna-172-64cddaee5aff470682659a8c08525046) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
| `citation2.glb` | “1990 Cessna Citation, Texture Detailed, Exterior” by [BlenderCommunityHead](https://sketchfab.com/aboodgoudagad) | [Sketchfab model](https://sketchfab.com/3d-models/1990-cessna-citation-texture-detailed-exterior-a78839624fe64900a8352cb23462350a) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
| `mq9.glb` | “MQ-9” by [IProZenoN](https://sketchfab.com/IProZenoN) | [Sketchfab model](https://sketchfab.com/3d-models/mq-9-fabe963feb354c5584b51f9c470c3f7e) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
| `b789.glb` | “Boeing 787-9” by [Nobilis 2](https://sketchfab.com/nobilishornet2) | [Sketchfab model](https://sketchfab.com/3d-models/boeing-787-9-b6711e2e698e4e469675c1154a50b7a3) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
| `atr72.glb` | “ATR 72 - 600” by [Oyan3D](https://sketchfab.com/oyan3D) | [Sketchfab model](https://sketchfab.com/3d-models/atr-72-600-1e1a7186f7444d288675262fcee44744) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures removed with dominant material colors baked into PBR factors (flat “abstracted” style), orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. |
How the layers use them: CC BY 4.0 permits sharing and adaptation, including commercial use, provided
appropriate credit is retained, the license is linked, and modifications are
- Each layer draws a vehicle with the model its type selects, scaled to the identified. These credits do not imply endorsement by the original creators.
type's published length (a vessel to its reported AIS length and beam), so
every model is drawn life size. Ground objects, balloons and obstacles in
the `other` category are drawn at the size they are built.
- An aid to navigation (a vessel record whose type names a buoy, beacon, IALA
mark, RACON or light, or whose MMSI is an aid station's `99MIDxxxx`) is
drawn as the `buoys` model of its mark (cardinal north, east, south or west,
port or starboard hand, safe water, isolated danger, special mark), a red
can when its type names no mark, at its built size and with no heading
(`vesselModelSpec`, `aidToNavigationModel`).
- An aircraft model's origin is its axis; it stands that model's scaled
clearance above the ground. A ship's origin is its waterline, and a transit
or rail vehicle's the road or rail top (a monorail or gondola is raised by
its `transit.elevation` entry, for a source that names those modes).
- Transit and rail models carry a `_TINT` vertex attribute that marks where
the route or category colour is painted.
- The whole set loads once, up front, when a layer that draws it starts; no
model is fetched while a session runs.
`src/data/fleetModels.js` reads the set and chooses each vehicle's model;
`src/data/fleetModels.test.mjs` checks that every model `types.json` names is
in the catalog and on disk, and that no GLB exceeds 3 MB.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 396 files, more files were not shown because too many files have changed in this diff. Show more