Аудит SIM-MIS (40): полётное задание, исполнение, журнал, отчёт¶
Дата: 2026-09-16 · Ветка: feat/simulate-audit · Код: simulate/ (84 файла вне docs/)
Требования: simulate/docs/50-mission/01-mission-execution.md (строки 567–606 — таблица SIM-MIS-01…40),
головной реестр simulate/docs/90-final/02-requirements-registry.md (строки 265–304).
Метод: чтение кода + прогоны .venv/bin/sim, .venv/bin/pytest, grep. Вердикт «реализовано» — только с местом в коде или выводом команды.
1. Сводка числами¶
| Статус | Кол-во | Доля |
|---|---|---|
| РЕАЛИЗОВАНО | 0 | 0 % |
| ЧАСТИЧНО | 7 | 17,5 % |
| РАСХОЖДЕНИЕ | 4 | 10 % |
| НЕ РЕАЛИЗОВАНО | 28 | 70 % |
| НЕ ПРОВЕРЕНО | 0 | 0 % |
| ТРЕБОВАНИЕ НЕГОДНОЕ | 1 | 2,5 % |
| Всего проверено | 40 | 100 % |
По вехам (вехи — из головного реестра, строки 265–304):
| Веха | Всего | РЕАЛИЗОВАНО | ЧАСТИЧНО | РАСХОЖДЕНИЕ | НЕ РЕАЛИЗОВАНО | НЕГОДНОЕ | % полностью закрытых |
|---|---|---|---|---|---|---|---|
| M0 | 13 | 0 | 3 (04, 16, 23) | 4 (03, 19, 22, 28) | 5 (01, 06, 17, 20, 33) | 1 (25) | 0 % |
| M1 | 19 | 0 | 4 (10, 14, 21, 34) | 0 | 15 | 0 | 0 % |
| M2 | 8 | 0 | 0 | 0 | 8 | 0 | 0 % |
Ни одно требование домена «миссия» не закрыто полностью — включая все 13 требований M0, которые по BRD являются MUST и входят в критерий выхода M0. Затронуто кодом (частично или с расхождением) 8 из 13 M0.
Отдельно по трём пунктам, которые велено проверить особо:
| Проверка | Итог |
|---|---|
Журнал против contracts/log_schema.json |
Манифест: нет 9 из 14 обязательных полей. Телеметрия: нет 4 из 4 обязательных (t_sim_s, uav_id, lon, lat), присутствует 3 поля из 31. Поток событий отсутствует целиком (0 из 34 типов). Схема в рантайме не проверяется. |
| Детерминизм (два прогона, один seed) | Телеметрия — бит-в-бит (d348f1ae… дважды). Манифест — не бит-в-бит (generated_at = системные часы). PRNG в коде нет вовсе: --seed 42 и --seed 777 дают один и тот же telemetry_sha256. |
| Приём настоящего KML от планировщика | Нет. grep -rniE "kml\|geojson\|kmz\|xml" src/ tests/ tools/ — 0 совпадений; файлов .kml/.geojson в репозитории нет. Принимается только внутренний JSON нашей схемы. |
2. Чеклист по всем 40 требованиям¶
| ID | Требование (кратко) | Веха | Статус | Доказательство | Что расходится и чем грозит |
|---|---|---|---|---|---|
| SIM-MIS-01 | Вход — комплект (scenario, plan, world_snapshot_id, run_config); план без сценария не исполняется, SCENARIO_MISSING |
M0 | НЕ РЕАЛИЗОВАНО | sim run --help → опции только --seed, --output, --world; src/geoscan_sim/cli.py:50-61. grep -rn SCENARIO_MISSING src/ tests/ tools/ → 0 (код есть только в contracts/error_codes.yaml:15). run_config в коде отсутствует как понятие. sim run fixtures/plans/s01.json без сценария → RC=0 |
Исполняется план без сценария: нет зон, нет ВП, нет ветра, нет площадок → все нарушения, ради которых существует симулятор, физически невычислимы. Прямое нарушение M0-MUST |
| SIM-MIS-02 | Импорт: наша схема + GeoJSON RFC 7946 + KML OGC 2.2 обязательны | M1 | НЕ РЕАЛИЗОВАНО | grep -rniE "kml\|geojson\|kmz\|xml" src/ tests/ tools/ → rc=1, 0 совпадений. find . -name "*.kml" -o -name "*.geojson" → пусто. src/geoscan_sim/mission/import_plan.py:16-22 — только json.load |
Планировщик по ТЗ выдаёт KML/GeoJSON (INH-01, R-OUT-2/3). Симулятор не может принять ни один реальный артефакт планировщика — стыковка двух половин продукта не существует |
| SIM-MIS-03 | Фрейм высоты обязателен; clampToGround — фатальная ошибка, код ALT_FRAME_MISSING |
M0 | РАСХОЖДЕНИЕ | Отклонение работает: sim run /tmp/aud/no_frame.json → RC=1, PlanImportError: missions[0].sorties[0] missing altitude_frame; clampToGround → RC=1, invalid altitude_frame: 'clampToGround' (import_plan.py:41-47,58). Но grep -rn ALT_FRAME_MISSING src/ → 0; вместо кода — Python-traceback. import_plan.py:9 разрешает RELATIVE |
Нет машиночитаемого {code, severity, path, message, source_ref} (§1.5) — CI/веб не могут разобрать причину. RELATIVE не входит ни в SIM-GEO-09 {AGL,AMSL,ELLIPSOIDAL}, ни в §1.3 {AGL,AMSL} — принимается фрейм, семантика которого нигде не определена |
| SIM-MIS-04 | ТТХ борта и нагрузки только из fleet.yaml/payloads.yaml; правка airspeed_cruise меняет makespan_s; UAV_MODEL_UNKNOWN |
M0 | ЧАСТИЧНО | ТТХ читаются из YAML: src/geoscan_sim/hardware/fleet.py:load_fleet_yaml/uav_spec, манифест содержит fleet_yaml_sha256/payloads_yaml_sha256. UAV_MODEL_UNKNOWN есть в mission/preflight.py:91. Но makespan_s факта не зависит от ТТХ: mission/runner.py:54 duration_s = min(_sortie_duration(sortie), 600.0) — берётся из плана. Проверено: план со speed_ms=500 → makespan_s.actual = 600.0 (тот же, что у s01) |
Приёмочный критерий требования («правка airspeed_cruise меняет makespan_s») не выполняется: факт makespan — это переписанный план, а не результат физики. Неизвестная модель роняет прогон traceback'ом KeyError: "Unknown UAV 'no_such_uav' in fleet.yaml" (hardware/fleet.py:30) уже после preflight, код UAV_MODEL_UNKNOWN до вывода не доходит |
| SIM-MIS-05 | Три уровня валидации импорта; строгий режим по умолчанию; import_fixups[] всегда пуст |
M1 | НЕ РЕАЛИЗОВАНО | import_plan.validate_plan (import_plan.py:25-51) проверяет только altitude_frame и типы. Семантики §1.4 нет: стыковка фаз (PHASE_GAP grep→0), монотонность seq, согласование photos. Перекрёстной нет: план со speed_ms=500 принят, RC=0, preflight_codes: []. grep -rn "import_fixups\|tolerant" src/ → 0 |
Невалидный план исполняется молча. Строгий режим заявлен, но проверять нечем: 7 из 10 кодов §1.5 не существуют в коде |
| SIM-MIS-06 | Фазы → путевые точки закрытого словаря из 10 типов; неизвестный тип — ошибка импорта | M0 | НЕ РЕАЛИЗОВАНО | grep -rn "transect_start\|transect_end\|turn_entry\|turn_exit\|climb_to\|transit_wp\|approach_1\|rally\|acceptance_radius" src/ → все 0. Проверено: план с "phase": "teleport" → RC=0, preflight_codes: [], журнал записан |
Понятия путевой точки в симуляторе нет вовсе; интегратор летит одной фазой по прямой (engine/integrator.py:86-116). Любой мусор в поле phase принимается — импорт не защищает ни от чего |
| SIM-MIS-07 | На survey ровно один триггер камеры; оба → CAMERA_TRIGGER_AMBIGUOUS |
M1 | НЕ РЕАЛИЗОВАНО | План с одновременными camera_trigger_interval_s: 2.0 и camera_trigger_distance_m: 50.0 → RC=0, preflight_codes: []. grep -rn CAMERA_TRIGGER_AMBIGUOUS src/ → 0 (код есть только в contracts/error_codes.yaml:57) |
Неоднозначный план триггера съёмки принимается; число кадров становится неопределённым, а photos_total — недоказуемым |
| SIM-MIS-08 | Round-trip KML/GeoJSON → модель → экспорт | M2 | НЕ РЕАЛИЗОВАНО | Экспорта нет: grep -rniE "kml\|geojson" src/ → 0; в journal/writer.py пишутся только run_manifest.json и telemetry.jsonl |
M2, к M0-коду претензий нет |
| SIM-MIS-09 | acceptance_radius_m пер-класс; галсы ограничиваются створом; флаг ACCEPTANCE_SENSITIVE |
M1 | НЕ РЕАЛИЗОВАНО | grep -rn "acceptance_radius\|ACCEPTANCE_SENSITIVE" src/ → 0 |
Ни круга, ни створа; coverage_fraction факта не вычисляется вообще (копируется из плана — см. SIM-MIS-28) |
| SIM-MIS-10 | Два типа разворота lzp/flyby, R = V²/(g·tan φ) |
M1 | ЧАСТИЧНО | Формула есть: src/geoscan_sim/uav/kinematics.py:turn_radius_m; эталон V-7 в sim validate --reference → 136.5 / 136.5 PASS; тест tests/test_uav.py::test_v7_turn_radius. Самих режимов нет: grep -rn "lzp\|flyby\|turn_mode" src/ → 0 |
Есть число, нет поведения: разворот не исполняется, разница покрытия lzp против flyby — один из главных ответов симулятора — не считается |
| SIM-MIS-11 | Камера на развороте по умолчанию выключена | M1 | НЕ РЕАЛИЗОВАНО | grep -rn "camera_trigger_in_turnaround\|photo" src/ → 0; src/geoscan_sim/camera/gsd.py (32 строки) считает только GSD |
Кадров как сущности нет; photos_out_of_transect невычислим |
| SIM-MIS-12 | Недостижимая точка: спираль, таймаут 120 с, событие, contingency после 3 подряд | M1 | НЕ РЕАЛИЗОВАНО | grep -rn "spiral\|unreachable\|WAYPOINT_UNREACHABLE" src/ → 0. Ближайшее — uav/kinematics.py:wind_triangle_ground_speed_ms бросает ValueError при W⊥ ≥ V_a, и preflight.py:155-158 даёт WIND_EXCEEDS_AIRSPEED до старта |
Точка «телепортируется»: интегратор не знает о точках. Нет unphotographed_area_km2 |
| SIM-MIS-13 | Перелёт по правилам Planner; при Δh > 30 м участок набора выделяется |
M1 | НЕ РЕАЛИЗОВАНО | engine/integrator.py:107 — height_m=config.flight_height_m, высота константна весь прогон; grep -rn "climb_rate\|climb" src/ --include=*.py → 0 |
Профиля высоты нет; время набора/снижения в makespan не входит |
| SIM-MIS-14 | Посадка — 3 точки, заход против ветра; парашют 201 с 70 м, снос | M1 | ЧАСТИЧНО | Снос считается: uav/kinematics.py:parachute_drift_m, эталон V-10 168 / 168 PASS в sim validate --reference, тест tests/test_uav.py::test_v10_parachute_drift; высота выброса 70 м — в допущении A-16 манифеста. В исполнении и отчёте нет: grep -rn "landing_offset_m" src/ → 0, маршрут посадки не моделируется (preflight.py:185-195 только ищет посадку под вышкой по литеральным координатам) |
Величина есть в справочной таблице, но не привязана к прогону: в fact_report.json её нет, «куда борт реально сел» не отвечается |
| SIM-MIS-15 | Возврат на текущей высоте, но не ниже минимальной высоты возврата | M1 | НЕ РЕАЛИЗОВАНО | grep -rn "return_altitude\|min_return_alt" src/ → 0 |
Режима возврата нет |
| SIM-MIS-16 | Единые модельные часы и фиксированный dt для всех бортов и подсистем |
M0 | ЧАСТИЧНО | dt фиксирован: engine/integrator.py:22 dt_s: float = 0.1; телеметрия 10 Гц подтверждена (6000 строк на 600 с). Но борт ровно один: mission/runner.py:15-23 _first_sortie берёт первую миссию и первый вылет. Проверено на s04 (3 борта, 5 вылетов): журнал 6000 строк, единственный борт, v_air_ms: 27.8 |
«Единые часы для всех бортов» проверять не на чем — многобортового прогона не существует. run_config с полем dt отсутствует, задать шаг из CLI нельзя, значит и «шаг пер-борт отклоняется» нереализуемо |
| SIM-MIS-17 | Автоматическая проверка сходимости по шагу (dt/2), флаг DT_NOT_CONVERGED |
M0 | НЕ РЕАЛИЗОВАНО | grep -rn DT_NOT_CONVERGED src/ tests/ → 0. sim run --help не имеет опции --dt; mission/runner.py:55-62 не передаёт dt_s в IntegratorConfig |
Приёмка A-03 BRD (dt→dt/2: \|Δmakespan\| ≤ 0,5 %, \|Δcoverage\| ≤ 0,2 п.п.) невыполнима технически — шаг нечем изменить. M0-MUST не закрыт |
| SIM-MIS-18 | Три режима часов (1×, ускоренный, headless) дают идентичный журнал | M2 | НЕ РЕАЛИЗОВАНО | grep -rn "clock_mode\|realtime\|accelerated\|time_scale" src/ → 0 (единственные совпадения на headless — имена функции run_plan_headless и docstring) |
M2. Попутно: clock_mode — обязательное поле log_schema.json, и его отсутствие ломает M0-манифест (см. SIM-MIS-23) |
| SIM-MIS-19 | Детерминизм по seed: единый PRNG, подпотоки (подсистема, uav_id), фикс. порядок обновления |
M0 | РАСХОЖДЕНИЕ | Два прогона одного комплекта: sha256(telemetry.jsonl) = d348f1ae… оба раза (вывод ниже). Тесты tests/test_determinism.py::test_determinism_digest_stable_10x, tests/test_engine_smoke.py::test_run_deterministic_telemetry_hash_10x. Но PRNG нет: grep -rn "random\|Generator\|PCG64" src/ → ни одного использования; --seed 777 даёт тот же telemetry_sha256: a6706a37…, что и --seed 42 |
Детерминизм тривиален — воспроизводится нечего, потому что стохастики нет ни в ветре, ни в GNSS, ни в отказах. Ни подпотоков, ни второго борта, ни фиксированного порядка обхода бортов. Как только появится любая случайность, гарантии не будет: механизм, который требование предписывает, не построен |
| SIM-MIS-20 | События внутри шага упорядочены по (t_event, uav_id, seq) с интерполяцией |
M0 | НЕ РЕАЛИЗОВАНО | grep -rn "event" src/ --include=*.py → 0 совпадений во всём пакете. Поток событий в журнал не пишется: journal/writer.py:30-57 создаёт только run_manifest.json и telemetry.jsonl |
Второй из трёх потоков журнала отсутствует. Ни одно требование, опирающееся на события (MIS-12/24/31, отчёт §5.2 по transect_start/turn_start/photo), не может быть выполнено |
| SIM-MIS-21 | Физика не обращается к системным часам и не зависит от частоты рендера | M1 | ЧАСТИЧНО | В физике часов нет: grep -rn "datetime.now\|time.time\|perf_counter\|monotonic" src/ → единственное совпадение journal/writer.py:34 (generated_at). Рендера не существует, вторую половину приёмки («замедление рендера вдвое») проверить нечем |
Единственное обращение к часам стоит ровно там, где ломает бит-в-бит журнала (см. SIM-MIS-22/23 и критерий выхода M0) |
| SIM-MIS-22 | Журнал = манифест + поток событий + телеметрия; append-only; повторный run_id отклоняется |
M0 | РАСХОЖДЕНИЕ | Есть 2 части из 3 (journal/writer.py:48-57), потока событий нет (см. MIS-20). run_id не существует: grep -rn run_id src/ → 0. Файлы открываются на перезапись: writer.py:50 manifest_path.open("w"), writer.py:53 telemetry_path.open("w"). Проверено: повторный sim run другого плана в тот же каталог молча перезаписал журнал, md5 6d7f8d3d… → 895d118d… |
Журнал не append-only и не защищён: прогон можно бесследно затереть другим прогоном. Отсутствие run_id делает невозможными parent_run_id, переигровку, ссылки «находок» на диапазон журнала |
| SIM-MIS-23 | Манифест: все хеши входа, seed, dt, версии образа и YAML парка, import_fixups[], assumptions_used[] |
M0 | ЧАСТИЧНО | Фактический манифест (вывод ниже) содержит seed, dt_s, fleet_yaml_sha256, payloads_yaml_sha256, assumptions_used, world_snapshot_id. Сверка с contracts/log_schema.json (прогон скрипта, вывод ниже): отсутствуют 9 из 14 обязательных — run_id, scenario_sha256, plan_sha256, run_config_sha256, telemetry_hz, clock_mode, import_fixups, started_at, exit_status; нет также sim_version, image_digest |
Приёмка требования («по манифесту прогон воспроизводится на другой машине») невыполнима: в манифесте нет ни хеша, ни имени плана — по нему нельзя установить, что исполнялось. assumptions_used берётся из src/geoscan_sim/hardware/assumptions.yaml (4 записи), а не из contracts/assumptions.yaml (27 записей) — два параллельных реестра допущений |
| SIM-MIS-24 | Поток событий покрывает закрытый словарь §4.3 | M1 | НЕ РЕАЛИЗОВАНО | grep -rn "event" src/ --include=*.py → 0. В contracts/log_schema.json описаны 34 типа событий, в коде — ни одного |
Ни mode_change, ни violation, ни contingency не фиксируются; INH-10 (violations против warnings) необеспечен |
| SIM-MIS-25 | Телеметрия 10 Гц без децимации, поля §4.4 вкл. clearance_m и xtrack_error_m; 108 000 ± 20 записей, ≤ 20 МБ |
M0 | ТРЕБОВАНИЕ НЕГОДНОЕ | Дефект требования: реестр (строка 289) требует 10 Гц / 108 000 ± 20 / ≤ 20 МБ, а §4.4 и таблица того же документа (строка 591) требуют 5 Гц децимацией k = round(1/(5·dt)) = 2 / 54 000 ± 10 / ≤ 10 МБ — прямое противоречие внутри одного свода. Факт реализации: 10 Гц и объём измерены прогоном на 180 мин — записей=108000 (в допуске), размер=21.55 МБ (вне лимита ≤ 20 МБ) уже при 11 полях вместо 31. Из 31 поля схемы присутствуют 3 (heading_deg, v_air_ms, v_ground_ms); из 4 обязательных — 0; clearance_m и xtrack_error_m отсутствуют |
Требование нельзя принять как есть: две взаимоисключающие частоты и бюджет объёма, недостижимый с собственным набором полей (JSONL 31 поле × 108 000 записей заведомо > 20 МБ — измерено 21,55 МБ уже на 11 полях). Фактически же телеметрия непригодна ни для покрытия, ни для CFIT: нет ни координат, ни борта, ни клиренса, ни бокового уклонения |
| SIM-MIS-26 | Паспорт кадра: позиция, углы, наклонная дальность, GSD, footprint, смаз | M1 | НЕ РЕАЛИЗОВАНО | grep -rn "photo" src/ → 0; camera/gsd.py — только gsd_m/gsd_cm по номинальной высоте (gsd.py:18 altitude_m * payload.gsd_coefficient), наклонной дальности нет |
photos_total факта, gsd_actual_cm_p50/p95, motion_blur_px_p95 невычислимы |
| SIM-MIS-27 | Keyframes каждые 60 с модельного времени | M2 | НЕ РЕАЛИЗОВАНО | grep -rni keyframe src/ → 0 |
M2 |
| SIM-MIS-28 | Отчёт именами и единицами валидатора; строка = план / факт / дельта | M0 | РАСХОЖДЕНИЕ | fact_report.json (вывод ниже) содержит 4 строки: makespan_s, total_flight_time_s, coverage_fraction, energy_used_frac, и в каждой только planned/actual — колонки delta нет. Имён валидатора нет: grep -rn "photos_total\|transects_total\|turns_total\|turn_time_fraction\|sorties_total\|uavs_used" src/ → все 0, violations.* в отчёт не выводятся. Эталонный набор — docs/task5/70-plan/01-validator-first.md:45-62 |
Diff имён непуст → M0-MUST нарушен. Хуже: «факт» не является фактом — coverage_fraction.actual копируется из плана (runner.py:93 metrics.get("coverage_fraction")), energy_used_frac.actual = None (runner.py:97), makespan_s.actual = min(план, 600) (runner.py:54). На s04: план 5280 с, «факт» 600,0 с. Сравнение «план против факта» — главный выход проекта — сейчас сравнивает план сам с собой |
| SIM-MIS-29 | Допуски дельт заданы явно и проверяются; превышение — флаг | M1 | НЕ РЕАЛИЗОВАНО | В fact_report.json нет ни дельт, ни допусков, ни колонки «в допуске». tolerance встречается только в validator/references.py и validator/runner.py — это допуски эталонов V-1…V-14, не план/факт |
Расхождение план/факт никак не квалифицируется |
| SIM-MIS-30 | Дельта makespan раскладывается на компоненты, сумма = полной дельте ± 1 с |
M1 | НЕ РЕАЛИЗОВАНО | grep -rniE "decompos\|delta_wind\|Δ_" src/ → 0 |
SIM-BIZ-06 (M1, MUST) опирается на это же разложение — падает вместе |
| SIM-MIS-31 | Фактические нарушения при валидном плане — в раздел «находки» | M1 | НЕ РЕАЛИЗОВАНО | grep -rniE "finding\|находк\|cfit" src/ → 0. fact_report.preflight — это результат предполётной проверки плана, а не находки прогона |
То, что показывается жюри («план прошёл валидацию, а вот что случилось при облёте»), не производится |
| SIM-MIS-32 | Отчёт содержит 11 метрик, недоступных планировщику | M1 | НЕ РЕАЛИЗОВАНО | Грепнуты все 14 имён §5.3 (min_clearance_m, clearance_p05_m, cfit_events, link_loss_events, link_loss_total_s, autonomous_return_km, photos_out_of_transect, gsd_actual_cm, motion_blur_px, unphotographed_area_km2, landing_offset_m, min_separation_m, queue_wait_total_s, energy_margin_frac_min) — в src/ 0 совпадений у всех, кроме parachute_drift_m (3 совпадения, все в кинематике и эталонах, не в отчёте) |
Блок, ради которого симулятор существует, в отчёте отсутствует полностью |
| SIM-MIS-33 | Пакетный режим: N сценариев × M планов одной командой, без GUI/GPU/сети | M0 | НЕ РЕАЛИЗОВАНО | cli.py:51 — @click.argument("plan", ...), ровно один план за вызов. grep -rn "batch\|csv" src/ → 0. find . -iname "docker*" -o -iname "*compose*" (вне .venv) → пусто |
Офлайн-исполнение есть, пакетного режима нет. Приёмка («docker compose up проходит s01–s10 × 2 плана, CSV/JSON») невыполнима: нет ни контейнера, ни батча, ни CSV. M0-MUST не закрыт |
| SIM-MIS-34 | Бюджет производительности: 1 борт-час ≤ 60 с реального на ядро, число в отчёте | M1 | ЧАСТИЧНО | Измерено (вывод ниже): 1 борт-час модельного времени = 0,81 с реального; sim run на s01 (600 с модели) = wall=0.35 s. Бюджет соблюдён с запасом ×74. Но: замер на s04 невозможен (моделируется 1 борт из 3 и 600 с из 5280), wall_time_s/sim_time_s в манифест не пишутся (journal/writer.py:32-44) |
Число получено на прямолинейном стабе без рельефа, камеры, событий и второго борта — оно ничего не говорит о бюджете настоящего движка. Требование формально «проходит», фактически не проверено на целевой нагрузке |
| SIM-MIS-35 | Пакетный прогон возвращает RC ≠ 0 при фактических нарушениях | M1 | НЕ РЕАЛИЗОВАНО | sim run fixtures/mutants/m01_nfz_intrusion.json → preflight_codes: ["NFZ_VIOLATION"], RC=0. sim run fixtures/mutants/m06_ir_on_gemini.json → PAYLOAD_INCOMPATIBLE (по §1.5 — fatal, прогон не должен стартовать), RC=0, журнал записан. cli.py:55-62 не анализирует preflight_codes |
CI не падает на нарушениях: регрессия алгоритмов планировщика через симулятор невозможна. Прямое противоречие §1.5 «фатальная ошибка → прогон не стартует» |
| SIM-MIS-36 | Интерактивный режим: ≤ 200 мс, ≥ 30 FPS, рендер не влияет на журнал | M2 | НЕ РЕАЛИЗОВАНО | UI отсутствует целиком: в simulate/ нет ни одного .ts/.tsx/.js/.html/.css |
M2 |
| SIM-MIS-37 | Режим разбора записанного прогона без пересчёта физики | M2 | НЕ РЕАЛИЗОВАНО | grep -rni "replay\|seek" src/ → 0; читателя журнала в коде нет (journal/ содержит только writer.py) |
M2 |
| SIM-MIS-38 | Команды оператора в журнал и interventions[]; повтор байт-в-байт |
M2 | НЕ РЕАЛИЗОВАНО | grep -rni "interventions\|operator_command" src/ → 0 |
M2 |
| SIM-MIS-39 | Команда доходит до борта только при связи; отклонённая даёт событие | M2 | НЕ РЕАЛИЗОВАНО | grep -rni command_rejected src/ → 0. env/link.py:LinkModel моделирует только круг радиодальности и таймер автономности, команд не знает |
M2 |
| SIM-MIS-40 | Сравнительный прогон двух планов, K = 20 парных сидов | M2 | НЕ РЕАЛИЗОВАНО | grep -rni compare src/ → 0 |
M2 |
3. Главные расхождения¶
3.1. Настоящий KML/GeoJSON от планировщика не принимается — только внутренний JSON¶
Требовалось (SIM-MIS-02, INH-01, ТЗ п. 2, R-OUT-2/3): три обязательных формата — наша схема, GeoJSON RFC 7946, KML OGC 2.2 — дают идентичный список путевых точек.
Сделано: src/geoscan_sim/mission/import_plan.py — 59 строк, единственный вход json.load.
$ grep -rniE "kml|geojson|kmz|xml" src/ tests/ tools/
(rc=1)
Ни парсера, ни фикстуры, ни теста. find по *.kml/*.geojson/*.kmz в репозитории — пусто.
Почему разошлось: реализация писалась от фикстур fixtures/plans/*.json, которые созданы вручную в нашей схеме; настоящего артефакта планировщика в контуре не было.
Чинить: импортёр KML (<Placemark>/<LineString>/<altitudeMode>/<ExtendedData>) и GeoJSON (FeatureCollection на борт, sortie_seq в properties), плюс тест «три формата → один список ППМ» на s01.
3.2. Журнал не соответствует собственному контракту contracts/log_schema.json¶
Требовалось (SIM-MIS-22/23/25, FIN-08 «заморозка контракта»): журнал = манифест + события + телеметрия, поля по §4.2–4.4. Сделано: две части из трёх, состав полей — свой.
manifest: отсутствуют обязательные поля: ['run_id', 'scenario_sha256', 'plan_sha256', 'run_config_sha256',
'telemetry_hz', 'clock_mode', 'import_fixups', 'started_at', 'exit_status']
manifest: поля вне схемы: ['generated_at', 'phase_durations_s', 'preflight_codes', 'preflight_violations',
'scenario_id', 'schema_version', 'telemetry_sha256', 'uav_model']
telemetry: отсутствуют обязательные: ['t_sim_s', 'uav_id', 'lon', 'lat']
telemetry: поля вне схемы: ['east_m', 'energy_left_wh', 'height_m', 'link_up', 'north_m', 'phase',
't_s', 'track_deg']
telemetry: из 31 поля схемы присутствует: ['heading_deg', 'v_air_ms', 'v_ground_ms'] = 3
Схема не проверяется нигде: grep -rn "log_schema\|jsonschema" src/ tests/ даёт единственную загрузку в contracts/loader.py:63-66, которую никто не импортирует; в .venv пакет jsonschema даже не установлен (jsonschema installed: False).
Почему разошлось: контракт писался как документ, движок — отдельно; связи «код → схема» нет ни в рантайме, ни в CI.
Чинить: валидировать манифест и каждую запись телеметрии против log_schema.json в RunJournalWriter.write и в CI; привести TelemetrySample к полям §4.4 (в первую очередь uav_id, lon, lat, clearance_m, xtrack_error_m).
3.3. Поток событий отсутствует целиком¶
grep -rn "event" src/ --include=*.py → 0 совпадений. В log_schema.json описаны 34 типа событий, в требованиях §4.3 — закрытый словарь из 14 групп. В коде нет ни одного.
Чем грозит: без событий не считаются transects_total, turns_total, photos_total, cfit_events, link_loss_events, не существует «находок» (MIS-31), не работает ни одна ссылка отчёта на журнал. Это один корень примерно половины «НЕ РЕАЛИЗОВАНО» в чеклисте.
3.4. Детерминизм подтверждён для телеметрии, но манифест бит-в-бит не воспроизводится¶
Требовалось (SIM-MIS-19, BIZ-03, критерий выхода M0 BRD §5: «2 прогона с одним seed → бит-в-бит журнал (sha256)»). Прогон (дословно):
$ sim run fixtures/plans/s01.json --seed 42 --output /tmp/aud_r1
{ "telemetry_sha256": "a6706a3732dc5ce2ac9bfdab3d1a3eea9c38e8199a3ba81d5c42900a5a2ee107" }
$ sim run fixtures/plans/s01.json --seed 42 --output /tmp/aud_r2
{ "telemetry_sha256": "a6706a3732dc5ce2ac9bfdab3d1a3eea9c38e8199a3ba81d5c42900a5a2ee107" }
$ sha256sum /tmp/aud_r{1,2}/telemetry.jsonl /tmp/aud_r{1,2}/run_manifest.json
d348f1ae5b32ab1708cde2034752a728af3d7469b25d6ef718105c01733b3110 /tmp/aud_r1/telemetry.jsonl
d348f1ae5b32ab1708cde2034752a728af3d7469b25d6ef718105c01733b3110 /tmp/aud_r2/telemetry.jsonl
c4cdacfd64b759e895024715e76c92d7bde35282908a42170883b1becf67ddf8 /tmp/aud_r1/run_manifest.json
7ffae6e301073b2f0f23ba9fa9d46efda0d76e0760b0b3dcf4e85528ab0d74d6 /tmp/aud_r2/run_manifest.json
$ diff /tmp/aud_r1/run_manifest.json /tmp/aud_r2/run_manifest.json
38c38
< "generated_at": "2026-09-16T05:05:09.945877+00:00"
---
> "generated_at": "2026-09-16T05:05:10.320729+00:00"
Телеметрия — бит-в-бит между процессами (это больше, чем проверяют тесты test_determinism_digest_stable_10x / test_run_deterministic_telemetry_hash_10x, которые гоняют 10 повторов внутри одного процесса). Манифест — нет: journal/writer.py:34 пишет datetime.now(). По букве SIM-MIS-22 журнал — это все три части, значит критерий выхода M0 не выполнен.
Чинить: либо вынести generated_at из хешируемой части (как сделано для world_snapshot_id в SIM-WORLD-36), либо считать journal_sha256 по нормализованному манифесту; поле journal_sha256 в схеме уже предусмотрено.
3.5. Seed ни на что не влияет — PRNG в движке нет¶
$ sim run fixtures/plans/s01.json --seed 42 → telemetry_sha256: a6706a37…
$ sim run fixtures/plans/s01.json --seed 777 → telemetry_sha256: a6706a37…
grep -rn "random|Generator|PCG64" src/ не даёт ни одного использования генератора. SIM-MIS-19 требует единый PRNG с подпотоками на (подсистема, uav_id) — механизма нет, детерминизм достигнут отсутствием случайности.
Чем грозит: как только появятся порывы ветра, шум GNSS, отказы и связь (домены ENV/M1), детерминизм придётся строить с нуля, и все зелёные тесты детерминизма сегодня ничего об этом не говорят. Это ложноположительный сигнал в CI.
3.6. «Факт» в отчёте план/факт — это переписанный план¶
mission/runner.py:54 — duration_s = min(_sortie_duration(sortie), 600.0); runner.py:93 — coverage_fraction.actual = metrics.get("coverage_fraction"); runner.py:97 — energy_used_frac.actual = None.
$ cat /tmp/aud/s04/fact_report.json # план s04: 3 борта, 5 вылетов, makespan 5280 с
"makespan_s": { "actual": 600.0, "planned": 5280 }
"total_flight_time_s": { "actual": 600.0, "planned": 12960 }
"coverage_fraction": { "actual": 0.998, "planned": 0.998 }
"energy_used_frac": { "actual": null, "planned": 0.65 }
Проверено также, что «факт» не зависит от физики: план со speed_ms = 500 м/с даёт тот же makespan_s.actual = 600.0.
Чем грозит: SIM-MIS-28 (M0), SIM-BIZ-27 (M0), UAV-40, BR-21 опираются на то, что симулятор даёт независимый факт. Сейчас он даёт копию плана, обрезанную константой 600 с. Любая демонстрация «план против факта» на этих числах вводит в заблуждение.
3.7. Ограничение 600 с и «первый вылет первой миссии» не описано ни одним требованием¶
runner.py:15-23 _first_sortie + runner.py:54 min(..., 600.0). На s04 из трёх бортов и пяти вылетов исполняется один борт в течение 600 с. В журнале нет ни признака усечения, ни списка пропущенных бортов, ни соответствующего warning'а. Это молчаливое сужение задачи — ровно то, что §1.5 запрещает («никаких „догадаемся сами“»).
Чинить: как минимум — код RUN_TRUNCATED/warning в манифест; как максимум — многобортовый цикл, который требует SIM-MIS-16/20.
3.8. Нарушения не влияют на код выхода; фатальные коды §1.5 не останавливают прогон¶
$ sim run fixtures/mutants/m01_nfz_intrusion.json → preflight_codes: ["NFZ_VIOLATION"], RC=0
$ sim run fixtures/mutants/m06_ir_on_gemini.json → preflight_codes: ["PAYLOAD_INCOMPATIBLE"], RC=0
PAYLOAD_INCOMPATIBLE по §1.5 — класс fatal, прогон не должен стартовать; он стартует и пишет журнал. SIM-MIS-35 (RC ≠ 0 при нарушениях) не выполнен.
Чинить: в cli.py:run_cmd — раздельные коды выхода: fatal preflight → RC=2 без журнала, фактические нарушения → RC=1 с журналом.
3.9. Импорт не проверяет ничего, кроме фрейма высоты¶
Проверено прогонами: неизвестный тип фазы ("phase": "teleport"), оба триггера камеры одновременно, speed_ms = 500 м/с — все три приняты, RC=0, preflight_codes: [].
Из 10 кодов §1.5 в коде существует один (UAV_MODEL_UNKNOWN), и тот роняет прогон traceback'ом. Семантический и перекрёстный уровни §1.4 отсутствуют.
Чем грозит: SIM-MIS-05/06/07 (и косвенно MIS-03) — вход симулятора не защищён; любой дефект плана проявится не кодом ошибки, а неверными числами в отчёте.
3.10. Ошибки импорта отдаются Python-трейсбеком, а не машиночитаемым списком¶
$ sim run /tmp/aud/no_frame.json
...
geoscan_sim.mission.import_plan.PlanImportError: missions[0].sorties[0] missing altitude_frame
RC=1
Требование §1.5 — {code, severity, path, message, source_ref}. path фактически есть внутри строки, всё остальное — нет; кода ALT_FRAME_MISSING не существует в коде, хотя contracts/error_codes.yaml:21 его определяет и в шапке файла записано «Коды в коде симулятора — только из этого файла».
Чинить: ввести исключение с полем code и таблицей из contracts/error_codes.yaml (загрузчик contracts/loader.py уже написан и не используется).
3.11. Preflight подменяет исполнение и опирается на литеральные константы¶
preflight.py:163 — NFZ определяется как max(lon) > 37.63; preflight.py:193 — вышка как |lon − 37.6495| < 0.001 и |lat − 55.772| < 0.001; preflight.py:119 — клиренс как alt < 100.0 без обращения к рельефу; preflight.py:130,133 — пороги interval ≥ 7.0 и spacing ≥ 200.0. Геометрия зон, HeightField и препятствия в проверках не участвуют; run_plan_headless мир вообще не загружает (world_snapshot_id — просто строка в манифесте).
Следствие, проверенное прогоном: на валидном базовом плане s04 preflight выдаёт 4 ложных срабатывания (PAYLOAD_INCOMPATIBLE, NFZ_VIOLATION, ENDURANCE_EXCEEDED, PARACHUTE_OBSTACLE_CONFLICT), а тест ложных срабатываний покрывает только s01 (validator/mutate.py:run_base_plan_check).
Чем грозит: «нарушения», которые видит симулятор, — свойство координатных констант, а не мира; на любом другом полигоне детекция развалится.
3.12. Сходимость по шагу (M0-MUST) невозможно даже попытаться проверить¶
SIM-MIS-17 и приёмка A-03 BRD требуют прогона с dt/2. sim run не имеет опции --dt; mission/runner.py:55-62 не передаёт dt_s в IntegratorConfig, оставляя дефолт 0,1 с; grep -rn DT_NOT_CONVERGED → 0.
Чинить: --dt в CLI и run_config, автоматический парный прогон dt/dt/2 с флагом в манифесте.
4. Замечания к самим требованиям (часть 1)¶
4.1. SIM-MIS-25 — противоречие внутри свода (ТРЕБОВАНИЕ НЕГОДНОЕ).
Реестр (90-final/02-requirements-registry.md:289): «10 Гц, без децимации, = шагу dt; 108 000 ± 20 записей; ≤ 20 МБ».
Исходный документ, §4.4 и таблица требований (50-mission/01-mission-execution.md:591 и раздел «4.4 Телеметрия»): «5 Гц (A-MIS-06), получается децимацией шага, k = round(1/(5·dt)) = 2; 54 000 ± 10; ≤ 10 МБ».
Это не редакционная неточность, а два взаимоисключающих механизма записи (децимация k=2 против k=1). Реализовано 10 Гц, но §4.4 остался непереписанным — тот, кто будет писать код по домену, прочтёт 5 Гц.
Вдобавок бюджет ≤ 20 МБ несовместим с собственным набором полей: измерено 21,55 МБ на 108 000 записей JSONL уже при 11 полях; при 31 поле §4.4 это будет 50–60 МБ. Требование надо либо переводить на бинарный/колоночный формат, либо менять число.
4.2. Рассогласование уровня обязательности между исходным документом и реестром.
| ID | Исходный документ (строки 574–605) | Реестр (строки 272–305) |
|---|---|---|
| SIM-MIS-08 | MUST | M2, SHOULD |
| SIM-MIS-18 | MUST | M2, SHOULD |
| SIM-MIS-34 | SHOULD | M1, MUST («повышено до MUST и объявлено единственным числом») |
| SIM-MIS-38 | MUST | M2, COULD |
| SIM-MIS-39 | MUST | M2, COULD |
Реестр объявлен головным, но исходный документ не обновлён. Пока оба файла в своде, любой вывод об «обязательности» зависит от того, какой файл читали. Особенно вреден SIM-MIS-34: он ещё и дублирует SIM-PLT-21 — два ID на один бюджет производительности (отмечено в сквозных темах свода), то есть у одного числа три разных нормативных статуса.
4.3. SIM-MIS-19 — приёмочный критерий не различает «детерминизм» и «отсутствие случайности». Формулировка «10 повторов → один sha256» выполняется тривиально любой чисто детерминированной программой без PRNG, что и произошло. При этом вторая половина критерия («добавление четвёртого борта не меняет траекторию первых трёх») требует многобортового прогона, которого в M0 не требует ни одно требование (SIM-MIS-16 говорит о единых часах, но нигде не сказано «M0 исполняет N бортов»). Итог: проверяемая половина ничего не доказывает, а доказывающая половина вне объёма вехи. Нужен критерий вида «прогоны с seed A и seed B различаются» плюс явное требование многобортового прогона в M0/M1.
4.4. SIM-MIS-22 против SIM-MIS-23 — дыра вокруг run_id.
MIS-22 (M0) требует «повторная запись существующего run_id отклоняется», но MIS-23 (M0), перечисляющий обязательный состав манифеста, run_id не называет («хеши входа, seed, dt, версии образа и YAML парка, import_fixups[], assumptions_used[]). run_id обязателен только в contracts/log_schema.json. Реализация закономерно его не завела. То же касается exit_status, clock_mode, telemetry_hz, started_at — они обязательны по схеме и не названы ни одним текстовым требованием M0.
4.5. SIM-MIS-01 — run_config объявлен обязательным элементом входа, но нигде не специфицирован.
Ни состава полей, ни формата, ни схемы: §3 описывает dt, частоту телеметрии и режим часов прозой, run_config_sha256 фигурирует только в §4.2. При этом MIS-09 («радиус задаётся в run_config»), MIS-11 («включение — флаг прогона»), MIS-16 («в run_config один dt»), MIS-29 («пороги вынесены в конфигурацию») все на него ссылаются. Нужен отдельный контракт run_config наравне с log_schema.json.
4.6. SIM-MIS-03 против SIM-GEO-09 — множество допустимых фреймов высоты не зафиксировано в одном месте.
SIM-GEO-09: {AGL, AMSL, ELLIPSOIDAL}; §1.3 mission-документа: altitude_frame ∈ AGL | AMSL. Третьего варианта в mission-домене нет, а KML/MAVLink-фрейм RELATIVE (который реализация и приняла, import_plan.py:9) не упомянут нигде. Расхождение реализации здесь предсказуемо прямо из требований.
4.7. SIM-MIS-33 (M0, MUST) требует артефакта из чужого домена.
Критерий приёмки — «docker compose up в изолированной сети проходит s01–s10 × 2 плана». Ни Dockerfile, ни compose-файл не являются предметом ни одного требования MIS; упаковка — домен PLT, где она заводится позже. M0-требование, которое невозможно закрыть средствами своего домена, будет провалено при любой добросовестной реализации.
4.8. SIM-MIS-17 (M0) требует переменного dt, которого не вводит ни одно требование.
MIS-16 фиксирует dt = 0,1 с как константу и требует отклонять «шаг пер-борт»; MIS-17 требует автоматического прогона с dt/2. Ни одно требование не обязывает сделать dt параметром прогона. Формально непротиворечиво, практически — MIS-17 недостижим без того, что не потребовано.
4.9. SIM-MIS-28 — «diff имён полей пуст» проверяется только вручную.
Валидатор планировщика — отдельный продукт, в этом репозитории его нет. Эталонный список имён приходится брать из docs/task5/70-plan/01-validator-first.md. Требование не указывает машиночитаемый источник имён (например, общий YAML-словарь метрик), поэтому «пустой diff» нечем автоматизировать — и это ровно тот шов, где расхождение и произошло.
4.10. SIM-MIS-35 против §1.5 — не определён приоритет кодов выхода.
§1.5 требует RC ≠ 0 при фатальной ошибке импорта (прогон не стартует), MIS-35 — RC ≠ 0 при фактических нарушениях по итогам прогона. Это разные ситуации, но реестра кодов выхода (какой код при какой ситуации) в своде нет. Сейчас в реализации sim validate без флага даёт RC=2, ошибка импорта — RC=1, нарушения — RC=0; интерпретировать это снаружи невозможно.
4.11. SIM-MIS-29 — допуски объявлены допущением с открытым вопросом.
A-MIS-08 (10 % / 2 п.п. / 2 % / 5 п.п.) и Q-17: в источниках таких чисел нет. Требование годное, но по правилу BRD §7.2 (величина с fidelity_class: assumed не может быть основанием NO-GO) вердикт по этим допускам нельзя использовать для отказа — это стоит записать прямо в требование, иначе оно будет прочитано как приёмочное.
5. Что осталось НЕ ПРОВЕРЕНО и почему¶
Строк со статусом НЕ ПРОВЕРЕНО в чеклисте нет — по каждому из 40 требований удалось получить доказательство в одну из сторон. Тем не менее ряд клауз внутри требований проверить было невозможно; это не меняет вердиктов (все они и так отрицательные по другим основаниям), но честность требует перечислить:
- SIM-MIS-23, клауза «прогон воспроизводится на другой машине с тем же образом». Второй машины и контейнерного образа в контуре нет (Dockerfile отсутствует). Вердикт ЧАСТИЧНО поставлен по составу манифеста (9 отсутствующих обязательных полей), а не по кросс-машинной проверке.
- SIM-MIS-21, клауза «не зависит от частоты рендера». Рендера не существует; проверено только отсутствие обращений к системным часам в физике (grep).
- SIM-MIS-19, клауза «добавление четвёртого борта не меняет траекторию первых трёх». Многобортового прогона нет в принципе (
_first_sortieберёт один вылет), сконструировать проверку не на чем. - SIM-MIS-34, замер на s04. Измерен стаб (0,81 с на борт-час); замер на целевой нагрузке (3 разнотипных борта, 40 км², 5280 с) невозможен — s04 исполняется как один борт 600 с. Приведённое число к бюджету требования отношения не имеет.
- SIM-MIS-17, прогон с
dt/2. Технически неосуществим:dtне выводится ни в CLI, ни вrun_config. - Кросс-платформенная бит-идентичность. Проверена только повторяемость между двумя процессами на одной машине (x86-64, один
.venv) — что и соответствует объявленному в BRD §11.2 ограничению. - Валидация журнала внешним валидатором JSON Schema выполнена собственным скриптом сверки состава полей: пакет
jsonschemaв.venvне установлен (jsonschema installed: False), поэтому провереныrequired/propertiesвручную, без проверки типов и форматов (uuid,date-time). Типы полей, следовательно, не верифицированы — но это не влияет на вывод, поскольку сами поля отсутствуют.
Приложение: воспроизведение проверок¶
cd /home/qovalenko/geoscan-wt/simulate-audit/simulate
.venv/bin/pytest -q # 50 passed in 1.13s
.venv/bin/sim run fixtures/plans/s01.json --seed 42 --output /tmp/aud_r1
.venv/bin/sim run fixtures/plans/s01.json --seed 42 --output /tmp/aud_r2
.venv/bin/sim run fixtures/plans/s01.json --seed 777 --output /tmp/aud_r3
sha256sum /tmp/aud_r{1,2}/telemetry.jsonl /tmp/aud_r{1,2}/run_manifest.json
diff /tmp/aud_r1/run_manifest.json /tmp/aud_r2/run_manifest.json
.venv/bin/sim run fixtures/plans/s04.json --output /tmp/aud/s04
.venv/bin/sim run fixtures/mutants/m01_nfz_intrusion.json --output /tmp/aud/m01; echo $?
grep -rn "event" src/ --include=*.py
grep -rniE "kml|geojson|kmz|xml" src/ tests/ tools/