Экспорт анимации
--animate превращает временной ряд прогона в нечто движущееся: анимированный файл Scalable Vector Graphics (SVG), один самодостаточный проигрыватель на HyperText Markup Language (HTML) или пронумерованную последовательность кадров SVG для видеокодера. Кампания воспроизводится по своим фазам (подавление, спуфинг, holdover, тревога целостности) с отметками тревог; прогон спектра воспроизводит свою водопадную диаграмму строка за строкой; пролёт на низкой околоземной орбите (LEO) воспроизводит свою линию связи по диапазонам и, при наличии спуфера, статистики монитора эпоха за эпохой.
kshana scenarios/campaign-jam-spoof-holdover-integrity.toml --animate all
kshana scenarios/l-band-waterfall-jamming.toml --animate html
kshana scenarios/clock-holdover.toml --animate frames --animate-fps 24 --animate-duration 10
Код: src/animation.rs. Тесты: tests/animation.rs.
Что это такое и чем не является#
Экспортёр читает документ результата, который прогон уже записал. Он не пересчитывает физику, не передискретизирует, не сглаживает и не интерполирует между отсчётами (кроме одного прямолинейного сегмента, которым трасса заканчивается у курсора) и не добавляет никаких собственных чисел. Поэтому анимация настолько хороша, насколько хорош прогон, на котором она основана. Её уровень — MODELLED, внутренняя согласованность: это визуализация, а не доказательство, и у неё нет собственной строки матрицы верификации.
Результаты#
Каждый формат записывается рядом со сценарием, возле обычных result.json, chart.svg и report.html:
| Формат | Записывается как | Что содержит |
|---|---|---|
svg |
<scenario>.animation.svg |
Один SVG, анимированный ключевыми кадрами Cascading Style Sheets (CSS), без скриптов: трассы прорисовываются за движущимся курсором времени, события появляются в свой момент, строки водопада раскрываются в порядке времени. Анимация зациклена, с задержкой 1,5 с на готовой картинке. |
html |
<scenario>.animation.html |
Один файл со встроенными скриптом и стилем и без внешних ресурсов: воспроизведение и пауза, ползунок перемотки, скорость (от 0.25x до 4x), все панели на одном синхронизированном курсоре, текущее значение для каждой трассы, полоса фаз и список событий, перематывающий по щелчку. Пробел запускает или приостанавливает, стрелки делают шаг, Home и End переходят к началу и концу. |
frames |
<scenario>.frames/ |
frame_0000.svg, frame_0001.svg, … и manifest.json. |
--animate можно повторять, и он принимает список через запятую (--animate svg,html) или all. --animate-fps (от 1 до 60, по умолчанию 12) и --animate-duration (от 0,5 с до 600 с, по умолчанию 8 с) задают воспроизведение; последовательность кадров ограничена 7 200 кадрами.
Когда --export запускается в той же команде, проигрыватель HTML перечисляет рядом записанные файлы экспорта (CZML, KML, GeoJSON, STK, SigMF) как относительные ссылки на эти соседние файлы; ссылка пишется только для простого имени файла, поэтому ни одна не может выйти за пределы папки или обратиться к сети. Расширенный отчёт (report.html) встраивает анимированный рисунок всякий раз, когда в результате есть временной ряд, независимо от того, запускался ли --animate.
Когда запускается --animate, в result.json добавляется блок animation: форматы, число кадров в секунду (fps), длительность, число кадров, интервал времени, заголовки панелей, пути JSON, из которых прочитана каждая трасса (sources), и любые найденные, но не нарисованные ряды (omitted). Без --animate результат побайтно идентичен обычному прогону.
Последовательность кадров#
Последовательность содержит ровно round(duration × fps) кадров. Кадр i показывает временную шкалу до t_start + (t_end − t_start) × i / (n − 1), так что первый кадр — это начало, а последний — готовая картинка. В manifest.json записаны fps, duration_s, frame_count, frame_pattern (frame_%04d.svg), размер в пикселях, t_start, t_end, время миссии каждого кадра (frame_times), фазы, события и источники. Повторный экспорт в тот же каталог сначала удаляет все уже имеющиеся там frame_NNNN.svg (и ничего больше), поэтому более короткая последовательность никогда не оставляет кадры более длинной, которые мог бы прочитать кодировщик.
Кадры представлены в SVG, а не в Portable Network Graphics (PNG). ffmpeg, собранный с библиотекой librsvg, читает их напрямую, с той частотой, с которой записана последовательность (24 в примере выше; fps манифеста):
ffmpeg -framerate 24 -i clock-holdover.frames/frame_%04d.svg -pix_fmt yuv420p clock-holdover.mp4
ffmpeg без librsvg останавливается с сообщением «no decoder found for: svg». Сначала растеризуйте кадры, например с помощью rsvg-convert, и закодируйте PNG:
for f in clock-holdover.frames/frame_*.svg; do rsvg-convert "$f" -o "${f%.svg}.png"; done
ffmpeg -framerate 24 -i clock-holdover.frames/frame_%04d.png -pix_fmt yuv420p clock-holdover.mp4
Доступность и тема оформления#
- Цветовая схема. Проигрыватель следует
prefers-color-schemeи перерисовывается при её изменении; анимированный SVG содержит светлый вариант своей палитры под тем же медиазапросом. Кадры используют тёмную палитру статичных графиков, потому что у видео нет схемы. - Уменьшенное движение. При
prefers-reduced-motion: reduceанимированный SVG отключает все анимации и показывает готовую картинку (его базовые стили соответствуют конечному состоянию, поэтому ничего не скрыто), а проигрыватель открывается на паузе на готовой картинке вместо воспроизведения; пользователь по-прежнему может перематывать или нажать «Воспроизвести». - У SVG есть
<title>и<desc>; элементы управления проигрывателя — кнопки с подписями, поле диапазона с озвучиваемым значением времени и список выбора.
Что считается временным рядом#
Документы результатов различаются от вида к виду, поэтому экспортёр распознаёт формы, которые выдаёт движок, а не список видов:
- Массив записей с полем времени (
t,t_s,t_hours,t_min,t_daysилиkв массивеepochs), строго возрастающим вдоль массива. Каждое другое числовое поле становится трассой; одно и то же поле под двумя корнями (quantum.series[].error_nsиclassical.series[].error_ns) делит одну панель. - Объект со строго возрастающим массивом времени (
t_s,t,times_s, …) рядом с массивами той же длины: соседние числовые массивы, картаchannelsзаписей{unit, values}(временная шкалаcampaign), массив записей, содержащих такие массивы (диапазоныspectrum, треки спутников орбитальной группировки), и двумерный массив, строки которого соответствуют оси времени, с соседней осью частот*_hz(водопадspectrum). - Помимо этого массива времени,
phases(name,t0_s,t1_s) становятся полосой фаз, аevents(t_s,label,alarm) становятся маркерами. Скаляры*_t_sдиапазона (его первая потеря захвата, его наименьшее отношение мощности несущей к спектральной плотности шума) тоже отмечаются.
Трассы с единицей измерения делят панель, когда их величины различаются не более чем в 100 раз, так что защитный интервал 50 нс не сплющивает ошибку 2 нс. Рисуется не более 6 панелей по 6 трасс; остальные перечисляются в omitted. Трассы длиннее 2 400 отсчётов прореживаются равномерным шагом, сохраняющим последний отсчёт, а водопад размером более 96 × 120 ячеек усредняется (по мощности, для ячеек в децибелах, чтобы узкий передатчик помех не размывался).
Вид, результат которого не содержит ни одной из этих форм (бюджет линии связи, геометрия одной эпохи, сводка Монте-Карло только со статистикой или телекоммуникационный holdover, результат которого публикует хеш записи, но не саму запись), отклоняется с сообщением no time series to animate, и ничего не записывается. В kshana 0.40.0 94 из 193 запускаемых файлов сценариев (все файлы, кроме набора исследований) анимируются, а 99 отклоняются. cargo test --test animation -- --nocapture выводит перепись.
Среди видов LEO анимируется каждый поставляемый сценарий leo-pass, leo-pnt-chain и leo-ppp. leo-pvt анимируется в режимах Доплера, совместного позиционирования и синхронизации (для прогона синхронизации нужен trace = true, из-за чего каждая строка сообщает ошибку времени и прогнозируемую сигму в каждую эпоху) и отклоняется в режиме полярного покрытия. leo-navmsg анимирует своё обновление в середине пролёта и отклоняется для анализов кодирования, интервала подгонки и сравнения моделей. Результаты leo-signal и ntn-positioning не содержат оси времени и отклоняются.
Детерминированность#
Результат — чистая функция документа результата и параметров: ни часов, ни случайных чисел, ни обхода хеш-таблицы, ни доступа к файлам или сети в библиотеке и нигде никакой метки времени, поэтому один и тот же сценарий, начальное значение (seed) и параметры дают побайтно идентичные файлы. Модуль без изменений собирается для WebAssembly; файлы записывает только интерфейс командной строки.
Использование как библиотеки#
use kshana::animation::{AnimationFormat, AnimationOptions};
let anim = kshana::api::animate_toml(src, AnimationFormat::Html, &AnimationOptions::default())?;
std::fs::write("run.html", &anim.files[0].content)?;
kshana::animation::animate_result анимирует уже имеющийся у вас документ результата, а extract_timeline возвращает то, что было бы нарисовано, без визуализации.
Ограничения#
- Колесо Python и модуль WebAssembly пока не предоставляют экспортёр; он доступен из командной строки, из библиотеки Rust и через инструмент
animate_scenarioсервера Model Context Protocol (MCP) (который возвращает в одном ответе не более 120 кадров). - Оси линейные. Ряд, охватывающий много порядков (рост ошибки часов от 0,1 нс до микросекунды), выглядит плоским почти до конца прогона.
- Анимированный SVG опирается на CSS-анимацию обрезающего прямоугольника; современные движки Chromium, Firefox и Safari воспроизводят её, а средство просмотра без CSS-анимации показывает готовую картинку.
- Кампании перебора, Монте-Карло и композиции не содержат оси времени и отклоняются; сводная статистика перебора или ансамбля не является временным рядом. Цепочечная кампания анимируется.