Commit Graph
246 Commits
Author SHA1 Message Date
Sameh Khamis 1a03e7483d feat: share the tool catalog with voice 2026-10-01 13:52:36 -07:00
Sameh Khamis 0953ebb026 feat: add a Bhote Koshi flood event tool 2026-10-01 13:44:41 -07:00
Sameh Khamis 28cfd39d1a feat: add a datacenter and dam tool 2026-10-01 13:38:38 -07:00
Sameh Khamis a8e5ff9390 refactor: move infrastructure data parsing into a portable source module
The datacenter and dam layers parsed their bundled GeoJSON Lines and
built analyst records inside the Cesium-backed layer module. Move the
data URLs, the line parser, mapAnalystRecord and layerTitle unchanged to
src/sources/infrastructureData.js; the layer module imports them and
still exports mapAnalystRecord. The consumer-build pin lists the new
module.
2026-10-01 13:34:26 -07:00
Sameh Khamis 0a454815cb feat: add a license plate reader camera tool
Add find_alpr_cameras over the layer's OpenStreetMap camera extracts:
cameras in a US or Canadian area of at most 3° per side, nearest first,
with operator, manufacturer and facing direction, and the source's
attribution. Areas outside coverage or too wide are reported.
2026-10-01 13:31:31 -07:00
Sameh Khamis c1957d884e feat: add a submarine cable tool
Add find_submarine_cables over the bundled TeleGeography data: cables
passing through or landing in an area, or matching a name, with landing
points and the dataset's attribution. The local MCP server's fetch reads
the bundled data files a browser would load by URL, and nothing outside
that directory.
2026-10-01 13:28:02 -07:00
Sameh Khamis 9be90fff44 feat: add a recent satellite imagery tool
Add get_recent_imagery. It searches the recent Harmonized Landsat and
Sentinel-2 catalog for an area, picks the image the layer would show
with the layer's own ranking, and returns it with its date, sensor and
cloud cover, falling back to the VIIRS daily overview.
2026-10-01 13:25:00 -07:00
Sameh Khamis a3aa058569 feat: add weather map and wind tools
Add get_weather_map, which returns the latest NOAA radar, satellite or
lightning map over an area as an image, choosing a window the image
route accepts and reporting areas outside the product's coverage, and
get_wind, which samples GFS or IFS wind at a location with the wind
layer's own grid sampling.
2026-10-01 13:22:16 -07:00
Sameh Khamis fe69f22346 feat: turn layers on in links to an area
open_in_gods_eye_view accepts layers and writes them with the app's own
share-link layer codec, so the link opens with those layers on. Layer
names are limited to the registered layer ids.
2026-10-01 13:17:15 -07:00
Sameh Khamis 9de2705eaf refactor: move the layer state coordinator into its own module
layerState.js held both the share-link layer codec and the coordinator
that restores layer choices into the running app and persists them to
browser storage. Move the coordinator, unchanged, to
layerStateCoordinator.js so the codec stays a pure data module that code
outside the browser can use to write share links. The registry stays in
layerState.js, where the token tooling reads it, and the aircraft
barrel re-exports both.
2026-10-01 13:16:19 -07:00
Sameh Khamis a8b5860e9f refactor: share the default tool services between surfaces
Move the composition of the default tool services from the Node MCP
server into gods-eye-view/tools/services, a portable export. Given a
fetch that resolves the sources' /api paths, it builds the same services
in a browser as on the local MCP server, so other surfaces can compose
the same catalog. Queries still read only their services.
2026-10-01 13:12:34 -07:00
Sameh Khamis 469b1e8d89 feat: serve the MCP server at /mcp on the local servers
Mount the local MCP server at /mcp on the development and preview
servers for clients that connect by URL. The route accepts only
requests from this machine that name a loopback host and, when an
Origin is sent, come from a loopback origin, and drains oversized
bodies so the 413 response is delivered. The provider-order regression
test now pins the route between the providers and the API fallback.
2026-10-01 12:47:44 -07:00
Sameh Khamis b6af8b34e0 feat: add a tool that links to an area in the app
Add open_in_gods_eye_view. It builds a version 2 share link looking
straight down on an area, with altitude chosen from the area's size, on
the app address the application supplies.
2026-10-01 12:42:08 -07:00
Sameh Khamis e6cbcc31ad feat: add ship tools
Add vessels_in_area, find_vessel and get_vessel_track over the live
vessel source, and a ships section in situation_brief. Without an AIS
key the source's own message reaches clients as an unavailable feed.
2026-10-01 12:32:09 -07:00
Sameh Khamis 0a74e36ce8 feat: add weather, hazard, terrain and situation tools
Add get_weather, get_regional_brief, get_cyclones, get_fire_perimeters,
get_terrain_height, find_military_installations and get_map_features
over the application request services and the layers' sources.

situation_brief combines weather, earthquakes, fires, aircraft and
cyclones for an area by reusing those tools, marking sections that are
unavailable instead of failing. get_hud_caption returns the app's
heads-up display caption for the same overview.
2026-10-01 12:28:34 -07:00
Sameh Khamis 8c97fed92d feat: add bike-share and transit tools
Add get_bike_share over the public GBFS systems and get_transit_vehicles
over the GTFS-Realtime feeds. Each finds the systems or feeds whose
coverage reaches the area, reads up to three, and reports any that were
unavailable instead of failing the whole answer. Transit results carry
each feed's attribution and license.
2026-10-01 12:22:13 -07:00
Sameh Khamis 2ba751ace8 feat: read the transit feed catalog through the transit source
Add getFeeds() to the transit source. It reads the public catalog route
the server already serves, so code outside the layer can find feeds
near a location without loading the layer.
2026-10-01 12:19:19 -07:00
Sameh Khamis eb12303f46 refactor: move GBFS station parsing into a portable source module
The bike-share layer parsed station documents inside its model factory.
Move the parsers unchanged to src/sources/gbfsStations.js and have the
model import them, so code outside the layer can read GBFS stations
without constructing the layer.
2026-10-01 12:18:48 -07:00
Sameh Khamis e680a59bf5 feat: add place search and routing tools
Add search_places and places_nearby over the app's Google Places routes,
and plan_route over its OpenStreetMap routing. The place search service
reports when no search key is configured, so an unconfigured server is
not mistaken for an empty result, and rate limits become retry_later.
Route endpoints accept a place name or coordinates.
2026-10-01 12:16:01 -07:00
Sameh Khamis 85fee59da1 feat: add public camera and radio tools
Add find_cctv_cameras, get_cctv_snapshot and find_radio_stations. A
snapshot returns the camera's current frame as image content with its
credit. Tool results may now carry base64 images, which the MCP adapter
sends as image content.
2026-10-01 12:11:00 -07:00
Sameh Khamis 22c3fe40b0 feat: add aviation and satellite tools
Add aircraft_in_area, find_aircraft, get_aircraft_track and
get_aircraft_info over the live aircraft sources, with an optional
military feed, and next_satellite_pass and satellites_overhead over the
satellite catalog and the existing pass predictor.

Source failures from the live contract now become tool errors with the
matching code, so a rate limit reaches clients as retry_later with a
wait time. Tool schemas may use pattern.
2026-10-01 12:05:50 -07:00
Sameh Khamis 9895942633 refactor: move TLE text parsing into a portable source module
The satellites layer parsed catalog text inside its Cesium-backed orbit
owner. Move the parser unchanged to src/sources/tle.js, add a catalog
number helper, and have the layer delegate to it so code outside the
layer can read TLE text without loading rendering.
2026-10-01 12:05:41 -07:00
Sameh Khamis c3dfa8e853 feat: add a tool catalog and local MCP server
Define tools that answer questions from the app's data once, independent
of the surface that exposes them. A tool declares its arguments and the
services it reads; applications compose tools with their own services and
optional call interceptors. Queries reuse the layers' portable source
factories, so they run wherever an application routes /api requests.

The first queries are get_earthquakes, get_active_fires and
get_recent_launches, with a shared area argument (place name, bounding
box, or point and radius) and capped lists.

gods-eye-view/tools/mcp adapts a catalog to the Model Context Protocol
with a small JSON-RPC handler and a stateless HTTP transport, adding no
dependencies. npm run mcp serves Core's tools over stdio to local clients
such as Claude Code, reading from a running app. Both exports are
portable and checked by check:boundaries; the app does not import them.
2026-10-01 11:51:59 -07:00
f4fcf267c6 fix(osm): stop using public Overpass by default (#648) (#742)
Traffic roads come from OpenFreeMap vector tiles, or TomTom flow tiles with a
key (Hybrid by default). Military areas use OpenFreeMap polygons with a bundled
worldwide name index; wide views show named points. ALPR reads an hourly
OpenStreetMap extract. Cockpit place context and country, state and US county
outlines use bundled Natural Earth and US Census data. Overpass is used only
when an operator configures their own endpoint.

Also: camera arrivals no longer land below ground, keyless terrain retries on
429, bounded relation geometry for configured Overpass, one shared
OpenStreetMap credit, and separate tile and API transports for embedders.

Co-authored-by: theGaryLarson <[email protected]>
Co-authored-by: Milan Khanal <[email protected]>
2026-09-28 19:07:57 -05:00
manjunath22466 d0105f1e9d fix: reconcile share-link token allocation with main
Preserve all published token assignments and add regression coverage for
all-enabled URL restoration, longer token lists, and manual provisional
token recovery after competing merges.

Builds on daikaginza's digits-first and merge-time allocation proposal.
Thanks to KnottyDyes for the review and regression-test suggestions.
2026-09-25 22:33:03 +05:30
Sameh KhamisandClaude Opus 5.5 00235365c3 build: register headingConfidence.js with the CCTV package boundaries (#643)
main's boundary manifest now requires every module to be owned; add the new
helper beside presentation.js in cctv-layer and the two application groups.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 22:46:58 -07:00
Sameh KhamisandClaude Opus 5.5 714d3dd1bb Merge main into pr-594
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 22:23:42 -07:00
Sameh KhamisandClaude Opus 5.5 351b5fcaa4 Merge main into pr-705
Resolve the CHANGELOG conflict with Fire Perimeters (#737) by keeping both
entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 21:52:39 -07:00
manjunath22466 54735a9e6f fix: allocate share-link layer tokens without collisions 2026-09-24 16:25:55 +05:30
Bilawal SidhuandBilawal Sidhu ce671ce500 fix(data): load bundled region packs in the browser; bound region lookups (#743)
The Natural Earth and neighborhood packs were loaded with a JSON-attributed
import(). The Vite dev server answers that request with a JavaScript module,
which the browser rejects, so every analyst region scope failed and fell
through to a geocode lookup that could take 12-28 s.

- src/data/bundledJson.js fetches the pack from a new URL(..., import.meta.url)
  asset in the browser; under Node the caller's attributed import runs.
- resolveRegionRingForQuery caps the geocode + admin-boundary rung at 3 s and
  returns { error: 'region-timeout' }; the lookup keeps running and fills the
  cache. The analyst query reports it as code 'region-timeout'. The Nepal
  locator opts out of the budget.
- Unit tests for the loader, the timeout and the analyst error; the behavior
  harness checks the Gulf of Mexico scope and retries Texas on region-timeout.

Co-authored-by: Bilawal Sidhu <[email protected]>
2026-09-24 01:52:41 -05:00
513dc87a0e feat(hud): Cyber HUD theme with GPU contact sonar (#706)
Add the opt-in Cyber HUD layout with coordinated map and cockpit panel
styling, GPU-only contact sonar (rings, range, power, opacity, sector) with
native-rendering fallback, and set_cyber_sonar voice controls. Sonar visual
style inspired by kk376's tactical naval sonar (#214).

Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-23 20:31:56 -07:00
f913066cf2 feat(fires): MODIS in Active Fires and a Fire Perimeters layer (#737)
Adds MODIS NRT to Active Fires and a keyless Fire Perimeters layer (NIFC WFIGS) with
InciWeb incident links, served through a capped, cached /api/fire-perimeters proxy.

Co-authored-by: Gustavo Beneduzi <[email protected]>
Co-authored-by: James Cooke <[email protected]>
2026-09-23 21:21:52 -05:00
Bilawal SidhuandBilawal Sidhu 4b56d0e917 fix(radio): restore the Radio QA harness and the row refresh it depended on (#736)
The harness called the compatibility facade's private _refreshTogglePanel,
which the layer-lifecycle refactor removed; Transit and Directions call
refreshLayerStats() on the bare lifecycle, which lacked it, so their rows
only repainted on the next tick. Add refreshLayerStats() to the lifecycle
and use it from the harness. With the crash gone, two further checks ran:
the selected-entity paint wait now also waits for Radio's own reveal, and
on phones the title bar sits 16 px from the top so both broadcast waves
stay on-screen.

Co-authored-by: Bilawal Sidhu <[email protected]>
2026-09-23 19:51:56 -05:00
1fc795577a feat(sdr): local receivers — browser RTL-SDR, Local ADS-B layer and decoder feeds (#732)
Adds a Local ADS-B layer fed by your own receivers: a browser WebUSB
RTL-SDR (with FM listening) and local 1090/978 MHz decoder feeds
(LOCAL_RECEIVER_FEEDS, local and private addresses only). Local aircraft
draw in magenta with class, silhouettes, 3D models, trails and a position
sanity filter; the click card names the band and input that heard them.

Builds on Sameh Khamis's SDR prototype and the initial GEV port by
Mazeyar Moeini Feizabadi (#134); the decoder-feed design follows the
notes on #57. Closes #57.

Co-authored-by: Sameh Khamis <[email protected]>
Co-authored-by: Mazeyar Moeini Feizabadi <[email protected]>
2026-09-23 16:00:53 -05:00
Bilawal SidhuandBilawal Sidhu 143cffd7ca fix(ui): keep panel scroll position through rail layout passes (#731)
The right-rail layout pass lifts the Context, CCTV, Weather and Recent
Imagery panel scrollers to measure their natural heights, which clamps
their scroll offset to zero, but it only restored scrollers tagged
data-rail-scroller. A layer that refreshes its Layers-panel row every
second snapped the Context panel back to the top while scrolling. Tag
every lifted scroller and pin the pairing with a test that reads the
measuring CSS.

Co-authored-by: Bilawal Sidhu <[email protected]>
2026-09-23 15:55:41 -05:00
Sean ArmstrongandClaude Opus 5 b456eb8ff1 fix(render): keep Cesium's model atmosphere off Apple Metal
iPad and iPhone show "An error occurred while rendering. Rendering has
stopped." on load. Cesium's AtmosphereStageVS calls
czm_computeGroundAtmosphereScattering with its `out` parameters bound
directly to varyings:

    czm_computeGroundAtmosphereScattering(
        v_positionWC, lightDirection,
        v_atmosphereRayleighColor, v_atmosphereMieColor,
        v_atmosphereOpacity);

ANGLE's Metal backend wraps each `out` argument in an ANGLE_out(...)
helper taking `thread T &`, but vertex-output members live in
`__metal_generic` and cannot bind to `thread`, so the program fails to
LINK and Cesium tears down the render loop.

This is the only shader in Cesium that binds varyings to `out` params --
SkyAtmosphere (czm_computeScattering) and the ground-atmosphere fragment
path both route through locals, so they are unaffected and stay on.

ModelSceneGraph.configurePipeline attaches the stage under a single
condition, `fog.enabled && fog.renderable`, so clearing `fog.renderable`
keeps the broken program from ever being built.

`renderable` rather than `enabled` is deliberate: Fog.update returns
early when `enabled` is false and forfeits the density that drives 3D
Tiles screen-space-error scaling, and visualSettings saves/restores
`fog.enabled` around IR boost, which would resurrect the broken shader
mid-session.

Detection is a WebGL2 link probe of the same out-param/varying pattern,
so a future driver fix silently restores the effect with no code change,
OR'd with iOS/iPadOS platform detection as a backstop for when no probe
context can be created. iPadOS desktop mode is identified by the
Macintosh UA token plus maxTouchPoints > 1, since real Macs report zero
and navigator.platform is deprecated.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HoRD3yHVgfTHeikTmsUoHf
2026-09-23 09:25:16 -06:00
Bilawal Sidhuandmanjunath22466 082074a006 feat(layers): Recent Imagery — open satellite imagery for a selected box (#716)
Select a box on the world and browse the last 30 days of open satellite imagery over it, keyless: NASA GIBS Harmonized Landsat Sentinel-2 (30 m) and the VIIRS daily overview (250 m), with a day strip, IMAGE / VS BASEMAP / A / B modes with a swipe divider, PNG export and share links. The before/after swipe from the Nepal scene moves into shared modules so both use it.

Co-authored-by: manjunath22466 <[email protected]>
2026-09-22 20:34:11 -05:00
25491f7e33 feat(taps): shared address contract for receiver taps (#382)
* feat(taps): shared address contract for receiver taps

Receiver taps (#57 RTL-SDR, #56 Rayhunter, #7 TAK, #6 Meshtastic) differ from
every other provider in this tree in one way that matters: the upstream address
comes from the user at runtime rather than from a fixed constant, so each tap
route is an SSRF surface unless the address is constrained. Today each tap
would answer that question for itself, and they would disagree.

parseTapAddress() is the single place the rule lives:

  A tap may address loopback or an RFC1918 private network, plus the
  `localhost` and `*.local` (mDNS) names. Nothing else.

That is what a tap is for — reading your own device on your own network. A tap
pointed at a public address is either a mistake or the proxy being used as a
confused deputy, and both get the same answer. Link-local (169.254/16) is
excluded wholesale, which covers the cloud metadata address by construction
rather than by blocklist.

Deliberately NOT reusing isNonGlobalIpv4() from radio/stations.js: it reports
malformed input like 999.1.1.1 as non-global, which is the safe direction for
the radio proxy's "is this public enough to fetch?" test but inverts into
*allowing* garbage under a tap's "is this local enough to fetch?" test. A
positive allowlist cannot invert that way, and the tests pin that case.

Two documented non-goals so they don't read as oversights: no general hostname
resolution (a name other than localhost/*.local is rejected rather than
resolved-and-pinned, avoiding DNS-rebinding defence for no gain), and no IPv6
(zone-index handling is platform-specific and no tap has needed it).

Scoped to the address rule alone. The recorded-fixture harness lands with the
first tap, designed against a real recorded response rather than guessed at
with no consumer.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01KGKBNNjPh4iQe8sviGZNPb

* style: adopt the tap address contract into the format scope

Mechanical. Adds server/providers/common/tap-address.js and its test to
scripts/format-scope.json and applies prettier, matching the six sibling
modules in server/providers/common/ that are already adopted. No behaviour
change.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01KGKBNNjPh4iQe8sviGZNPb

* docs(taps): state accurately which taps this contract governs

The header claimed #57, #56, #7 and #6 as consumers. Only #56 (Rayhunter)
actually is today: it takes ?base=host:port from the browser, which is the
shape that makes a route an SSRF surface. #57 would qualify if built
server-side.

The other three are different shapes, and forcing them through a private-only
check would be wrong rather than merely unnecessary:

- Meshtastic (#6) reads a fixed public broker, mqtt.meshtastic.org.
- WiGLE (#11) reads a fixed public API behind a token.
- TAK (#7) takes its host from operator-set env config rather than from the
  browser, and a TAK server is frequently a legitimate remote host.

No behaviour change; comment only.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01KGKBNNjPh4iQe8sviGZNPb

* refactor(taps): move the tap address contract to src/data so both sides share it

The browser needs the same answer the server does — the server as a trust
boundary, the client to give immediate feedback on a typed address without a
round trip. Keeping the rule under server/providers/ would have meant the
client re-deriving it, which is how the Rayhunter branch already carried a
hand-synced copy of the regex with a comment admitting it.

#274 is open about eight independent copies of haversine, one of which had
silently swapped its arguments. Shipping a new duplicated validator across a
trust boundary in the same tree would be hard to defend.

server/providers/gbfs.js already imports src/data/gbfsSource.js, so shared
validation living in src/data with the server importing it is the established
direction here rather than a new one.

Pure move plus the path/docblock updates. No behaviour change; the 12 contract
tests are untouched and still pass.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01KGKBNNjPh4iQe8sviGZNPb

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Co-authored-by: Bilawal Sidhu <[email protected]>
2026-09-22 19:22:55 -05:00
0251c74109 feat(weather): add globe wind, observed weather and cyclone context (#617)
Wind (NOAA GFS and ECMWF IFS: animated particles, colour field, a reading at map centre), observed weather (NOAA MRMS radar, GOES and NESDIS infrared clouds, lightning density) on one shared history timeline, NHC and CPHC cyclone advisories with tracks and cones, and a WEATHER panel in the right rail. On 3D Tiles the observed products render as raised shells with a sharper window around the view.

Co-authored-by: Gustavo Beneduzi <[email protected]>
Co-authored-by: Daniel Slay <[email protected]>
Co-authored-by: Rehaan Delmotra <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
2026-09-22 17:45:39 -05:00
Sameh Khamis 9ad64112dc fix: report OpenSky Basic and auto modes accurately 2026-09-20 16:43:16 -07:00
Sameh Khamis a716b059b8 Merge origin/main into fix/issue-362-opensky-doctor 2026-09-20 16:43:01 -07:00
Sameh Khamis 5d684c22b3 Merge origin/main into fix/ais-unavailable-sog-cog
# Conflicts:
#	CHANGELOG.md
#	docs/CURRENT-STATE.md
2026-09-20 16:33:53 -07:00
daikaginza ae35a2c939 docs(attribution): credit adsbdb and carry its route-data terms
adsbdb supplies the aircraft type, model name and registration behind every
enriched flight, and the airline and origin/destination pair behind the
tracked contact's FROM -> TO strip. It appeared in neither attribution
surface: DATA_SOURCES.md and src/data/dataCredits.js each held zero
occurrences of it, against the rule both files state for themselves and
CONTRIBUTING's fourth pull-request item. adsb.lol is a different operator and
its ODbL credit does not reach this data.

The reporter of #256 left the patch off deliberately, saying the terms were
what should not be guessed. They are published, so this takes them from the
source rather than inventing them: adsbdb credits PlaneBase for the aircraft
data and Guillaume Michel for the ICAO-to-N-number conversion, and states that
the flight route data "is the work of David Taylor, Edinburgh and Jim Mason,
Glasgow, and may not be copied, published, or incorporated into other
databases without the explicit permission of David J Taylor, Edinburgh". No
licence or rate limit is published for the service itself.

That restriction is the reason the note records what the proxy does with what
it fetches: responses are cached per key for 24 hours in .gev-cache/adsbdb.json,
which is gitignored, so the data stays on the operator's machine and is never
bundled with this repo or redistributed.

A test pins the credit. The attribution is a requirement rather than a
preference, so it should fail loudly if a later edit drops the names or the
restriction, instead of surviving on luck.
2026-09-19 20:12:36 +07:00
Theerayut BoonkirdandClaude Opus 5 75869d0c10 chore: adopt the new AIS test into the explicit formatting scope
scripts/format.mjs skips *.test.mjs during runtime discovery, so a test
added under a discovered root is never seen by format:check until it is
listed in scripts/format-scope.json — which is what CODE-BOUNDARIES.md
means by adding tests to the explicit list as they are adopted. The file
went in unformatted and the check stayed green, hiding it.

Listed and formatted; format:check now covers 917 files instead of 916.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-18 00:43:02 +01:00
manjunath22466 621d186de1 fix: scope Nepal media playback to explicit scene actions 2026-09-16 12:14:15 +05:30
Sameh Khamis 078fc290ec Add Director authoring, import previews and scene bundles 2026-09-15 20:15:32 -07:00
Sameh Khamis 80027756ef feat: add declarative Director feature actions 2026-09-15 19:16:49 -07:00
Sameh Khamis 92526e9a92 feat(director): add bounded scene data packs and placement 2026-09-15 17:59:53 -07:00
Sameh Khamis 13c0b8d1bc feat(director): add authored camera directions and anchors 2026-09-15 16:53:26 -07:00
Sameh Khamis 18300e7e00 feat(director): validate scene documents and preserve saved edits 2026-09-15 16:20:36 -07:00