Сценарии использования симулятора: акторы, потоки, границы¶
Этот раздел описывает кто взаимодействует с симулятором «мир БВС», зачем и какими командами/API получает проверяемый результат. Текст рассчитан на три аудитории: жюри и заказчик (суть за 10 минут), новый разработчик (воспроизводимый запуск), оператор и аналитик (шаги и коды ошибок). Утверждения сверены с кодом ветки docs2/sim-usecases; там, где функции нет, это сказано явно — см. краткий перечень ограничений и LIMITATIONS.md.
Соседние разделы: зачем симулятор · архитектура · API (черновик) · эксплуатация · сценарии планировщика.
Акторы и артефакты¶
| Актор | Цель | Типичный вход | Что получает на выходе |
|---|---|---|---|
Планировщик работ (человек или сервис geoscan) |
Проверить план до вылета | JSON полётного задания (schema_version, missions[], sorties[], фазы survey/takeoff/…) |
fact_report.json, журнал (telemetry.jsonl, events.jsonl, frames.jsonl), коды preflight |
| Разработчик / CI | Доказать, что физика и геометрия не «поплыли» | sim validate --reference, sim identities, pytest |
Таблица V-1…V-14, PASS/FAIL, ненулевой RC при регрессии |
| Инженер качества | Поймать слепые зоны валидатора | sim mutate, мутанты в fixtures/mutants/ |
Матрица «мутант → код нарушения → detected» |
| Оператор стенда | Прогнать план на сервере, отдать URL команде | sim serve, POST /api/v1/runs |
run_id, статус queued → running → done, ссылки на артефакты через API |
| Исследователь / жюри | Увидеть пролёт и цифры «план vs факт»; оценить план по кодам и эталонам | Веб-клиент simulate/web/ (mock или VITE_SIM_API=api) |
3D/2D сцена, телеметрия, таймлайн, «Покрытие» / «Эталоны»; экран «Вердикт» есть, но HTTP всегда отдаёт UNRESOLVED до B6 (api/verdict.py:26-29) — для приёмки смотреть preflight_codes и V-1…V-14 |
Вне границ симулятора: построение галсов и оптимизация парка (планировщик), управление реальным БВС, выгрузка в S3, ночной sim batch (не реализован).
Граница системы¶
На диаграмме — что входит в продукт simulate/** и что остаётся снаружи. Зависимость от планировщика односторонняя: симулятор импортирует план как файл/JSON (mission/import_plan.py:1), обратных вызовов в geoscan нет (simulate/README.md:20-22).
flowchart TB
subgraph external [Вне simulate]
GS[geoscan — планировщик]
OP[Оператор поля]
CREDS[creds / стенд k3s]
end
subgraph simulate [Симулятор simulate]
CLI[CLI sim]
API[FastAPI /api/v1]
ENG[HeadlessIntegrator + WorldRuntime]
VAL[validator V-1…V-14 / mutate]
WEB[web — просмотр журнала]
OUT[(simulate-out / data/runs)]
end
GS -->|mission JSON KML GeoJSON| CLI
GS -->|plan inline или plan_ref| API
OP -->|браузер| WEB
CLI --> ENG
API --> ENG
ENG --> OUT
VAL -.->|эталоны без плана| CLI
WEB -->|mock fixtures или REST| API
CREDS -.->|API key Bearer| API
Вывод: единственный обязательный «контракт» с планировщиком — формат полётного задания и каталоги fleet.yaml / payloads.yaml (совместимость нагрузки проверяется до прогона, cli/__init__.py:132-141). Всё остальное — внутренняя модель мира и журнала.
Коды выхода CLI и смысл для оператора¶
Реестр: cli/exit_codes.py:10-14, логика после preflight: cli/exit_codes.py:30-42, should_run_with_preflight: cli/exit_codes.py:45-51.
| Код | Имя | Когда |
|---|---|---|
| 0 | OK | Прогон завершён, список preflight_codes пуст |
| 1 | FATAL | План не принят или прогон не стартовал (fatal preflight, несовместимая нагрузка — cli/__init__.py:119-122, 132-141) |
| 2 | VIOLATIONS | Прогон выполнен, но есть violation/warning в preflight_codes (типично мутант с ENDURANCE_EXCEEDED) |
| 3 | INTERNAL | Исключение при прогоне, обёрнутое в INTERNAL_ERROR (cli/__init__.py:143-152). В том числе: повторный sim run в тот же --output без очистки → run_id already exists (journal/writer.py:259-265), exit 3 (не FATAL) |
Структурированные ошибки печатаются JSON-строками в stdout (cli/__init__.py:27-30).
Жизненный цикл прогона (HTTP)¶
Асинхронный прогон через API хранит метаданные в api_meta.json и переводит статусы в RunService (api/run_store.py:122-211). CLI sim run этот граф не проходит — он синхронно пишет артефакты в --output.
stateDiagram-v2
[*] --> queued: POST /api/v1/runs 202
queued --> running: worker стартовал
running --> done: subprocess ok
running --> failed: exception или ok=false
done --> [*]
failed --> [*]
note right of queued
Ответ 202 может вернуть status queued
пока meta уже running
run_store.py:167-170
end note
Вывод: оператору стенда нужно опрашивать GET /api/v1/runs/{id} до done или failed (tests/test_api.py:115-124), а не полагаться только на первый ответ 202.
Сквозной поток прогона (CLI)¶
sequenceDiagram
participant U as Пользователь
participant CLI as sim run
participant C as contracts + import_plan
participant P as analyze_plan preflight
participant R as run_plan_headless
participant J as RunJournalWriter
U->>CLI: sim run plan.json --output DIR
CLI->>C: validate_plan_contract + load_and_validate_plan
alt контракт невалиден
C-->>CLI: structured_error
CLI-->>U: exit 1
end
CLI->>P: analyze_plan strict_zone_frames
alt только fatal без violation
P-->>CLI: codes
CLI-->>U: exit 1 без прогона
end
CLI->>R: integrator + world + sorties
R->>J: telemetry events frames fact_report
R-->>CLI: summary JSON
CLI->>CLI: preflight_exit_code
CLI-->>U: exit 0 или 2 + summary
Порядок в коде: cli/__init__.py:66-159, ядро прогона: mission/runner.py:187-320.
Путь исследователя (веб-клиент)¶
По умолчанию UI работает без бэкенда: фикстура s01, seed 42 (docs/92-api/02-user-guide.md:4-5, web/src/config/runtime.ts:3-11). Live-режим: VITE_SIM_API=api и запущенный sim serve.
journey
title Просмотр результата прогона (исследователь)
section Подготовка
Установить npm зависимости: 3: Разработчик
npm run dev на :5173: 4: Разработчик
section Экран Прогон
Открыть 3D и 2D карту: 5: Исследователь
Запустить плеер 1x–30x: 5: Исследователь
Сверить plan vs fact в панели: 4: Исследователь
section Анализ
Перейти в Покрытие GSD: 4: Исследователь
Открыть Эталоны V-1…V-14: 4: Жюри
Экран Вердикт — только UNRESOLVED: 2: Жюри
Сверить preflight_codes и RC sim run: 4: Жюри
Маршруты навигации: web/src/App.tsx:17-34. Экран «Вердикт» в UI подключён, но трёхзначный NO_GO / NO_FINDINGS в этой ветке не вычисляется: GET /api/v1/runs/{id}/verdict и mock-фикстура web/src/fixtures/mocks/verdict.json возвращают только "verdict": "UNRESOLVED" с блокером VERDICT_ENGINE_INCOMPLETE (api/verdict.py:20-29). Жюри и заказчик для GO/NO-GO по факту опираются на коды preflight, эталоны UC-1 и артефакты прогона, а не на готовый вердикт API.
UC-1. Регрессия эталонов V-1…V-14 (разработчик / CI)¶
| Поле | Содержание |
|---|---|
| Цель | Убедиться, что якорные формулы (endurance, GSD, парашют V-10, детерминизм sha256) совпадают с допусками |
| Предусловия | Установлен пакет geoscan-sim (uv sync в simulate/), доступна команда sim |
| Основной поток | 1. Выполнить sim validate --reference. 2. Убедиться, что все строки PASS. 3. Код выхода 0 |
| Альтернативы | sim identities — быстрый smoke по алгебраическим тождествам (cli/__init__.py:220-227) |
| Исключения | sim validate без флага → подсказка и exit 2 (cli/__init__.py:45-47) |
| Результат | Таблица 14 метрик; при FAIL — ненулевой RC (validator/runner.py через reference_exit_code) |
Команда (выполнено в сессии документирования):
cd simulate
uv sync -q
.venv/bin/sim validate --reference
Фрагмент вывода:
V-1 Gemini endurance calm cruise 40.01 40 -0.009217 ±5% PASS
...
V-14 Determinism 10× identical sha256 0 0 +0 exact match PASS
Код выхода: 0.
UC-2. Headless-прогон эталонного плана s01 (аналитик плана)¶
| Поле | Содержание |
|---|---|
| Цель | Получить факт прогона для одного sortie Gemini: журнал, сухие кадры, fact_report |
| Предусловия | Файл fixtures/plans/s01.json, пустой или новый каталог --output |
| Основной поток | 1. sim run fixtures/plans/s01.json --seed 42 --output <DIR>. 2. Дождаться JSON-summary в stdout. 3. Открыть fact_report.json и run_manifest.json |
| Альтернативы | --world m0-synthetic-flat (дефолт, cli/__init__.py:70) · другой синтетический мир из каталога worlds API |
| Исключения | Повторный запуск в тот же --output без очистки → run_id already exists, exit 3 (journal/writer.py:259-265) |
| Результат | preflight_codes: [], exit 0; sorties обрабатываются последовательно (mission/runner.py:35-47, 235-265) |
Команда:
rm -rf /tmp/sim-doc-out
cd simulate
.venv/bin/sim run fixtures/plans/s01.json --output /tmp/sim-doc-out --seed 42
Фрагмент вывода:
{
"fact_report": "/tmp/sim-doc-out/fact_report.json",
"frames_written": 380,
"preflight_codes": [],
"telemetry_sha256": "e11273715f65affb7987923aeb444d85ded8c8919264af05a2cf446cf4b62371",
"world_snapshot_id": "24e205faab997a835132abc87aeaf74d8e2760e528ec35d2c013c277af0c11a3"
}
Код выхода: 0. Время прогона на станции документирования: ~75 с (зависит от CPU).
Проверка коллизии run_id (тот же --output, без rm):
cd simulate
.venv/bin/sim run fixtures/plans/s01.json --output /tmp/sim-doc-out --seed 42
Фрагмент вывода:
{"code": "INTERNAL_ERROR", "message": "run_id already exists: 7d77fbdf-2ebe-5e16-8c7b-3e0787886d85", "path": "fixtures/plans/s01.json", "severity": "fatal"}
Код выхода: 3 (согласовано с таблицей RC выше; отдельный код RUN_ID_COLLISION в реестре пока не выделен).
UC-3. Прогон плана с нарушением endurance (контроль качества)¶
| Поле | Содержание |
|---|---|
| Цель | Показать, что симулятор не блокирует прогон при violation-кодах, но помечает их в отчёте и возвращает RC=2 |
| Предусловия | Мутант fixtures/mutants/m08_endurance_201.json (ожидание зафиксировано в tests/defects/test_a9_exit.py:13-17) |
| Основной поток | 1. Очистить --output. 2. sim run … m08 …. 3. Прочитать JSON ENDURANCE_EXCEEDED в stdout. 4. Проверить exit 2 |
| Альтернативы | Только preflight без движка: POST /api/v1/preflight с телом {"plan":…} (api/routes/preflight.py:16-38) |
| Исключения | Fatal-only кейс (несовместимая нагрузка) → прогон не стартует, exit 1 (tests/test_exit_codes.py:55-59) |
| Результат | Артефакты на диске есть, preflight_codes содержит violation; CI может трактовать RC=2 как «красный» прогон |
Команда:
rm -rf /tmp/sim-m08
cd simulate
.venv/bin/sim run fixtures/mutants/m08_endurance_201.json --output /tmp/sim-m08 --seed 42
Фрагмент вывода:
{"code": "ENDURANCE_EXCEEDED", "message": "Длительность вылета превышает endurance с учётом резерва", "path": "", "severity": "violation"}
Код выхода: 2. Объём телеметрии велик (длинный sortie); для оператора важнее коды, чем размер telemetry.jsonl.
Контраст — fatal до прогона:
.venv/bin/sim run fixtures/mutants/m06_ir_on_gemini.json --output /tmp/sim-m06
{"code": "PAYLOAD_INCOMPATIBLE", "message": "Нагрузка несовместима с бортом", "path": "", "severity": "fatal"}
Код выхода: 1 (cli/__init__.py:119-122, 132-141).
UC-4. Прогон через HTTP API (оператор стенда)¶
| Поле | Содержание |
|---|---|
| Цель | Поставить прогон в очередь сервиса, получить run_id, дождаться done, отдать фронту данные по REST |
| Предусловия | Запущен sim serve (порт 8080 по умолчанию, cli/__init__.py:167-168) или тестовый TestClient; для прод-стенда — API key (cli/__init__.py:171-206) |
| Основной поток | 1. POST /api/v1/runs с plan или plan_ref. 2. Ответ 202 + run_id. 3. Poll GET /api/v1/runs/{id} до status=done. 4. GET …/telemetry, …/fact_report, …/verdict |
| Альтернативы | plan_ref: "s01" → файл из fixtures/plans/ (api/run_store.py:83-94, tests/test_api.py:248) |
| Исключения | Очередь полна → 429 RUN_QUEUE_FULL (api/routes/runs.py:74-79) · неизвестный мир → 404 при resolve (WorldNotFoundError) |
| Результат | Артефакты в simulate/data/runs/<run_id>/ (дефолтный каталог, api/app.py:43-44) |
Воспроизведение без долгого serve (тот же код приложения, что и в pytest):
cd simulate
.venv/bin/python -c "
from fastapi.testclient import TestClient
from geoscan_sim.api.app import create_app
import json, time
from pathlib import Path
app = create_app(auth_disabled=True)
c = TestClient(app)
plan = json.loads(Path('fixtures/plans/s01.json').read_text())
print('preflight', c.post('/api/v1/preflight', json={'plan': plan}).status_code,
c.post('/api/v1/preflight', json={'plan': plan}).json())
r = c.post('/api/v1/runs', json={'plan': plan, 'world_id': 'plane_seed_42', 'seed': 42})
print('create', r.status_code, r.json()['run_id'], r.json()['status'])
rid = r.json()['run_id']
for _ in range(400):
g = c.get(f'/api/v1/runs/{rid}')
if g.json()['status'] in ('done','failed'):
print('final', g.json()['status'], 'exit', g.json().get('exit_status'))
break
time.sleep(0.25)
"
Фактический вывод (сокращённо):
preflight 200 {'codes': [], 'warnings': ['OBSTACLE_LAYER_INCOMPLETE'], 'violations': {}}
create 202 93cb304c-8c75-44e1-9322-80af2f1a9d98 queued
final done exit completed
После status=done запрос вердикта (тот же TestClient, auth_disabled=True):
# внутри того же python -c после poll: c.get(f'/api/v1/runs/{rid}/verdict')
Фрагмент ответа GET /api/v1/runs/{run_id}/verdict (проверено в сессии доработки):
{
"verdict": "UNRESOLVED",
"layer_completeness": { "obstacles": 0.0, "zones": 0.0 },
"blockers": [
{
"code": "VERDICT_ENGINE_INCOMPLETE",
"message": "Трёхзначный вердикт заблокирован до закрытия fidelity_class, layer_completeness и V3 (чеклист §7.2)."
}
]
}
HTTP 200; значение NO_GO / NO_FINDINGS API не возвращает (api/verdict.py:26-29).
Запуск реального сервера (для оператора с браузером):
cd simulate
.venv/bin/sim serve --no-auth --port 8080
# другой терминал: cd simulate/web && VITE_SIM_API=api npm run dev
Статика web/dist монтируется, если каталог собран (api/app.py:80-82).
UC-5. Мутационная матрица (инженер качества / ревьюер)¶
| Поле | Содержание |
|---|---|
| Цель | Проверить, что известные дефекты планов детектируются preflight/прогоном (регрессия валидатора) |
| Предусловия | Набор fixtures/mutants/, манифест fixtures/mutants/mutants_manifest.yaml |
| Основной поток | 1. sim mutate. 2. Убедиться, что для каждого мутанта detected=True. 3. Exit 0 |
| Альтернативы | HTTP POST /api/v1/validate/mutants (см. tests/test_api.py:225) |
| Исключения | Ложный PASS базового s04 в матрице → exit 1 (cli/__init__.py:58-61) |
| Результат | Таблица кодов (NFZ_VIOLATION, COVERAGE_GAP, …) |
Команда:
cd simulate
.venv/bin/sim mutate
Хвост вывода:
m25 NFZ_VIOLATION True NFZ_VIOLATION
m26 PARACHUTE_OBSTACLE_CONFLICT True PARACHUTE_OBSTACLE_CONFLICT
Код выхода: 0.
UC-6. Мультибортовый план s04 (планировщик парка)¶
| Поле | Содержание |
|---|---|
| Цель | Прогнать план с несколькими missions/sorties подряд в одном каталоге артефактов |
| Предусловия | fixtures/plans/s04.json, чистый --output |
| Основной поток | sim run fixtures/plans/s04.json --seed 42 --output <DIR> |
| Ограничение | Борта летят последовательно в sim-time, не параллельно (mission/runner.py:235-265; см. limitations-summary) |
| Результат | Больше кадров, чем s01; exit 0 при чистом preflight (tests/test_exit_codes.py:26-36) |
Команда:
rm -rf /tmp/sim-doc-s04
cd simulate
.venv/bin/sim run fixtures/plans/s04.json --output /tmp/sim-doc-s04 --seed 42
Фрагмент summary: "frames_written": 2230, "preflight_codes": [], exit 0 (~5 мин на станции документирования).
Сводка: что выбрать¶
| Задача | Сценарий | Интерфейс |
|---|---|---|
| «Физика не сломалась?» | UC-1 | sim validate --reference |
| «Этот план из geoscan пролетит?» | UC-2, UC-3 | sim run |
| «Нужен URL и UI для команды» | UC-4 | sim serve + web |
| «Валидатор ловит мутанты?» | UC-5 | sim mutate |
| «Несколько бортов в одном JSON» | UC-6 | sim run s04 |
Чего симулятор пока не умеет (для сценариев)¶
Кратко: limitations-summary.md. Полный реестр: LIMITATIONS.md.
Проверка отсутствия batch:
cd simulate && .venv/bin/sim batch --help
Error: No such command 'batch'.
Карта файлов кода (точки входа)¶
| Интерфейс | Файл |
|---|---|
CLI run / serve / validate / mutate |
simulate/src/geoscan_sim/cli/__init__.py |
| Headless-прогон | simulate/src/geoscan_sim/mission/runner.py |
| Preflight | simulate/src/geoscan_sim/mission/preflight.py |
| HTTP runs | simulate/src/geoscan_sim/api/routes/runs.py, api/run_store.py |
| Веб-маршруты | simulate/web/src/App.tsx |