Commit Graph
526 Commits
Author SHA1 Message Date
Denis Groshev 0fdde159cb Merge branch 'main' into denis/gev-news-layer
# Conflicts:
#	DATA_SOURCES.md
#	docs/CURRENT-STATE.md
#	src/data/local_data/natural_earth/README.md
#	src/i18n/same-as-english.json
#	style.css
2026-11-09 15:35:56 +01:00
Sameh Khamis 8ae56587c3 fix(satellites): a SATCAT failure no longer stops TLE refreshes
Both CelesTrak routes shared one cooldown, and any failure set it: one bad
SATCAT answer made /api/celestrak return 502 "cooling down" for a minute,
failing the Starlink load and stalling every TLE group past its cache.
Throttling (403/429) still holds both routes off, since CelesTrak throttles
the client; any other failure holds off only the route that failed.
2026-11-08 15:16:01 -08:00
Denis Groshev c56f098d07 feat(satellites): file satellites under six classes with a toggle each
The satellites layer now sorts every object into Stations, Imaging,
Telecom, Starlink, Probes or Debris, each with a chip and a live count on
the layer row. Stations and Imaging show by default; the Starlink chip is
the former DENSE catalog switch. Hidden classes keep their points, so a
toggle needs no reload, and they leave the detection overlay and the voice
position lists.

A class comes, strongest evidence first, from CelesTrak SATCAT (object type
and status, served by a new /api/celestrak-satcat/<group> proxy route and
cached for a day), the bundled UCS Satellite Database pack (operator, owner
country and purpose), name patterns for later launches, and the CelesTrak
group. The core catalog also reads the resource, weather, planet, sar and
science groups. Payloads with no evidence of life launched 20 or more years
ago are filed as debris. The UCS pack, its build script and its data credit
are added.
2026-11-08 23:34:55 +01:00
Denis Groshev a3902590cd Merge branch 'main' into denis/gev-news-layer 2026-11-07 23:19:12 +01:00
Said Turksever 084a63ab5f Merge upstream/main into fix/taiwan-highway-cctv-fallback 2026-11-07 17:54:15 +00:00
Said Turksever 6af14f7f13 fix(cctv): leave Iceland to its own pack, credit World and Canada packs
Main now ships an Iceland pack that loads the full live Vegagerðin list
(about 500 views) with its CC BY 4.0 credit. The World catalog's 166
Vegagerðin rows point at the same frames under different ids, so every one
of those cameras would be drawn twice. Drop them from the World export and
its loader policy; World is now 2,230 rows and the default catalog limit
7,350 (4,000 shared slots plus 3,350 reserved).

Register Data attribution entries for Ottawa, Québec, TripCheck and MLIT,
as every other CCTV pack has. MLIT's terms ask for the source in the form
"出典:国土交通省 川の防災情報" with the page link, and ODOT asks for
"Camera courtesy of ODOT"; TripCheck partner cameras keep their owner's
name in the label.

The Houston guard test from main checked the old 4,000 default ceiling;
check the 4,000 shared slots instead, like the other pack tests.
2026-11-07 17:53:55 +00:00
Said Turksever cc3d390911 Merge upstream/main into fix/taiwan-highway-cctv-fallback 2026-11-07 17:46:53 +00:00
Said Turksever b99c2f1ade Merge remote-tracking branch 'upstream/main' into pr1619-fix2 2026-11-07 17:43:20 +00:00
Said Turksever 39d70fa1c0 fix(cctv): keep Icelandic labels intact and treat 'og' facings as ambiguous
- Label repeat-collapse uses letter lookarounds instead of ASCII-only \b, so
  'Þverá Árnessýslu' is no longer joined and 'séð séð' collapses.
- 'til austurs og vesturs' and 'í norður og suður' name two facings and keep
  the low-confidence pose.
- Fallback name uses the frame stem when the station number is missing.
- Tests: label and heading regressions, dedupe keeps the last row, cap clamp,
  high-confidence pose, frame redirect pin; file added to format scope.
- Credit names the dataset, links the terms and states no endorsement.
2026-11-07 14:38:52 +00:00
Said Turksever 8455c4bce1 Merge branch 'main' into feat/cctv-iceland 2026-11-07 14:38:51 +00:00
NeoXrated23 891eb56091 feat(cctv): add optional Giroc camera and support larger HLS segments 2026-11-07 11:46:48 +02:00
daika 021e887f94 feat(cctv): add Iceland (Vegagerðin / IRCA) as a keyless CCTV source pack
One JSON list from gagnaveita.vegagerdin.is (about 500 camera views at roughly
170 road stations). Frames are JPEG stills in a single directory on
www.vegagerdin.is; only links inside that directory are registered, and the
media proxy pins the same directory.

The list decides ids, coordinates and which frame path is proxied, so it is
fetched over https with redirects refused and the body read capped at 2 MiB;
every rejection path releases the response body. Camera ids come from the frame
file name, which is unique per view in the live feed, and repeated frames
collapse before the cap.

The feed's view description (`Skyring`) is the label. An explicit Icelandic
facing ("til vesturs", "í suður", "séð austur") sets a high-confidence heading
(264 of 499 views); a view that states none, or two different ones, keeps the
id-hash low-confidence pose. A bare road direction ("Miklabraut austur") is not
read as a facing.

IRCA publishes road camera images under CC BY 4.0 and asks that the licensor is
acknowledged without implying endorsement; the attribution is in the Data
attribution popover and DATA_SOURCES.md, and every camera carries the licence
string. Default cap is 250 with anchors at Reykjavík and four regional centres so
a cap keeps every region. CCTV_ICELAND_ENABLED=0 turns the pack off.

Verified against the live feed through fetchCctvImageFromUpstream: 499 of 499
frames returned a JPEG, all distinct, all under 20 minutes old.
2026-11-07 12:57:08 +07:00
Sameh Khamis 5e42a83d88 fix(chat): send OpenAI chat tool schemas Chat Completions accepts
OpenAI refused every typed chat request with 400 invalid_function_parameters:
resolve_area's area-id pattern uses Unicode property classes (\p{L}), which
Chat Completions does not accept as a regex. OpenAI chat now leaves such
patterns out of the tool schemas it sends; each action still checks its
arguments against its own contract, and realtime voice keeps the patterns.
2026-11-06 21:51:46 -08:00
Denis Groshev 2e87dd87eb Merge branch 'main' into denis/gev-news-layer
Wildlife Sightings (#1591) published share token 9, so News takes the
next free token, 00. The token tests read the ledger before both where
they were written against it, and the README counts both layers.
2026-11-06 12:33:13 +01:00
Denis Groshev 61424e4256 fix(news): keep a refused key refused during a slow read, and read Local when Conflicts fails
A slow re-read of a refused key read as unavailable until it landed; it
now stays refused. The first-read wait is 10 s, for a margin under the
browser's 15 s. A failed Conflicts read no longer stops Local's refresh,
which the server reads on its own. Tests cover GDELT's wait, the refused
key and a caller abort past the timeout, and fail rather than hang; the
docs say what a section serves meanwhile.
2026-11-06 12:02:58 +01:00
Denis Groshev c6ae4c44d7 fix(news): answer within the browser's wait on a first read
A country's first World News API read, or Liveuamap's, could take up to
20 s while the browser waits 15, so the panel showed the browser's own
"signal timed out". A read with nothing to serve yet is now waited for
at most 12 s, as GDELT's already was: past that the section answers
unavailable while the read goes on, and the next refresh is served from
it. A client timeout reads "News feed unavailable".
2026-11-06 11:41:37 +01:00
Denis Groshev 525543d08a test(news): take Jordan as the sample country 2026-11-06 10:18:26 +01:00
Sameh Khamis d807c3007e Merge branch 'main' into pr/wildlife-sightings 2026-11-05 17:15:10 -08:00
Sameh Khamis 198c2faf4f Say the -45° tilt default in the pitchDeg description again
The trim that fitted the tilt wording under the Realtime prompt ceiling dropped the -45° default from pitchDeg, so the model no longer read why a bare tilt goes oblique. pitchDeg reads 'Absolute; -45 default, -89 top-down.' again, and the direction description becomes 'Pan/rotate/tilt steps; orbit/rotate_globe default right.' to pay for it: the Overpass prompt measures 55,499, 1 under the 55,500 ceiling. The schema pin and the measured-size note follow.
2026-11-05 16:14:27 -08:00
Sameh Khamis 82138478d0 fix(wildlife): treat a valid empty Acartia snapshot as a fresh success
A quiet or fully filtered feed raised invalid_snapshot, so the proxy
answered 502 with a 15 s cooldown and re-polled about four times a
minute. Only a non-array payload is malformed now; an empty projection
caches for the normal ten minutes and clears earlier rows.
2026-11-05 16:03:52 -08:00
Sameh Khamis bd99000433 Merge remote-tracking branch 'origin/main' into pr/wildlife-sightings 2026-11-05 16:00:37 -08:00
Denis Groshev 427463cc45 fix(news): list Local newest first
Local ranked its reports by severity first. A country's own news now
carries no severity, so in a conflict area GDELT's reports (severity 1
and up) could crowd every one of its stories out of the 40 listed.
Local is now newest first; severity still shows by colour.

Also: readTitle reads Conflicts headlines only, the economy category's
comment and the attribution popover name World News API's category,
and KNOWN-ISSUES names Core's ids for World News API's categories.
2026-11-06 00:45:33 +01:00
Denis Groshev 82057cbb51 fix(news): keep World News API's own category for a country's news
The bundled rules read fighting and crises; in everyday headlines their
words mislead ("Kazakhstan Advances in Global Digital Rankings" read as
ground fighting). A country's own stories in Local now keep World News
API's category (politics, business, else other) and carry no severity.
Fighting in the area still shows through the Conflicts reports placed
there.
2026-11-06 00:45:33 +01:00
Denis Groshev 1e78ac931c Blame a GDELT export only while GDELT answers, and fix the review's notes
An export is given up on (never published, unreadable, or failing three
times) only while GDELT itself answers: its list of exports read, or
another export of the same round read. An outage that answers 404, or junk,
for everything now gives up on nothing, and a failing file earns one strike
a round rather than three within the first seconds' rounds.

Docs: News's share token is 9; territories without a country code are named
as themselves too; the changelog fragment credits one pull request. Unused
exports dropped from the rules and stale scaffolding from the ledger tests.
2026-11-06 00:45:33 +01:00
Denis Groshev 71adf0d8cd Let a host turn Core's own news reading off
A host that serves news only from its own news server mounts the proxy with
`builtIn: false`: without GEV_NEWS_ORIGIN the routes then answer "not
configured", as before Core read any news itself.
2026-11-06 00:45:32 +01:00
Denis Groshev 34c75ff5de Serve the News layer without a news server: GDELT, Liveuamap and World News API
With GEV_NEWS_ORIGIN unset, Core answers the News layer's routes itself.

Conflicts need no key: the last 24 h of GDELT 2.0 event exports, physical
violence only (CAMEO 18-20), one report per story (copies of one agency
story merged), placed where GDELT put it, a town the article's own words
name winning over a bare country, and titled by the article's page title
from the GKG export of the same quarter hour (the newest 3 h), else by its
address words. GDELT's actor coding is never shown. Exports are read up to
the newest GDELT lists; one that cannot be read is given up on without
holding the rest back, and an outage of two hours answers 502 rather than
an empty feed. With LIVEUAMAP_API_KEY, Liveuamap's mapped items join them.

Local reads World News API by the source country at the view centre
(WORLDNEWSAPI_KEY, at most WORLDNEWSAPI_DAILY_READS a day), with the
Conflicts reports placed inside the area. Disputed areas (11 from Natural
Earth, bundled) and territories without a country code are named as
themselves and read from no country's outlets; a country's outline leaves
them out.

Categories, relevance and severity come from shared rules; keyed answers
are cached and served while they refresh, and no key or address is logged.
2026-11-06 00:45:31 +01:00
Denis Groshev e95ae7b80c Keep the news layer inside its day and its memory bounds
From review of the news layer:
- An event's other sides older than 24 hours (or undated) are no longer
  listed, and a report dated more than 15 minutes ahead is outside the
  window, as in the regional brief.
- Picture cards are one canvas per event in a bounded cache (24), repainted
  in place when the age changes, with each card's picture dropped with it;
  the age in the key had made a new canvas every minute.
- The feed list drops reports that leave the window and is capped at 600.
- "In view" is filtered from the reports the layer already holds; it reads
  the feed only with minor news on, once per refresh, never per pan.
- Infinite scroll restarts after a filter change or a failed read.
- The two filter selects stack, so "Significant and above" is not cut off.
- The timeout tests hold the event loop until aborted, so they pass on
  Node 22 as well.
2026-11-06 00:45:25 +01:00
Denis Groshev 5bb489d1fb Put news on the globe, with a feed panel in the right rail
Events ▸ News draws the last 24 hours of reports from curated hotspots
(Ukraine–Russia, Israel–Gaza–Lebanon, Iran–Hormuz, Yemen–Red Sea, Sudan,
Sahel) and developing situations elsewhere, from any server that answers the
`/api/news` read routes.

- Globe: one marker per merged event, clamped to the ground and culled at the
  horizon; one beacon per hotspot when zoomed out and a pulsing beacon for a
  developing situation; hotspot outlines with disputed areas dotted and
  labelled by who administers and who claims them; picture cards for at most
  ten major or fast-growing events with two or more sources, never
  overlapping, switchable off.
- Panel: hotspot chips, a severity filter (significant and above by default),
  categories, minor news off by default, and a scrolling list of event cards
  with picture, headline, place, sources, age and the sides that reported it.
  "In view" lists what is near the map below 3,000 km once the camera
  settles. A card flies the globe to its event and opens it with a link to
  the publisher. Reduced motion stops the pulse and the flights.
- Core serves no news itself: the local server proxies GEV_NEWS_ORIGIN when it
  is set, and answers 503 unavailable otherwise, so the layer reads "not
  configured" once instead of retrying.
- Share token 9 (enabled-only); voice stays off for this layer.
2026-11-06 00:45:24 +01:00
Denis Groshev dd83812a21 Hold Cockpit regional news to the last 24 hours, and tell its states apart
Regional News showed "REGIONAL NEWS UNAVAILABLE" above populated rows and a
static "RECENT" footer over months-old articles. The status banner kept
`display: grid` when hidden, every non-empty answer carried the unavailable
text, and nothing limited or sorted articles by age.

- One window (regionalNewsWindow.js, 24 h): the local proxy asks Google News
  with `when:1d` and GDELT for 24 h, drops undated, older or future-dated
  articles, sorts newest first, and reports newsWindowHours/newsFetchedAt.
- The Cockpit labels four states: fresh (banner hidden, "LAST 24 H"),
  kept after a failed refresh ("CACHED · REFRESH FAILED"), no news in the
  window, and unavailable. The footer lives in the news state, so it
  survives page and tab changes, and the client drops out-of-window rows.
- `.cockpit-brief-status[hidden]` now hides the banner.
2026-11-06 00:45:19 +01:00
Sameh Khamis 395eba4548 Fit the tilt wording under the Realtime prompt ceiling
With main merged, the Overpass prompt measured 55,549 against the 55,500 ceiling. The pitchDeg and direction descriptions now read 'Absolute; -89 top-down.' and 'Needed for pan/rotate/tilt steps', bringing it to 55,497. The schema pin and the measured-size note follow.
2026-11-05 15:38:32 -08:00
Sameh Khamis 8687512fc8 Merge branch 'main' into manjunath/hal-695-voice-tilt
Re-derive the Realtime schema pin for main's control_cockpit wording and this branch's pitchDeg.
2026-11-05 15:37:50 -08:00
Yehe LiuandClaude Opus 5.5 14f88b3e08 feat: Wildlife Sightings layer from iNaturalist and Acartia
Recent community wildlife observations, as a new Wildlife group in the
layer panel.

- Server: keyless iNaturalist proxy (v2 observations with only the
  projected fields; undocumented v1 UTFGrid density tiles, decoded on the
  server) sharing one paced queue (burst 4, ~1/s, 60/min, 8,000/day budget,
  up to four in flight), in-memory caches and upstream-only cooldowns;
  Acartia Salish Sea marine-mammal feed rounded to 0.01 degrees with
  comments dropped.
- Layer: grid counts drawn as a soft view-ranked density glow; real sampled
  sightings as taxon badges at their true positions with ambient photo
  thumbnails and labels; obscured records stay privacy cells; a clicked
  sighting stays pinned through camera moves and refreshes.
- Anchored photo card per selection with the single iNaturalist link;
  attribution in the layer row and the Data attribution popover.
- Filters (CONFIRMED, taxa presets) round-trip in share links with token 9.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:17:12 -05:00
Manjunath 1c59105a80 Judge tilts by camera pitch and follow a newer flight at once
- A tilt's angle, limits and "unchanged" check use the camera's own
  pitch. At regional range the pivot pitch is up to about 8 degrees
  shallower, so "tilt to 5 degrees" from 900 km ended at -12.9 degrees.
  The swing now solves for the pivot pitch that gives the camera angle.
- A zoom out during a pivoting tilt stops at the 1,000 km pivot limit
  instead of the orbit's 5,000 km, and a zoom in that reaches the
  clearance is applied before the pivot ends.
- An angle the camera already holds still takes navigation, so it stops
  a running motion and releases the follow camera.
- A motion queued behind a fly-to moves to a newer fly-to as soon as it
  begins, so a stalled lookup no longer holds it past a flight that
  landed. Wait listeners are removed when a wait ends, and a lookup that
  found nothing points straight at the flight that moved.
- A fly-to that stops a running motion names it (stoppedMotion).
- A tilt left or right is refused, and the pitchDeg description says
  steps use direction.
2026-11-05 11:36:29 +05:30
Denis Groshev 219f3e1079 feat(cockpit): enter Cockpit with only the ridden aircraft's layer on
Cockpit entry no longer needs Contacts or both flight feeds: a civilian
aircraft needs Live Flights, a military one Military Flights. Contacts and
every other layer can be turned on or off before or during a session, and
turning off the ridden layer ends it. A Context mode change in flight still
blocks entry.

When the other air feed is off, Live Signals and the Contact card say
FLIGHTS FEED OFF or MILITARY FEED OFF · PARTIAL PICTURE instead of
presenting a partial picture as complete. Voice refuses entry with
subject-layer-off rather than contacts-inactive, and its tool description
says Contacts is not required.

Double-clicking the followed aircraft enters Cockpit through the same gate.
The Cockpit takes the scene's double-click before Cesium's stock handler,
which would otherwise untrack a followed aircraft whose model is a
primitive. The entry also shows under the Intel HUD readout, sharing the
Contacts button's face. A test keeps the classes the Cockpit adds to the
body from matching another element's rule.
2026-11-04 21:53:05 +01:00
Manjunath bf73a8a1e1 Merge main into the voice tilt branch
No conflicts. The tool payload pin and the Realtime prompt size are unchanged (55,492 with Overpass).
2026-11-04 13:45:45 +05:30
Sameh Khamis b6967a3dd9 Merge branch 'main' into feat/typed-ai-chat
# Conflicts:
#	src/i18n/locales/ar.json
2026-11-03 15:45:43 -08:00
Sameh Khamis 8cd0cc82f5 fix(cctv): read TranStar dir as the camera's side of the road, at low confidence
TranStar's `dir` field is the side of the freeway a camera sits on, not
the way it faces. TranStar's own map script (translateCameraDirection)
shows it as "Traffic closest to the camera is traveling": South is
eastbound, North westbound, West southbound and anything else
northbound. The live catalog agrees: I-10 cameras are North/South and
I-45 cameras East/West.

Treating `dir` as a high-confidence facing pointed most Houston cameras
across or away from the road they watch, and dropped the "estimated"
marking. Map it to the travel direction instead (S 90, N 270, W 180,
E 0) as a low-confidence prior with the low-confidence pose, and keep
the id-hash fallback for None or unknown values.
2026-11-03 15:02:30 -08:00
Sameh Khamis 09bdc314d0 Merge branch 'main' into fix/houston-cctv-pack 2026-11-03 15:01:13 -08:00
Sameh Khamis 585392cbcf fix(drone-airspace): refuse cross-site requests to the FAA proxy
A foreign Origin or Sec-Fetch-Site: cross-site request was answered and
counted against the client's 60-a-minute quota, and could start a refresh.
Run the keyless same-site checks the other map routes use before rate
limiting and cache acquisition; reverse-proxy forwarding headers stay
accepted. List the route with the keyless map routes in SECURITY.md.
2026-11-03 14:35:14 -08:00
Sameh Khamis 8e63420f06 fix(drone-airspace): show stale FAA snapshots as stale and cap them at a day
The source dropped the proxy's stale flag and fetch time, so the layer row
read nominal and freshly updated through an outage, and the proxy served its
last snapshot indefinitely. Carry stale and fetchedAt through the source as
Fire Perimeters does, date a stale snapshot by its fetch, and stop serving a
snapshot more than 24 hours old.
2026-11-03 14:34:01 -08:00
Sameh Khamis 6bdedd8523 fix(drone-airspace): refuse empty or incompletely paged FAA snapshots
An upstream answer whose zones all fail validation normalized to an empty
list, and paging that stopped at the page limit while the service still
flagged more records was accepted as complete. Both were cached as a fresh
snapshot for an hour and replaced the last good one. Treat them as failed
refreshes so stale-on-error keeps serving the previous snapshot.
2026-11-03 14:33:10 -08:00
Matt Van Horn 08b8be26fe Merge remote-tracking branch 'upstream/main' into fix/drone-airspace
# Conflicts:
#	docs/CURRENT-STATE.md
#	src/app/constructCatalog.test.mjs
#	src/app/sourceComposition.test.mjs
#	src/data/layerState.test.mjs
#	src/data/layerStateTokenLedger.test.mjs
#	src/i18n/locales/ar.json
2026-11-03 11:55:42 -08:00
manjunath22466 39165bb199 fix(voice): offer typed chat the same Overpass tools as voice
Main now offers osm_query, and names it in the prompt, only when Overpass
is configured. Typed chat follows the same switch on both providers: its
prompt, the tools it offers and the calls it accepts.
2026-11-04 00:17:54 +05:30
manjunath22466 fe78b3ebc9 Merge branch 'main' into feat/typed-ai-chat 2026-11-04 00:17:47 +05:30
Matt Van Horn 038b4f3ef6 Merge remote-tracking branch 'upstream/main' into fix/houston-cctv-pack
# Conflicts:
#	docs/CURRENT-STATE.md
2026-11-03 10:41:50 -08:00
Manjunath eeb20d24e9 Merge main into the voice tilt branch
Re-pin the Realtime tool payload hash for the merged tree (voice geometry plus pitchDeg). To fit main's 55,500 prompt ceiling with Overpass on, the pitchDeg schema drops its -90..90 range (the runner already clamps to -89..-5, now pinned by a test) and its description is shorter. The Overpass prompt measures 55,492.
2026-11-04 00:06:56 +05:30
Sameh Khamis 6b92602589 fix(osm): key place-search clients by IPv6 /64
/api/osm/features keyed its rate limit and its one-search-per-client slot
by the full peer address, so an IPv6 host could rotate addresses inside
its /64 to take both global slots and slip the 12/min limit. It now uses
clientNetworkKey, as /api/overpass does.
2026-11-03 09:29:52 -08:00
Sameh Khamis b531dd545b Merge branch 'feat/voice-geometry' into maintainer fixes
Picks up acd87e6c: the keyless cross-site gate on /api/osm/features
(582bfce6) and main at 93804d5d. SECURITY.md, the format-scope list and
the schema-test comments take the branch's side; the place-search budget
wording from the timeout fix is kept.
2026-11-03 09:29:23 -08:00
Sameh Khamis 3d2896cf27 fix(osm): default an absent /api/osm/features limit to 500
Without a limit parameter the route read null, Number(null) is 0, and the
clamp turned that into a one-feature list. An absent or empty limit now
gets the documented default of 500.
2026-11-03 09:13:40 -08:00
Sameh Khamis 4efba8ccca fix(osm): end the server's place search before the voice answer gives up
The voice answer waited 25 s for /api/osm/features, while the server let
Overpass run for 25 s and itself waited up to 40 s per endpoint and 45 s
in all. When the browser gave up first it closed the request, the route
cancelled the upstream query, and nothing was cached, so the OSM_TIMEOUT
retry started the same query from scratch and timed out again.

The server now tells Overpass 20 s, waits 22 s per endpoint and 22 s in
all, which ends inside the client's 25 s. A test pins that ordering.
2026-11-03 09:08:16 -08:00