Русский · English · Español · 中文 · Português

«Космос сейчас» — API для агентов

Эта страница — для ИИ-агентов (и их людей). Здесь описано, как агент подключается к живой странице «Космоса сейчас» (known.html) в браузере человека и что он там может: всё, что умеет интерфейс, — открыть любой объект, вести камеру, крутить время, включать слои и фильтры, режим космонавта, искать затмения и парады планет, рассказывать голосом с подписями, показывать свои картинки, писать видео, читать ошибки. Рисует браузер человека (WebGL на его видеокарте); агент шлёт команды и получает ответы и снимки.

1. Подключение

  1. Человек открывает сайт и нажимает круглую кнопку 🤖 (справа внизу, над космонавтом) — открывается полоска агента; в ней «Включить агента» — появляется код (20 знаков, например r4fxjbjsxdy6ex3pf4hd). Код — это пропуск: у кого он есть, тот управляет этой вкладкой. 🤖 только прячет/показывает полоску, связь остаётся; выключить — «Отключить» в полоске. В полоске всегда есть 📂 (открыть файл сюжета или сохранить показанный — и без агента) и «MCP ↗» (эта справка).
  2. Человек даёт код агенту. Агент вызывает connect с этим кодом.
  3. Постоянное соединение (по желанию): на странице — 📌 «Помнить» в плашке агента (код хранится в этом браузере, сайт подключается сам при каждом открытии), агент — connect с save: true (код в файле ~/.orbit-agent.json, MCP подключается сам при запуске). Дальше достаточно открыть сайт. Забыть: снять 📌 и forget.
  4. «Отключить» на странице (или уход агента) завершает сеанс: страница возвращает слои и интерфейс, как было до агента, глушит музыку и голос, убирает подписи, картинки, места посадок (sites), рисунки и метки (draw, mark, flybys), окна медиа, свои тела (spawn), останавливает сценарий и запись — независимо от onstop. Возвращаются и камера, время, выбор (по умолчанию; onstop {restore: false} — оставить, как показал агент). Исключение — play / story run с detach: true: показ доигрывает, откат — по его концу.

Звук (голос, музыка) — только после того, как человек хотя бы раз щёлкнул по странице: так требует браузер. Голос по умолчанию выключен: человек включает его сам — 🔊 в плашке агента (запоминается); подписи видны всегда.

Чек-лист показа — делай всегда

[
  {"cmd":"onstop","args":{"restore":true}},        // первым шагом: по концу, Esc или ✕ всё вернётся
  {"cmd":"reset"},
  {"cmd":"view","args":{"on":["oplanets"]}},        // только нужные слои: спутники, аппараты, орбиты, созвездия — выключены
  {"cmd":"hideUi","args":{"on":true,"labels":false}},
  {"cmd":"time","args":{"at":"<момент>","rate":0}}, // время задавай сам; rate 0 — свет не уедет
  … перелёт (fly) …
  … показ …
]

2. Связь

Обе стороны подключаются к комнате-ретранслятору на том же сайте: wss://<сайт>/agent/<код>?r=agent (страница — ?r=page). Комната пересылает сообщения между ними и ничего не хранит. Сообщения — JSON:

агент → страница   { "id": 1, "cmd": "go", "args": { "search": "?o=Mars" } }
страница → агент   { "id": 1, "ok": true, "result": … }   |   { "id": 1, "ok": false, "error": "…" }
комната → обоим    { "sys": "peer", "on": true|false }      (собеседник подключился / ушёл)
поддержание связи  { "sys": "ping" } раз в 30 с, комната отвечает { "sys": "pong" }

По одной связи на роль (новый агент с тем же кодом вытесняет старого, код закрытия 4000 — тогда не переподключаться). Сообщение — до 4 МБ. Сайты: https://space.bl2.ru, https://space.bl2.net (один сайт, язык по умолчанию — ru / en).

3. MCP-сервер

Готовый MCP-сервер (stdio): scripts/agent-mcp.mjs в репозитории orbit-proto (Node ≥ 20, зависимость ws). Каждая команда ниже — инструмент с тем же именем (hide_ui → команда страницы hideUi), плюс connect, disconnect, help (эта страница текстом; lang: ru | en). Файлы: image берёт path, record сохраняет в path, screenshot возвращает картинку.

{ "mcpServers": { "orbit-space": { "command": "node", "args": ["scripts/agent-mcp.mjs"] } } }

Переменные: AGENT_SITE — сайт по умолчанию, AGENT_CODE — подключиться сразу, AGENT_LANG — язык справки.

Подключение без MCP-сервера (облачные агенты)

// «запустил и вернулся» — из консоли вкладки на space.bl2.ru (или Node ≥ 22)
const code = '<код>', ws = new WebSocket(`wss://space.bl2.ru/agent/${code}?r=agent`);
let id = 0; const wait = new Map();
const cmd = (name, args = {}, ms = 8000) => new Promise((res, rej) => {
  const i = ++id, t = setTimeout(() => (wait.delete(i), rej(new Error('timeout ' + name))), ms);
  wait.set(i, (m) => (clearTimeout(t), m.ok ? res(m.result) : rej(new Error(m.error))));
  ws.send(JSON.stringify({ id: i, cmd: name, args }));
});
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (!m.sys) wait.get(m.id)?.(m), wait.delete(m.id); };
ws.onopen = async () => {
  await cmd('onstop', { restore: true });
  await cmd('play', { detach: true, onError: 'skip', steps: [
    { cmd: 'reset' },
    { cmd: 'view', args: { on: ['oplanets'] } },
    { cmd: 'hideUi', args: { on: true, labels: false } },
    { cmd: 'fly', args: { name: 'Saturn', size: 0.5, lit: true }, wait: 6500 },
    { cmd: 'caption', args: { title: 'Сатурн', sub: '1,4 млрд км от Солнца' }, wait: 8000 },
  ] });
  ws.close(); // показ доиграет сам и потом вернёт всё как было
};

4. Команды

Посмотреть и состояние

cmdargsчто
stateвремя, скорость, выбранный объект, центр вида, камера (yaw, pitch, dist км, fov), звезда рядом, ключи включённых слоёв, размер холста, px
screenshotwidth, quality, fullJPEG сцены 3D. full: true — вся вкладка с интерфейсом (человек один раз разрешает захват кнопкой «📷» в плашке агента)
bodynameдля кадрирования: радиус км, вид, родитель, луны, углы камеры, sunYaw/sunPitch (откуда тело освещено целиком), fov, distForSize {0.25, 0.5, 0.66, 0.8 → км}: с какого расстояния диск займёт такую долю высоты экрана (= радиус / tan(fov/2) / k)
wherename, atгде тело в момент: км и а.е. от Солнца и Земли, прямое восхождение и склонение с Земли (J2000)
logslevel, last, clearжурнал консоли страницы: ошибки, предупреждения, info (последние 300)
snapshotвсё состояние (время, скорость, камера, центр, выбор, все галочки слоёв, спрятан ли интерфейс, ссылка космонавта) — сохранить, чтобы повторить баг
restorestateвернуть состояние из snapshot

Вид, камера, время, слои

cmdargsчто
searchq, limitпоиск по базе объектов, как поле поиска: тела, луны, аппараты, туманности и галактики (если слой включён), спутники Земли (имя или NORAD), астероиды (имя или номер; каталог грузится строкой load-asteroids). Ответ — строки с n
opennоткрыть найденное: выбор, карточка, перелёт — как щелчок по строке
gosearchоткрыть вид как по ссылке: ?o= тело (Mars, Марс, ISS…), ?a= астероид (номер или имя), ?s= спутник NORAD, ?g= галактика, ?n= туманность, ?st= звезда созвездия; добавки d= камера км, v=yaw,pitch, t= ISO-время
flyname, size, lit, face, secondsплавный перелёт к телу (как щелчок в дереве). size — доля высоты экрана под тело (0.66 — две трети), lit: true — с освещённой стороны; кадр — ещё ~2.5 с после перелёта (всего ~6 с, ставьте wait ≥ 6500). face: "spot" — у Юпитера время отматывается, пока Большое красное пятно не выйдет на дневную сторону
camerayaw, pitch, dist, msкамера вокруг центра вида (рад, км). ms > 0 — плавно за ms. Пример «тело на 2/3 экрана с освещённой стороны»: body → camera {yaw: sunYaw+0.4, pitch: sunPitch, dist: distForSize[0.66], ms: 3000}
timeat, rate, live, from, tofrom, to, ms, ease — плавная прокрутка от даты до даты; at — секунды Unix или ISO-момент (до н. э.: -000500-01-01), скорость (1 — как в жизни, 3600 — час в секунду, отрицательная — назад, 0 — пауза), live: true — «вживую»
viewon, set, orbitsвид — единые ключи всех переключателей. Без аргументов — список {key, on}; on: [...] — выключить всё и включить только перечисленное; set: {ключ: true|false} — переключить только эти; orbits: false — все линии орбит разом. Ключи (у каждой группы есть ещё <ключ>:orbits — линии орбит): oplanets — Солнце, планеты и луны; odwarfs — карликовые планеты и крупнейшие астероиды; ohelio — гелиопауза; oshock — ударная волна; osandbox — свои тела (spawn); oknown-small — мелкие известные (Апофис, Бенну…); ocomets — кометы; ointerstellar — межзвёздные (1I, 2I, 3I); okuiper — пояс Койпера; ooort — облако Оорта; a0…a9 — астероиды по размеру (a0 — крупнее 100 км); ocraft, ocraft-orbit, ocraft-l — аппараты в полёте, на орбитах тел, в точках Лагранжа; s0…s30 — группы спутников Земли, sats — все разом; ogalaxy — модель Млечного Пути; ouniverse — галактики; ostarsdb — известные звёзды (Gaia, Hipparcos); earth:ph|cities|seas|mountains|volcanoes — подписи на Земле; stars — звёзды созвездий с подписями; conlines — линии фигур созвездий (вместе со stars; не перечислил в view {on} — линий нет); weather — облака Земли; aurora — полярное сияние; sw — солнечная погода (вспышки, выбросы); galmarks — подписи центра Галактики и апекса; galsun — «Солнце — вы здесь»; scenelabels — подписи только у нужного сцене (labels {only}); sk:0…6, sk:neb — виды объектов неба (галактики, скопления…, туманности). Путь одного межзвёздного тела: ointerstellar:orbits выключить, select {name: '3I/ATLAS'} — его путь ярко. У сюжета и слайда — поле on (перед шагами): только то, что нужно, — новые слои, включённые по умолчанию, в старых сюжетах не появятся
layersвсе группы слоёв: key, имя, видно ли, орбиты, сколько объектов
starname | hip | gaia | index, flyзвезда из базы известных звёзд (Gaia + Hipparcos): знаменитая по имени (Sirius, Сириус, Proxima…), по номеру Hipparcos (hip) или Gaia DR3 (gaia, строкой). По умолчанию подлетает (fly: false — только карточка); вблизи — шар, в карточке спектр. Первый вызов качает базу (несколько МБ). Точки остальных звёзд — слой ostarsdb
layerkey, visible, orbitsвключить/выключить слой. Ключи: oplanets, odwarfs, ocomets, ocraft*, ogalaxy, ouniverse, ostarsdb — база известных звёзд (Gaia + Hipparcos, ~470 тыс.; первое включение качает данные, ответ придёт после загрузки; прогреть заранее — warm {code:"starsdb:"}), a0…a9 — астероиды по размеру, s0… — группы спутников…
galaxyfarвид на Млечный Путь (far — дальше, с другими галактиками). Для сцен: dist (0 — модель у Солнца, > 0 — отлёт), pitch, center: true — облёт вокруг центра Галактики, off: true; время Галактики — galtime {rate} (лет/с)
galaxiescount, dwellпролёт по N известным галактикам по возрастанию расстояния, dwell секунд на каждую
clearснять выбор, закрыть карточку

События (считает страница, формулы не нужны)

cmdargsчто
findkind, from, to, total, count, minlunar (total: true — «кровавая Луна»), solar, parade (min — планет 3…7) от любой даты; ответ — момент середины, полное ли, где видно (lat/lon/region); у лунных — contact (касание тени)
showkind, at, keepViewпоказать событие как из «📅 События»: время — за полчаса до середины, скорость ×100 (поставь time {rate: 0} или свою), выбор снят, центр вида и камера — на Землю (солнечное: над точкой тени) или Луну (лунное); парад — вид с Земли от первого лица, только планеты. Слои, заданные view, не трогает (keepView: false — как у события). После: hideUi, при нужде camera

Весь интерфейс

cmdargsчто
uitext, withinвсе видимые кнопки, галочки, ползунки, поля, строки списков и дерева — с ref, текстом, id, data-атрибутами, состоянием. Фильтр по тексту или CSS-области
clickref | selectorнажать
setref | selector, valueгалочка true/false, ползунок, поле, список
keycode, ms, shift, ctrlклавиша (KeyW… — держать ms, лететь; Space — пауза; KeyH — спрятать панели; Escape…)
pointeraction, x, y, to, dyмышь по сцене 3D: click (выбрать), dblclick (лететь), move (наведение), drag (вращать) до [x,y], wheel (dy — приблизить)
hideUion, labelsспрятать всё: панели, кнопки по углам, плашку агента (labels: false — и подписи тел); остаются сцена, подпись, картинки, окна медиа, метки (mark) и рисунки агента. on: false — вернуть
astroaction, nameкосмонавт: enter (где камера), goto name (подлететь, орбита), land (встать на грунт), exit, link, state. Ходить и смотреть — key/pointer
evalcodeтолько на dev-сервере (npm run dev): выполнить JS-выражение в странице; window.__known — состояние. На сайте недоступно

Рассказ

cmdargsчто
captiontitle, sub, text, secondsподпись слева внизу, как в заставке (пустой title — убрать)
saytext, title, sub, audio, keep, captionвывести текст подписью; audio — озвучка mp3 (ссылка или имя файла на компьютере; голоса браузера нет — mp3 делают заранее, например edge-tts); 🔇 — тишина
musicon, theme, volumeфоновая музыка, собирается в браузере: theme 0 «Туманность», 1 «Глубина», 2 «Кристаллы», null — по очереди
imageid, src | path, at, x, y, dx, dy, w, label, remove, clearсвоя картинка поверх сцены: у тела (едет за ним) или в точке экрана
playsteps | stop | pause | seek | stepсценарий целиком проигрывает сама страница: [{cmd, args, wait}], wait — мс или "speech" (пока не дочитает голос). Как плеер: pause, seek: n — на шаг n (предыдущие выполняются сразу, без ожиданий и голоса), step: ±1. У человека внизу плеер: ⏮ ⏪ ▶/⏸ ⏹ ⏩ ⏭, «слайд · шаг», 💾 сохранить на сайт (пароль), ⬇ скачать, 🗑 удалить, ✕. Останавливается при отключении агента — кроме detach: true (доиграет, потом откат). onError: 'skip' (по умолчанию — дальше) | 'stop'; ошибка шага — в logs, в play без аргументов (lastError) и в плеере. Презентация доиграла слайд — сама идёт к следующему; ✕ или Esc — выйти (интерфейс вернётся)
deckslides, start, go, closeпрезентация: [{title, steps}]; у человека внизу «◀ n/N ▶» (и PageUp/PageDown), переход проигрывает шаги слайда. Видна и при hideUi
buttonsitemsсвои кнопки внизу: [{label, title, steps}], щелчок проигрывает шаги; [] — убрать
storyaction, story, id, password, nameсюжет — то, что составил агент (план полёта, рассказ, визуализация — например для NASA Space Apps): {title, summary, lang, slides:[{title, steps}] | steps}. run — показать; publish — сохранить на сайте с паролем на правку (password; нет — страница спросит человека) → ссылка known.html?story=<uuid>, смотреть может любой без агента (с «◀ n/N ▶»); до 256 КБ, хранится 30 дней с последнего открытия; update — поправить (id + тот же пароль); delete — удалить с сайта (id + тот же пароль); download — человеку файлом (любого размера); open — человек выбирает файл (или перетаскивает .json на страницу; в плашке агента — меню «📂»); load — по id
stepsгде сейчас в сюжете или презентации: слайд и список слайдов, шаг и список шагов (n, подпись), играет ли, пауза. Переход — play {seek: n} / {step: ±1}, deck {go: n}. У человека в плеере — ☰ список слайдов и шагов, щелчок — перейти
scenecodeсцена заставки внутри сюжета — её пролёт камеры, подпись и время (~20–30 с, ждёт конца). Коды — scenes: body:Сатурн, event:<id>, mission:…, astro:…, lapse:…, galaxy, pullback. Сама заставка («∞») — тот же генератор сцен в общем плеере: ⏮ ▶/⏸ ⏭, ☰ сыгранные и все сцены, 💾/⬇ — сыгранное сюжетом из шагов scene
scenesвсе сцены заставки по разделам с кодами
recordaction, fps, mbps / pathвидео сцены: start, stop (MCP сохраняет WebM в path)

Одна точка входа: WS, MCP, консоль, сюжет

Команды и язык одинаковы, откуда бы ни пришли: по WebSocket из любого языка, из MCP, из консоли страницы (кнопка >_ в полоске агента) или из сюжета. Можно написать готовый сценарий и отдать его story, а можно дёргать команды по одной. Новое: exec {steps} — выполнить шаги языка сразу (переменные живут между вызовами), expr {e} — посчитать выражение, js {code, timeout} — JavaScript в песочнице (изолированный iframe + Web Worker: без DOM, cookies и сети; внутри — await cmd(имя, args), await expr('…'), log(), sleep(), return).

Консоль: fly Марс size=0.6 (команда, первое слово — name), JSON-команда, шаги {…} / […], выражение = …, код js ….

Базовый скрипт — Node.js:

// node agent.mjs <код>   (Node ≥ 22 — WebSocket встроен)
const code = process.argv[2], ws = new WebSocket(`wss://space.bl2.ru/agent/${code}?r=agent`);
let id = 0; const wait = new Map();
const cmd = (name, args = {}) => new Promise((res, rej) => {
  const i = ++id; wait.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, cmd: name, args }));
});
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.sys) return;                                   // peer / pong
  const w = wait.get(m.id); wait.delete(m.id);
  m.ok ? w.res(m.result) : w.rej(new Error(m.error));
};
setInterval(() => ws.send('{"sys":"ping"}'), 30000);
ws.onopen = async () => {
  await cmd('fly', { name: 'Mars', size: 0.6, lit: true });
  console.log(await cmd('expr', { e: "round(au(get('Mars','sun')),3)" }));
  await cmd('exec', { steps: [{ wait: "dist('camera','Mars') < 1e5 km" }, { cmd: 'caption', args: { title: 'Mars' } }] });
  process.exit(0);
};

Python:

# python agent.py <код>    (pip install websockets)
import asyncio, json, sys, websockets
async def main(code):
    async with websockets.connect(f"wss://space.bl2.ru/agent/{code}?r=agent") as ws:
        n = 0
        async def cmd(name, **args):
            nonlocal n; n += 1
            await ws.send(json.dumps({"id": n, "cmd": name, "args": args}))
            while True:
                m = json.loads(await ws.recv())
                if m.get("id") == n:
                    if not m["ok"]: raise RuntimeError(m["error"])
                    return m["result"]
        await cmd("fly", name="Saturn", size=0.6, lit=True)
        print(await cmd("expr", e="get('Saturn','moons')"))
asyncio.run(main(sys.argv[1]))

Язык сценариев v2

Версии: сценарий без v (или v: 1) — шаги и слайды, как выше; v: 2 — язык с условиями. Новая версия обработчика старые не трогает.

{ "v": 2, "title": "…", "on": ["oplanets"], "vars": { "n": 0 }, "steps": [
  { "cmd": "fly", "args": { "name": "Марс", "size": 0.5, "lit": true } },
  { "wait": "dist('camera', 'Марс') < 50000 km", "timeout": 20 },
  { "set": { "n": "n + 1" } },
  { "if": "visible('Марс')", "then": [ { "cmd": "caption", "args": { "title": "Марс", "sub": "до Солнца {round(au(dist('camera','Солнце')),3)} а.е." } } ] },
  { "repeat": 2, "do": [ { "wait": 1 } ] }
] }

Шаги: {cmd, args, wait} (wait — мс или выражение), {wait: выражение | секунды, timeout}, {if, then, else}, {repeat: n | выражение, do}, {set: {имя: выражение}}, {on: [...]}. Выражения — без eval: числа с единицами km au ly m, строки '…', переменные, + - * / %, < > <= >= == !=, && || !; функции dist(a, b) ('camera' или тело, км), visible(тело), elapsed(), time(), camdist(), au(x), ly(x), round(x, d), abs min max. Свойства на лету: get(тело | 'camera', 'свойство') — у тела name, kind, radius, parent, moons, x y z, sun (км от Солнца), earth, camera (км от камеры), center, visible; у камеры dist, yaw, pitch, fov, x y z, sun, center; без свойства — весь объект. Результат команды — в переменную: {cmd, args, as: 'r'}, поле — field(r, 'rows.0.text'), размер — count(x). В строках args — {выражение}. Ошибки — в logs. Дальше: триггеры (when/once/every) и песочница (spawn тел и 3D-моделей). Свои функции: {def: имя, params, e} — в выражениях, {def, params, steps} — шагом {call, args, as}, {local}, {return}; в сюжете — funcs. Ещё функции: angle(a, b) — градусы между направлениями из камеры, edge(a, b) — на сколько градусов a видно за краем диска b, sin cos (градусы), pow sqrt. wait: true у команды — пока она доиграет. Для сцен: fitdist(r[, запас]) — с какого расстояния круг радиуса r км целиком в экране (по узкой стороне; у тела есть day — сутки, с); satcount('all' | 'active' | 'debris' | 'rb' | 'dead') — объекты каталога у Земли; mstat(аппарат, 'launch' | 'km' | 'turns' | 'rmin' | 'rnow' | 'flybys') — числа миссии из её траектории на сегодня. Траектории аппаратов и их пролёты — из импорта (scripts/fetch-missions.mjs, JPL Horizons, раз в сутки): новый аппарат в базе получает пролёты и числа сам; flybys {craft} — точки пролётов на пути. Медиа в сценах: media {url | file, title, x, y, w, h, volume, loop, start, id} — окно со звуком или видео: ссылка на mp3/видео или YouTube; file — файл с компьютера человека (сайт файлы не хранит: человек выбирает его в окне, дальше — без сети); close: true — закрыть окна. Состояние: remember {name} — запомнить камеру, время, вид и интерфейс; recall {name} — вернуть; onstop {restore: true} (в файле сюжета — поле "onstop": "restore") — при конце, Esc или ✕ вернуть всё как до запуска. Подскажи человеку: сюжет и заставку можно вести с пульта — кликер презентаций (PageDown/PageUp, «.» или B — чёрный экран), медиакнопки клавиатуры, наушников и ТВ-приставки (следующий, предыдущий, пауза), геймпад (крестик ←/→, A — пауза, B — чёрный экран), с клавиатуры — стрелки, пробел, Esc. Прямые ссылки: ?ambient — заставка, ?ambient=<код сцены из ☰> — с этой сцены (например ?ambient=landings:Moon:apollo), ?story=<id>&step=N или &slide=N — опубликованный сюжет с пункта; кнопка 🔗 в плеере копирует ссылку на то, что играет. Команда ambient {play: код} — то же из агента; reset — сброс камеры, времени и вида перед сценой.

Камера и движение

cmdargsчто
flyname, ms, ease, size, alt, lat, lon, rel, litПерелёт к телу одним движением сразу в ракурс: size — доля высоты экрана под диск, alt — высота над поверхностью, км, lat/lon — над точкой поверхности, rel: {to, deg, elev} — со стороны относительно другого объекта (0° — он за спиной, 180° — он за телом в кадре; elev — подъём). Ответ — ms.
cameradist, yaw, pitch, rel, ms, ease, targetКамера вокруг центра вида; rel — как у fly. target: {between: [a, b], k} — центр взгляда между телами; {sat: 25544} — центр облёта на спутнике (по NORAD); {sat, space: {tilt, spin, from, ms}} — вид со стороны космоса (Земля за спутником): наклон от вертикали tilt°, кружение spin °/с, подлёт с расстояния from (км) до dist за ms; null — центр обратно на тело.
mediaurl | file, kind, title, x, y, w, h, volume, loop, start, id, anchor, dx, dy, header, close, forgetDirОкно со звуком или видео: ссылка (mp3/видео/YouTube) или file — файл с компьютера (выбирает человек). anchor: {sat: NORAD} | {body: имя} — окно у объекта, ходит за ним (dx, dy — сдвиг, px); header: false — без строки заголовка (у привязанных — по умолчанию). Ответ ms — длительность для wait: true. close: true | id; forgetDir: true — забыть папку с медиа. Перед запуском сюжета — окно «Подготовка сценария» (файлы, предзагрузка ссылок).
ambientplayЗаставка «∞» сразу со сцены: код как в ☰ (hover|Mars, landings:Moon:apollo, event:eclSun, mission:Паркер, satellites…); без play — список кодов.
langsetЯзык интерфейса: без set — текущий и список (ru, en, es, zh, pt); set — переключить. Переключает на лету, без перезагрузки: агент остаётся подключён. Заставка играет текущую сцену заново на новом языке, плеер, карточка, дерево слоёв и окна перерисовываются. Адрес страницы становится /<язык>/ (например /es/; у языка корня домена — просто /) — язык задаёт только адрес, параметра ?lang= нет; ссылку на сайт на нужном языке — https://space.bl2.net/<язык>/ (русский — https://space.bl2.ru/).
reset—Перед сценой: стоп облёта, взгляда, смены времени; Галактика выкл; метки, точки пролётов, окна медиа — прочь (в сценах заставки вставляется сам).
remember / recallnameЗапомнить состояние (камера, время, вид, интерфейс) / вернуть.
onstoprestoreПо умолчанию включено: при конце, Esc, ✕ или уходе агента вернуть всё как до первой команды агента: слои и переключатели вида, панели и спрятанный интерфейс, камеру (ракурс, расстояние, центр, слежение), выбранное, время и скорость (или «вживую»); музыку и голос — стоп. Своё агента (sites, draw, mark, flybys, image, media, spawn) снимается всегда. restore: false (в файле сюжета — "onstop": "keep") — камеру, время и выбор оставить, как показал агент. У exec откат по концу — только при явном onstop {restore: true}.
consoleopenКонсоль >_ открыть / закрыть.
sitesbody | code, group, ids, light, onМеста посадок: карточки у точек на поверхности (в сцене). body можно не писать — тело по группе (apollo, luna, rovers, new, farside, first, china — Луна; mars, chryse — Марс; venera — Венера) или по ids. Пример: sites {group: 'apollo', light: true} → {lat, lon, alt, at, ids}; затем time {at, rate: 0} и fly {name: 'Moon', lat, lon, alt}. Без перелёта по lat/lon камера может смотреть на обратную сторону — карточек не будет видно. light: true — время, когда места на дневной стороне (ответ at).
flybyscraft, onТочки гравманёвров и выходов на орбиту на пути аппарата (из траектории миссии).
orbitspeed (°/с), turns | ms, pitch, dist, end, blend, ease, stopОблёт тела. end: {to, deg, elev} — кончить ровно на этом ракурсе (в одну сторону, без возврата). Кончился на ходу — докатывается, следующий fly подхватывает скорость.
lookto, ms, ease, clearВзгляд на объект: глаз на месте (или облётом), взгляд плавно поворачивается и следит. clear: true — обратно в центр.
select, shadow, camera targetname; name, by, center, ms, dist; targetДля сцен заставки: select {name} — путь тела ярко ('' — снять); shadow {name, by, center, ms, dist} — камера на пятне тени затмения; camera {target: {between: [a, b], k}, ms} — центр взгляда между телами (облёт — вокруг него), target: null — обратно; look {to: 'galcenter'}; fly {frame: {path, from, to, with}, elev, deg} — рамка на весь путь аппарата.
followname, ms, dist, easeСледить за другим объектом: плавный переезд с тем же ракурсом.

Показать маленькое тело (комету, межзвёздный объект — с десятков миллионов км его не видно): follow {name} или fly {name}, затем mark {name, label: true} — мигающий разноцветный маркер у тела (виден и при hideUi; ещё color, speed), select {name} — его путь ярко. Снять: mark {remove: name} или mark {clear: true}, select {name: ''}; при откате onstop и уходе агента маркеры снимаются сами.

Плавность ease — как timing function в CSS: linear, ease, ease-in, ease-out, ease-in-out, smooth, cubic-bezier(x1,y1,x2,y2) или формула от t (0…1): 1-pow(1-t,2), t*t*t*(t*(6*t-15)+10).

Внахлёст: {cmd, args, then: [шаги], before_end: мс | after_begin: мс | when: "условие"} — вложенные шаги идут одновременно с командой. Пример: облёт Земли, разворот на Ио, перелёт к ней:

[ {"cmd": "fly", "args": {"name": "Earth", "size": 0.4, "rel": {"to": "sun", "deg": 0, "elev": 10}, "ms": 3000, "ease": "ease-in-out"}, "wait": true},
  {"cmd": "orbit", "args": {"speed": 60, "ms": 6000, "ease": "ease-in"},
   "when": "edge('Io','Earth') < 15", "then": [{"cmd": "look", "args": {"to": "Io", "ms": 1500}, "wait": true}], "wait": true},
  {"cmd": "fly", "args": {"name": "Io", "size": 0.15, "rel": {"to": "Jupiter", "deg": 180, "elev": 15}, "ms": 8000, "ease": "ease-out"}, "wait": true} ]

Песочница, графика, космонавт

cmdargsчто
spawnid, like, diameter, color, at: {near, dist, dir} | pos, velСвой объект: копия тела или глыба; встаёт на круговую орбиту вокруг главного тела (законы, не прямые)
moveid, vel | impact: {target, at}Новая скорость или «попасть в тело к моменту»
despawn / objectsid | allУбрать / список
drawitems | clear | removeГрафика в пространстве для отладки и уроков: arrow (vel, sun, north, {sum}), axes (крен/тангаж/рыскание), line, dash, path, angle (дуга и градусы), project (проекция на тело, высота), point, text; label — текст вдоль линии
flybyscraft, onТочки гравманёвров на пути аппарата: «Тело · дата» и что дал манёвр; on: false — убрать
sitesbody | code, group, ids, light, onМеста посадок (Луна, Марс, Венера, Титан): точки и карточки в сцене — фото, название, год, описание; group — совмещённый вид (apollo, luna, rovers, new, farside, first, china, mars, chryse, venera; тело — по группе, body не обязателен); light: true — места на дневной стороне; ответ — lat, lon, alt для fly; on: false — убрать
astroaction: goto | view | shield | land | exit, name, tpd, yaw, pitch, on, textКосмонавт: якорь на что угодно (тело, МКС, номер NORAD, астероид); view — вид на фигурку; shield — снять/поставить щит, своя надпись

Консоль страницы — клавиша ё.

Как работать

  1. Прочитать эту страницу (help), connect с кодом.
  2. Спросить у страницы данные: search / open (объекты), body (размеры, освещённая сторона), where (положения), find (события), layers, state.
  3. По этим данным подготовить действия: один сценарий play, или презентацию deck, которую человек листает сам, или свои buttons.
  4. Запустить; проверить по screenshot и logs; поправить.

5. Пример: «расскажи мне про планеты»

connect {code}
layers → layer {key, visible:false} для всего, кроме oplanets
music {theme: 0, volume: 0.4};  hide_ui {on: true}
play {steps: [
  {cmd:"fly", args:{name:"Марс"}, wait: 3800},
  {cmd:"camera", args:{yaw: …, pitch: …, dist: …, ms: 3000}, wait: 3200},   // значения — из body {name:"Марс"}
  {cmd:"say", args:{title:"Марс", sub:"228 млн км · свет идёт 13 минут", text:"…", audio:"mars.mp3"}, wait: "speech"}, // голос — Piper в браузере (ru ruslan, en amy); audio — готовый mp3 вместо него
  …
]}

6. Ресурсы

Правила: страница принимает только эти команды; никаких переходов на другие сайты и личных данных. Управление — у человека: он может нажать «Отключить» в любой момент.