Merge remote-tracking branch 'upstream/main' into fix/drone-airspace

# Conflicts:
#	src/i18n/locales/es.json
#	src/i18n/locales/ru.json
#	src/i18n/locales/zh-CN.json
This commit is contained in:
Matt Van Horn committed 2026-10-29 06:19:47 -07:00
commit f01393c41d
237 files changed
+10805 -945

No files matched your search

+73 -7
View File
@@ -4,6 +4,24 @@ Docker Compose mounts the checkout with the shared SELinux `:z` label so
containers can read it on enforcing SELinux hosts. The separate image-owned
`node_modules` volume and loopback-only port publishing remain unchanged.
The HUD's AI summary line looks up place names for the view only when the
summary can use them. The page asks `GET /api/openai/hud-summary/status` once;
a server without an OpenAI key answers `{ configured: false }`, and the HUD then
makes no reverse-geocode or Nearby Places request until the page reloads. A
server without the route is asked once, and an unconfigured summary reply
stops the lookups the same way. Each lookup reverse-geocodes at most two
points: the view center alone below 10 km, the center and one viewport point
up to 750 km, and two viewport points up to 3000 km; Nearby Places is still
added below 25 km. A lookup is reused until the view center moves a quarter of
the visible width, the camera's distance to the center doubles or halves, or
the camera crosses one of those heights. The width is measured from the
camera's distance to the view center, so terrain and tilt scale it. A reused
lookup asks again only for what it did not get (a timeout, a failure, an empty
answer); everything else is a cache hit. A small pan over a reused lookup
repaints the last AI reply instead of asking for a new one. A host passing
`cachedOnly` keeps reading every cached sample. Voice context lookups are
unchanged.
The HUD's LAT/LON degrees-minutes-seconds readout rounds the coordinate once, to
hundredths of a second, and then splits it. A value within 0.005" of the next
whole minute carries into the minutes and degrees, so `-122.35` reads
@@ -579,6 +597,11 @@ dateline while preserving both authored endpoint longitudes, and turns heading
and roll the short way for any authored angles.
A seek to a shot's start, as a shot action makes, selects that shot rather than
the end of the previous one.
Same-shot seeking restores declared layer visibility after a manual toggle,
including one still in flight, by reconciling only the changed layers through
the cancellable layer admission path. Refused restoration reports a failed
seek and keeps the last settled position. Undeclared operator layers remain
untouched, and the shot is not rebuilt.
See [camera directions](DIRECTOR-CAMERA.md).
Realtime voice composes separate connection, response/tool, Radio handoff, input
@@ -812,6 +835,11 @@ rendered detection set. The legacy manager retains panel helper compatibility;
registration sealing, restoration order and failed-transition rollback remain
unchanged.
Polled layer refreshes and the HUD's camera readout pause while the page is
hidden. An app embedded inline in a panel keeps both running, because the
panel's host can report the page as hidden while it is on screen. This is the
same rule the render loop follows (`keepPanelRendering` in `app/embed.js`).
Application scene setup owns request services, terrain/floor caches and annotation
lookup state. Controls, layers and voice share those owners, and cancellation
clears caches and removes map-stack listeners. Standalone setup supplies the
@@ -1163,6 +1191,10 @@ Shot selection updates the highlight without replacing the row, preserving
native double-click rename. Replacing rows revokes their old listeners. Disposal stops controls immediately;
late file and failed-action completions cannot update removed presentation.
Director clock listeners, interaction observers and scene release failures no
longer leave the director inconsistent, and a phase error is kept when cleanup
also fails.
## Cockpit component ownership
Cockpit presentation is separated from its camera/controller behavior. Existing
@@ -2273,6 +2305,16 @@ fetching, trailing-24-hour filtering and partial-success caching are unchanged.
> SCOPE toggle + FEATHER slider, hash keys `sc`/`scf`) — it is NOT the
> six zero-intensity style stages anymore (those are disabled; see the
> history note in `_initStages`). Hidden tabs stop the render loop.
> AIS vessels no longer hold the loop while enabled (2026-09-30): vessels move
> only when a poll lands, so the layer requests a frame for every forced pass
> (new data, selection), leaves one trailing frame when the camera moved inside
> the 800 ms visibility cadence, and holds `ais-vessels` only while focus
> emphasis animates or any vessel stays dimmed under a live focus target. The
> trailing frame goes through the layer's injectable runtime clock and is
> cancelled on disable, destroy and reset (`renderGovernor.test.mjs` pins the
> no-self-wake invariant). The 80 ms focus pass also skips hidden sprites whose
> focus state is at rest (`spriteFocusAtRest`), since advancing them changes
> nothing; with a 12k worldwide feed that is most of the fleet.
> The scope's OUTSIDE terminus is **altitude-adaptive** (2026-08-17): 0.94
> at/above **10 Mm**, so
> faint stars survive in the corners of a TRUE full-globe view, fading quickly
@@ -3097,7 +3139,14 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> usable per point, while an uncached absent height still returns 502 rather
> than becoming a fabricated ground value. A position the upstream answers
> with a null ellipsoid is reported as an absent height rather than a refresh
> failure, and is left uncached so a later poll re-asks it. Client geoid
> failure, and is left uncached so a later poll re-asks it. A `Retry-After`
> longer than the 10 s retry budget ends the retries at once. After a 429 that
> outlasts its retries, the proxy pauses upstream calls for the `Retry-After`
> (60 seconds when none, at most 10 minutes). A request whose points are all
> cached, fresh or stale, is still served; a request with any uncached point
> gets 502 and the page uses its geoid fallback for that chunk. Each request
> that was waiting on the upstream when the 429 came back logs one warning;
> requests refused during the pause do not log. Client geoid
> fallbacks wait 60 seconds before retrying and self-heal to Re:Earth on the
> first later successful fetch. Smaller chunks reduce timeout risk; complete
> camera batches still wait for their sequential requests, and unresolved
@@ -3752,7 +3801,7 @@ test:track` 43 tracking invariants · headless QA harnesses under
### Interface localization (October 2026)
Shipped locales: **en** (source and fallback), **es**, **ru** and **zh-CN**. The code lives in
Shipped locales: **en** (source and fallback), **es**, **ru**, **uk**, **zh-CN** and **zh-Hant**. The code lives in
`src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow:
[`docs/TRANSLATORS.md`](./TRANSLATORS.md).
@@ -3763,8 +3812,8 @@ Shipped locales: **en** (source and fallback), **es**, **ru** and **zh-CN**. The
value). English has no catalog; every other locale is
`src/i18n/locales/<code>.json`, and a missing entry falls back to English.
- **Locale choice.** `?lang=` → stored choice (`gev:locale:v1`) →
`navigator.languages` → English. Chinese tags map to `zh-CN`; tags containing
`Hant`, `TW`, `HK` or `MO` are skipped. `?lang=` is not stored, and share links drop
`navigator.languages` → English. Chinese tags default to `zh-CN`; Traditional Chinese tags
select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop
it, so a recipient keeps their own language. The globe picker in the
bottom-right corner (`src/ui/localePicker.js`) stores the choice, drops
`?lang=` (keeps it only when storage is blocked) and reloads. It asks first
@@ -3871,7 +3920,7 @@ anchors, recovery, keyboard and mobile states; emergency repainting is covered
by `scripts/qa-emergency-squawk.mjs`. Both accept `--timeout-ms` for slow boot.
Fire Perimeters uses capped, timed server reads with stale-on-error caching and a per-client limit. Unchanged snapshots retain geometry; link checks cancel on disable or selection change, and the row legend shows reported containment.
Fire Perimeters uses capped, timed server reads with stale-on-error caching and a per-client limit. A stale snapshot is served for at most 24 hours; the layer row then reads STALE, dated by that snapshot's fetch, and `get_fire_perimeters` returns `stale` and `retrieved_at` with "(data may be stale)" in its summary. Unchanged snapshots retain geometry; link checks cancel on disable or selection change, and the row legend shows reported containment.
Prohibited & Restricted Airspace is off by default (share-link token `6`) and sits in
Infrastructure. The proxy pages `TYPE_CODE IN ('P','R')` from the FAA SUA
@@ -3934,6 +3983,8 @@ reports it (`keyAlternatives`), and the panel's KEY REQUIRED, its coverage
line and the layer row's tooltip say to light it. The
viewer sits under the header with EXPAND, FIT/FILL and close above the
image. SINCE is a stepped slider whose readout names the cut-off date.
The viewer caption's bearing wraps after rounding to whole degrees, so a view
facing 359.5° or more reads `0°` rather than `360°`.
MapillaryJS loads on the first photo. Opening one frames it with a single
camera flight through the application's deferred navigation (`begin` /
@@ -5293,7 +5344,13 @@ HTTP refusals and outages remain retryable. Searches share a 12-second total
deadline, with Photon requests capped at six seconds each. Caller/application
cancellation stops retries and late cache writes. Replacing a location search
cancels the previous lookup; disposing its controls cancels the active lookup. Photon uses a soft proximity bias, up to five
candidates, and an unbiased retry for name mismatches. Invalid or wrapped bounds returned by providers
candidates, and an unbiased retry for name mismatches. A "place, region" query
prefers a candidate named exactly for the place whose city, county, state,
country or country code (or USA/UK) contains every word of the region, and a
settlement or administrative area over a POI when both fit. Nearby results that
fail that check are retried unbiased. After the region checks of both passes, a
nearby place whose whole name is the query (one with a comma in it) comes next,
then the unbiased name match. Invalid or wrapped bounds returned by providers
are omitted rather than framing the wrong part of the globe.
A location search that finds nothing, or fails, leaves the field open with the
@@ -5784,7 +5841,11 @@ easier to meet (detection is now on more often), but does not create it.
coverage number. `stats.loading` covers outstanding flow work as well as the
road fetch, so a flow failure landing after the first paint still ends the
shared loading batch as LOAD FAILED. Harnesses must gate on `!stats.error`,
never on `mode === 'live'` alone.
never on `mode === 'live'` alone. When the drawn flow came from the proxy's
cached STALE-BUDGET / STALE-ERROR tiles, `getStats()` reports `stale: true`
and `oldestTileAt`, the row reads STALE with the oldest tile's age, and the
sync chip reads
`STALE · Roads: <source> · Flow: cached TomTom tiles` instead of LIVE.
- Traffic runs in `sim` mode (white dots, hardcoded speeds) unless `TOMTOM_API_KEY`
is configured (env or Keychain `tomtom-api`/`api-key`), which enables `live` mode:
TomTom flow vector tiles via the budget-governed `/api/tomtom` proxy
@@ -6087,6 +6148,11 @@ reception does not interrupt internet radio.
Changes apply to the open receiver without reconnecting.
- **Receiver stats (ADS-B).** CRC-valid messages per second, aircraft heard,
aircraft with a fresh position and the IQ level.
- **Error text.** A receiver error sets two fields (`errorState()` in
`src/sdr/controller.js`): `message` is the UI language for the Radio card,
`errorText` is the English text the Local ADS-B layer reports as its stats
`error` (`localAdsbStatus()`, falling back to `message` when a receiver sets
none); the Layers panel translates it where it shows it.
- **Devices.** An already-authorized receiver opens without the WebUSB
picker. ADS-B prefers a device whose product name matches ADS-B or 1090 (the
1090 MHz channel of a dual-channel board); FM avoids ADS-B/UAT channels.
+2 -2
View File
@@ -1,6 +1,6 @@
# Translating God's Eye View
Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Russian** (`ru`) and **Simplified Chinese** (`zh-CN`).
Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Russian** (`ru`), **Ukrainian** (`uk`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`).
## How it works
@@ -63,4 +63,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth
## Language notes
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Spanish](./translations/es.md), [Russian](./translations/ru.md), [Simplified Chinese](./translations/zh-CN.md). Read it before translating, and add a term there when you settle on one.
Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/<code>.md`: [Spanish](./translations/es.md), [Russian](./translations/ru.md), [Ukrainian](./translations/uk.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one.
+106
View File
@@ -0,0 +1,106 @@
# Ukrainian (`uk`)
Catalog: `src/i18n/locales/uk.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md).
Translation source: meirm (#312).
## Style
- Address the user with polite imperatives: «Натисніть», «Увімкніть», «Спробуйте ще раз».
- Sentence case, not Title Case: "Live Flights" → «Рейси онлайн».
- Ukrainian norms, not Russian calques: «у радіусі», «за посиланням», «надто довгий», «потім», «вигляд» (a camera view: «вигляд згори»), «стеження» (tracking); prepositions alternate у/в and і/й by euphony.
- The apostrophe is always ʼ (U+02BC): обʼєкт, звʼязок, памʼять. Not ' or ’.
- Quotes inside Ukrainian text are «ялинки»; a button or layer name inside a sentence goes in them: Увімкніть «Вуличні знімки».
- In short labels, a standard abbreviation with a period beats a wrapped line: ПОПЕР., ДАЛЬН., КАЛІБР.
- Units: км, м, км/год, м/с, вуз (knots), фт (feet), год, хв, с, МГц, дБ, гПа; in UPPERCASE text КМ, М, КМ/С, ВУЗ, ФТ. MW is МВт in any case. `°` and `%` stay. Decimal comma in coordinates written as text: 28,2710° пн. ш.
- Coordinates: пн. ш. / пд. ш. / сх. д. / зх. д.; the HUD hemisphere letters are Пн, Пд, Сх, Зх.
## Plurals
A `tn()` entry needs four forms. `Intl.PluralRules('uk')` picks:
| Form | Counts | Example |
| ------- | ------------------------ | ---------- |
| `one` | 1, 21, 31, 101… | 1 камера |
| `few` | 2–4, 22–24, 32–34… | 3 камери |
| `many` | 0, 5–20, 25–30, 111–114… | 5 камер |
| `other` | fractions: 1.5, 2.25… | 1,5 камери |
`other` takes the genitive singular: 1,5 кадру, 1,5 літака, 1,5 ключа. When a word next to a number cannot agree in every form, move the number after a colon instead: «поруч: {count}», «відповідей: {responses}». The same trick keeps place names and tool names out of a preposition's case: «звідки: {origin}», «Активно: {name}».
## Glossary
| English | Українська |
| ----------------------------------------------- | ---------------------------------------------------------------- |
| layer / data layer | шар / шар даних |
| feed | потік даних (short: потік) |
| source / data source | джерело / джерело даних |
| LIVE (status) / live data | ОНЛАЙН / у реальному часі (status chip: ДАНІ ОНЛАЙН) |
| SIMULATED / SYNTHETIC | ІМІТАЦІЯ |
| stale / degraded / unavailable | застарів / з перебоями (chip: ПЕРЕБОЇ) / недоступний |
| retry | повтор |
| key (API key) / POWER UP | ключ / ЗАПУСК |
| Provider Settings / provider | Налаштування постачальників / постачальник |
| globe / map / map source / basemap | глобус / карта / джерело карти / підкладка |
| aircraft / flight / military flights | літак (short HUD: борт) / рейс / військові рейси |
| vessel, ship / vessels | судно / судна (gen. pl. суден) |
| satellite | супутник |
| launch (rocket) / space mission | запуск / космічна місія |
| ascent (rocket) / booster FLIGHT {n} | виведення / ПОЛІТ {n} (not рейс) |
| contact / tracking | контакт / стеження |
| installation, site / mapped | обʼєкт / нанесений (обʼєкт, контакт) |
| camera / CCTV camera | камера / камера CCTV |
| earthquake / active fire / fire perimeter | землетрус / активна пожежа / контур пожежі |
| advisory (NHC/CPHC cyclone) | зведення |
| scene / shot / scene recipe | сцена / кадр / рецепт сцени |
| briefing / panel / dock | брифінг / панель / док |
| preset / visual preset | пресет / візуальний пресет |
| Scope, keyhole (circular viewport mask) | приціл |
| overlay (detection overlay) / world overlay | накладка / накладка на глобусі |
| heading / altitude / ground speed | курс / висота / шляхова швидкість |
| map bearing, map heading | азимут карти |
| HDG / BRG / CRS, TRK / FOV / DEST / SPD / RANGE | КУРС / ПЕЛ. / ШК (шляховий кут) / ОГЛЯД / ПРИЗН. / ШВИД / ДАЛЬН. |
| Street View (Google) | Перегляд вулиць (as in Ukrainian Google Maps) |
| Street Level (layer) | Вуличні знімки |
| imagery / recent imagery | знімки / свіжі знімки |
| radar reflectivity / lightning density | радіолокаційна відбивність / щільність блискавок |
| infrared | інфрачервоний (short: ІЧ) |
| receiver / gain / decoder | приймач / підсилення / декодер |
| broadcaster | мовник |
| CONUS, WHOT/BHOT, Ironbow, CRT, NVG, FLIR | без перекладу |
| PREV / NEXT (HUD) / Previous / Next | ПОПЕР. / ДАЛІ / Попередній / Наступний |
| anchor / data pack / bundle (scene) | якір / пакет даних / архів |
| box (Recent Imagery area) | область |
| camera move | переліт |
| SIG / NEWS / LOCAL (briefing tabs) | СИГН. / НОВИНИ / МІСЦ. |
| compass points (N, NNE, NE, …) | Пн, ПнПнСх, ПнСх, … |
| L / R (relative bearing) | Л / П |
| click / tap | клацнути / натиснути |
| session (voice, OAuth) | сеанс |
| sequence (street-level photos) | послідовність |
| search (place search) | пошук місця |
| pitch (CCTV camera pose) | нахил |
| traffic (road traffic layer) | дорожній рух |
| phosphor (CRT) | люмінофор |
| view (map extent) / view centre / in view | видима область (карти) / центр карти / у кадрі |
| select (verb) / selected | вибрати / вибраний (not обрати) |
| ping (cockpit proximity contacts) | контакт |
| merge (turn-by-turn) / on ramp | влитися в потік / вʼїзд (off ramp: зʼїзд) |
| watch (Earth/Orbital Watch) | спостереження |
| timeout | тайм-аут |
| icon | значок (not іконка) |
| run (scene playback) / RUN LOG | відтворення / ЖУРНАЛ ВІДТВОРЕННЯ (запуск is a rocket launch) |
| Preview (voice panel replies) | відповіді |
| Marine (radio tag) / pleasure craft | море (not флот) / прогулянкове судно |
| emergency (ADS-B) / Emergency (radio tag) | аварійний стан / надзвичайні ситуації |
| confidence (FIRMS fire detection) | достовірність (short: достов.) |
| thermal (FLIR-style imaging) | тепловий (not термальний) |
| map stack | набір карт |
| stage (rocket) re-entry / recovery | вхід ступеня в атмосферу / повернення |
| ON / OFF (toggle chips) | УВІМК. / ВИМК. |
| Clean UI / EXIT CLEAN VIEW | чистий інтерфейс / ПОВЕРНУТИ ІНТЕРФЕЙС |
| full globe view | вигляд всього глобуса |
| inputs (cockpit context chip) | потоки (not вхідні дані) |
| downrange (rocket stage) | дальність |
| resolution (spatial) | роздільна здатність (not роздільність) |
| shared follow expired | посилання на стеження застаріло |
+17
View File
@@ -0,0 +1,17 @@
# Traditional Chinese (`zh-Hant`)
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.
After `s2tw` conversion, fix the characters OpenCC picks wrongly: 年曆史 → 年歷史, 下遊 → 下游, 並道 → 併道, 發佈 → 發布, 公佈 → 公布.
## Style
- Follow [zh-CN.md](./zh-CN.md) for wording, punctuation, spacing, units and the glossary, 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 前.
- The picker displays 中文(繁體) / Chinese (Traditional); explicit Hans tags select Simplified Chinese.
## Plurals
`Intl.PluralRules('zh-Hant')` uses only `other`. Keep the measure words of the `zh-CN` entry: 「{count} 架飛機」、「{count} 艘船」.