The voice runner subscribes to camera handoffs as it is created. The Cyber sonar test builds its runner on a StyleManager made from the prototype, which has no navigation owner, so the facade's delegation threw and both Node jobs failed. The facade now returns nothing there, like its other optional owners; the real shell builds its navigation owner before voice creates a runner.
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.
`./server/providers/news` (node) with its own package boundary, so a host composing its own local providers can serve /api/news from GEV_NEWS_ORIGIN as Core's standalone server does.
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.
The pure half of a news layer: validated records for a news server's
`/api/news/hotspots|feed|outline` contract, a presentation model and canvas
markers and picture cards, and a fetch client.
- Colour carries severity only (0 no physical event, 1 minor, 2 significant,
3 major), never a side; the category is the icon, the rim is attestation
(confirmed, a claim, one side's own press), size grows with sources and
markers fade with age.
- Cards say "Reported by {side} sources" and mark official channels; a
machine-read English headline is labelled as such.
- `createNewsSource` reads same-origin routes with a 15 s timeout, a 4 MiB cap
and record-by-record validation; `NEWS_API_PATHS` and `newsQueryAllowed`
let a host proxy exactly what the client sends. A 503 marked unavailable
means "not configured": no retries.
- Exported as `./layers/news/source`, with its own package boundary.
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.
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]>
The launches layer swallowed every fetch error in its update, so the
manager treated a 429 or timeout on /api/launches as a successful start.
Context then committed the isolating mode with no missions: the other
layers stayed off, unrelated enables were refused, the camera stayed at
the full-globe view and the roster kept reading "loading".
Until the first index loads, the update now reports the failure. The
manager fails the enable closed and both entry routes roll back to the
pre-entry layers. The camera pose captured at entry is restored when the
mode is left before any index loaded, and the roster shows a failed
state instead of loading. A valid empty index still enters, and a
refresh failure after a loaded index keeps its records.
- Replace the four era chips with the start and end year pickers the
air-accidents layer uses (the same RowRange control, ported as is);
the era colours stay as a legend under the row.
- Rasterize the boat marker at 144 px so large markers stay sharp.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new Events layer maps shipwrecks with a card per wreck:
- EMODnet Human Activities heritage shipwrecks (about 6,500 in European
waters, CC BY 4.0), skipping rows taken from wrecksite.eu.
- Wikidata shipwrecks with a Wikipedia article plus sinking events
(CC0); an event folds into its ship for the date and death toll.
- A same-origin /api/shipwrecks proxy refreshes once a day and serves
the last snapshot as stale for up to a week.
Markers are a boat icon coloured by era and sized by deaths; four era
chips filter the layer. The card follows the air-accidents layout
(deaths first). Text that names people is dropped; victims appear as
a count only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
At the ~20 px the fleet renders at, the bus, tram, metro and unknown
glyphs were near-symmetric boxes: rotated along the course, nothing showed
which end was the front. Each now has a front built into its outline (an
arrow-like nose for the bus, a pointed cab for the tram, a chamfered cab
for the metro), so it survives the tint, the halo and the sensor profiles,
which keep only the outline.
A vehicle with no known course sits at rotation 0, where a pointed glyph
would claim it is heading north. Those vehicles, fleet and selected alike,
now get a symmetric outline (rail and ferry included) and switch to the
pointed one once a course arrives. The raster cache doubles to a fixed 72
variants.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
The changelog fragment named share-link token 6 (sea marks); the layer
reserves 8. CURRENT-STATE said voice could toggle and query the layer and
listed it among the analyst record sources, but it is in VOICE_OFF_LAYERS.
Say voice is off for now, and that the layer covers FAA prohibited and
restricted special-use airspace only, not the UAS Facility Map / LAANC grids
or TFRs. A test pins these claims to the token ledger and the voice manifest.
From the whole-globe view the camera could end up facing empty space with
only stars on screen. Cesium's free look (Shift+drag) turns the camera in
place, and from 18,000 km the planet's edge is only about 15 degrees from
nadir, so a few drags turned it out of frame. The rolled pose was then
saved into the share hash, so a reload or shared link opened on stars too.
- Allow free look only below 1,000 km. Orbit, tilt and zoom are unchanged.
- Measure how much of the frame shows the planet's surface by casting a
32x18 grid of view rays at the WGS84 ellipsoid. While the scope mask is
on (the default) only the rays inside its keyhole count, since the mask
hides or dims the rest of the frame.
- When a camera move ends above 1,000 km with no ray on the surface, turn
straight down at the same position and heading. Limb glow and a sliver
up to about one grid cell thick can count as out of view. The end of a
move comes from Cesium's moveEnd and from a check of its own that
samples the pose every 250 ms after moveStart and acts once it has held
for half a second: at some of these poses the camera drifts by a
rounding error every frame, so Cesium never sees it stop and never
raises moveEnd. The zoom-out guard from #1522 keeps wheel and pinch
zoom-outs in view; this catches the other ways a move can end looking
away, such as a saved or shared view that did, or a
drag that Cesium turns into an in-place look below its 7,500 km
trackball height (one that starts on the stars, or a tilt drag with the
screen centre off the planet). Views with more surface, tracked or
locked cameras and the 2D and Columbus modes are left alone.
- Voice tilt and in-place turns of a free 3D globe camera above 1,000 km
stop when about a tenth of the frame still shows the planet (or about as
much as it showed when it started, two grid cells of sampling noise
allowed), never below about 1% of the visible frame; a view that already
shows less only keeps or gains what it has. A turn that cannot go half a
degree further without crossing that limit is refused with a reason
instead of reported as done, and a turn it accepts turns at least that
half degree. A turn that keeps or gains surface is not refused, even
after the window has been resized, except that near the limit, where the
planet's edge crosses the sample grid, sampling wobble can occasionally
refuse one (most often above about 15,000 km and on phone or ultrawide
frames); nothing then turns away from the planet. A turn that starts
where the last voice turn left the camera keeps that turn's limit, or a
higher one if the planet now fills more, so repeating the request does
not take any more; once anything else has moved or turned the camera, a
turn is measured from what is on screen. A request made while the camera
is tracked is judged by the pose voice's release of the follow camera
will leave, before that release, so a refused turn keeps the tracking.
The frame measure and the look-step limit live in src/globeInView.js,
shared by the viewer and the voice camera verbs.
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.
Picks up #1527, which restores the 97 format-scope entries an earlier merge
dropped; this branch had kept the same 97. The resolved list is main's plus
this PR's 12 test and helper files, 1,222 paths, all present.
The guard's own tests pass even if createApplicationControls stops
installing it. This checks the ceiling and frame listeners are in place
after construction and gone after teardown.