# Проверки поэтапного переноса из Owniverse.U `test_simulation_optimizations.gd` сравнивает прямые формулы дрейфа с прежней матрицей, включая целые количества больше E27 и накопленные остатки. Тестовый ledger сверяет выборочное округление с полным на каждом поколении нагруженной симуляции. `test_ui_updates.gd` проверяет обновление по публикации, выбору объекта и изменению параметров мира, а также пропуск повторных расчётов для неизменившегося состояния. Динамический размер партий проверяется в `test_batch_boundaries.gd`: скорости 1–6, сохранение больших целых количеств и дробных остатков, повторное укрупнение и снижение скорости. Высокие скорости допускают приближение времени переходов; сравнение со старым состоянием целиком не является проверкой эквивалентности такого режима. `test_coarse_generations.gd` проверяет отсутствие повторного перехода C0.0, C0.1 и C0.2 внутри одного интервала, старое смещение сетки партий, продолжение эволюции в следующем интервале, большие целые количества и дробные остатки. `test_frame_evolution.gd` сравнивает целый и разбитый по кадрам расчёт, включая возобновление сохранённого незавершённого шага. ## Производительность на сохранении ```sh godot --headless --path Owniverse.GI --script res://tests/benchmark_saved_state.gd -- /tmp/save.adventure.json ``` Читает диагностический JSON формата 4 в отдельные экземпляры мира и замеряет шесть последовательных шагов на скоростях 1, 4, 6. Запись сохранений не вызывается. Последним аргументом можно задать длительность шага в реальных секундах (по умолчанию 1/60). Это время расчёта симуляции, без отрисовки интерфейса. На диагностической копии с 120 партиями прежняя рациональная арифметика после оптимизации пустых окон давала 90–99 мс на шаг 1/60 секунды на скорости 6. После перехода на целые блоки и устранения повторного построения физических количеств повторный замер дал 53–58 мс. При шаге 0,1 секунды: 65–67 мс вместо 117–138 мс. Это замеры расчёта на данной копии, а не обещание FPS. Для сравнения старый JSON был преобразован только во временный тестовый файл; в загрузчик игры миграция не добавлена. Разреженные сценарии сравниваются с полным расчётом в `test_short_lived_events.gd`. ## Целые блоки и дробные остатки `godot --headless --path Owniverse.GI --script res://tests/test_exact_math.gd` Независимый эталон Python Fraction: 160 пар с произведением внутри E27; целые проверяются точно, дроби — с допуском 10^-12. Также границы int64/E27, сериализация, рецепты с дробными стоимостями, сохранение сумм, время и 100 циклов с меняющимися долями. Все остальные тесты используют числовое сравнение и сериализацию через `E`, а не сравнение ссылок на большие числа. Границы точности и новый формат сохранения описаны в [документации арифметики](../world/owniverse/math/README.md). ## Модули симуляции `godot --headless --path Owniverse.GI --script res://tests/test_module_lifecycle.gd` Проверяются отдельные экземпляры модулей и состояния для каждой вселенной, слабая обратная ссылка, освобождение модулей вместе с миром и защита от некорректного delta. Остальные тесты используют прямые API batches, ledger, production, automatic, stellar, life, environment и evolution; временные методы-переадресации Owniverse удалены. Описание границ модулей и порядка фаз: `../world/owniverse/README.md`. ## Математика распределений и каталог `godot --headless --path Owniverse.GI --script res://tests/test_quantity_catalog.gd` Проверяются целочисленность и границы распределения, сохранение суммы, разрешение равных остатков, накопление дробей и долгов округления, нормализация весов, размер производственной партии, загрузка каталога и его индексы. Архитектура описана в `../world/owniverse/README.md`. Сохранение игрока не используется. ## Иконки стадий жизни Тест также проверяет модуль `item_presentation.gd`: отсутствие изменений входного состояния и списка закрытых иконок, решения по разблокировке, отложенную и принудительную публикацию количеств. `godot --headless --path Owniverse.GI --script res://tests/test_life_icons.gd` Проверяются одновременная разблокировка подробных и общих иконок EON/LP и C0.0/C0, количества свободных и связанных планет без двойного учёта, переключение подпанелей и очистка счётчика при исчезновении стадии. Сохранение игрока не используется. ## Параметры мира и индикаторы ```sh godot --headless --path Owniverse.GI --script res://tests/test_world_indicators.gd ``` Проверяются сроки жизни существующих и новых звёзд при смене горючести, вероятности и мощность взрывов, влияние агрессии/порядка/просвещения на жизнь, накопление тенденций по времени и этикам, несколько событий за шаг, продолжение после сериализации, валидация сохранения, границы шкал и описания на двух языках. Сцена использует фикстуру без доступа к сохранению игрока. ## Прямой расчёт завершившихся сверхновых ```sh godot --headless --path Owniverse.GI --script res://tests/test_short_lived_events.gd ``` Сравниваются ускоренный путь и прежний путь с созданием всех партий, отключаемый только в тестовой фикстуре. Проверяются LRG, HRG, RSG и RHG на границах коротких и длинных интервалов, существующие SN, нехватка энергии, ресурсы, бласты, привязки, остатки округления и живые партии. Дополнительно проверяется, что длинный интервал создаёт не более двух живых окон и одну запись завершений, сохраняя общее количество. Сохранение игрока этот тест не использует. `test_stellar_rules.gd` также проверяет автоматическое создание HBD: долю 0,1% нового автоматического водорода, появление по одному со случайным округлением, сохранение оплаченного остатка топлива, отсутствие повторного начисления на старый запас, продолжение случайной последовательности после сериализации и переход HBD → CBD с сохранением счётчика BD. Формулы описаны в `../simulation_rules.md`. ## Фоновое сохранение и частота обновления интерфейса ```sh godot --headless --path Owniverse.GI --script res://tests/test_async_save.gd godot --headless --path Owniverse.GI --script res://tests/test_ui_updates.gd ``` Сохранение: основной поток создаёт глубокий снимок состояния и параметров. Рабочий поток проверяет данные, записывает зашифрованный временный файл, проверяет чтением и заменяет основное сохранение; диагностический JSON создаётся из того же снимка. Одновременно работает один писатель. Для каждого имени файла хранится только последний ожидающий запрос. `request_save()` подтверждает постановку в очередь; результат записи сообщает сигнал `save_completed`. При закрытии игра ждёт `saves_drained`, при приостановке приложения и уничтожении менеджера очередь завершается через `flush_saves()`. Копирование снимка остаётся в основном потоке. Тест сохранений пишет только в уникальную временную папку `/tmp`, затем удаляет собственные файлы. Проверяются независимость снимка, работа кадров при записи, объединение запросов, диагностическая копия, сохранность предыдущего файла при ошибках и завершение очереди при уничтожении менеджера. Сообщения `Invalid save`, `Save blocked` и `Cannot write` в этом тесте ожидаемы. Тест интерфейса проверяет передачу количеств из presentation в контролы, очистку очереди после публикации, отложенное и принудительное обновление, производство по нажатию и формат времени. Stage в текущей реализации применяет очередь счётчиков и время каждый кадр; интервал 0,1 секунды используется для индикаторов. Рефакторинг этот порядок не меняет. Тест использует игровую сцену с отключёнными загрузкой, сохранением и автоматической симуляцией, не обращаясь к сохранению игрока. ## Время: шаг 0,1 игрового года ```sh godot --headless --path Owniverse.GI --script res://tests/test_time.gd ``` Команда выполняется из общей папки проектов. Тесты используют искусственные состояния; сохранение игрока не читается и не перезаписывается. Проверяются границы десятых долей года, производство ресурсов на этих границах, накопление дробного остатка, скорости 1–10 000 лет/сек, продолжение после загрузки и недопустимые интервалы. Схема состояния `0.3.0` хранит: - `time.datetime` — накопленный возраст, включая необработанную дробную часть; - `time.year` — последний рассчитанный возраст с точностью до десятой доли года. Сохранения используют формат 4 и актуальную структуру состояния. Проверяются целостность данных, фоновая запись и повторная загрузка; поддержка и миграция прежних форматов отсутствуют. Файл с несовместимым номером формата при загрузке переносится в `*.adventure.incompatible.bak` (при совпадении имени добавляется числовой суффикс). Игра начинает новую сессию и может сохранять её в формате 4. Прогресс несовместимого файла не переносится, исходные байты сохраняются в архиве. Ошибка архивирования, расшифровки или повреждение данных текущего формата по-прежнему блокируют перезапись. ## Согласованные правила эволюции Сохраняются постепенные интервальные окна GI: срок жизни делится на 40 возрастных групп; начало преобразования — через `lifespan`, длительность окна — `lifespan / 40` при `lifespan > 10`, иначе `lifespan`. На скоростях 4–6 узкие звёздные партии могут объединяться по границе полного цикла смерти шириной 200/2000/20000 игровых лет. Прежнее окно шире цикла сохраняется: долгоживущие звёзды не дробятся. Из существующих партий сливаются только две или более одного типа, чей оставшийся переход полностью попадает в общий цикл; широкие и пересекающие границу партии сохраняются. `test_batch_boundaries` проверяет объединение, точные большие количества и остатки, однократное изъятие, сериализацию, сохранение широких окон, распределённые рождения и интеграл производства. `benchmark_large_star_cohorts.gd` создаёт отдельные большие диагностические сохранения со старой и новой звёздной сеткой при одинаковых количествах и входных интервалах рождения. Переключатель `batches.cycle_stars` используется только для сравнительного замера, в игре он включён. Парные прогоны меняют порядок моделей и сбрасывают исходное состояние перед каждым запуском: ```sh godot --headless --path Owniverse.GI --script res://tests/benchmark_large_star_cohorts.gd -- cycles=50 repeats=2 ``` Сохранения и полные профили записываются в `tests/artifacts/large_star_cohorts_*`; сохранение игрока не затрагивается. `generate_only` проверяет запись/чтение и исходное равенство физических количеств без запуска замеров. `benchmark_many_objects.gd` создаёт новую диагностическую вселенную со всеми типами объектов и цивилизаций и 4800 синтетическими звёздными партиями, попадающими группами в общие циклы. Это отдельный стресс-сценарий большого числа партий; старый и новый расчёты используют одинаковый исходный состав. ```sh godot --headless --path Owniverse.GI --script res://tests/benchmark_many_objects.gd -- cycles=40 repeats=2 ``` Файлы сценария: `tests/artifacts/many_objects_fragmented_stars_*`. Вероятностные исходы используют накопление дробного остатка GI, без случайного округления. Этот же подход выбран для будущего создания планет. Шансы планет записаны долями (`0.01` = 1%), а альтернативные исходы звёзд — процентами (`%0.1` = 0.1%). Парсер планет сохраняет дроби и поддерживает один элемент без `&`. Уязвимость к бластам читается из `blastability` таблицы GI: HP, EON и цивилизации до C1.2 включительно; C1.3 и выше неуязвимы. Это ограничение относится к бластам уровня 1; локальное уничтожение планет уровня 2 в Unity охватывает все планеты. ```sh godot --headless --path Owniverse.GI --script res://tests/test_stellar_rules.gd ``` Проверяются парсер планет, граница уязвимости, накопление редких исходов, сериализация остатков и постепенное преобразование реальной партии RD в TWD. Создание планет подключено к ручному и автоматическому производству звёзд. Бласты обрабатываются как события, а не ресурс. Выжившие планеты переходят преемнику звезды пропорционально числу реально полученных преемников; при отсутствии преемника освобождаются. Локальное разрушение выполняется перед передачей. Дополнительно проверяются частичная передача, альтернативные преемники, уровни разрушения 1/2, накопление слабых бластов, сохранения, ручное/автоматическое создание планет и несколько поколений за длинный шаг. Правила и формулы описаны в `../simulation_rules.md`. ## Описания выбранных объектов ```sh godot --headless --path Owniverse.GI --script res://tests/test_descriptions.gd ``` Источник текста — `Global.Templates[Global.Locale]`. Заголовок берётся из `Name`, основной текст — из `Description`. Поддерживаются ключи с классом (`RD.Entity.Description`) и старые ключи без класса (`RD.Description`). `%valueN%` заменяется значением переменной, имя которой записано в поле `valueN` этой строки. Поддерживаются `amount`, `colored costs`, `lifespan`, `voids`, `he / h`, `metallicity`, показатели энергии, плотности и скорости времени. `amount1`–`amount4` сил пока отображаются как `—`; неизвестные непустые обозначения тоже дают `—`, пустые поля — пустую строку. `[p]` превращается в разделение абзацев, цветовые BBCode-теги сохраняются. Доступные требования зелёные, недоступные красные. Для объектов со связанными экземплярами выводится доступное количество и серым общее; для планет — общее количество. Скорость изменения вычисляется по реальному времени и обновляется раз в 0,25 секунды. Доли He/H вычисляются из текущих свободных запасов; при пустой смеси показывается `—`. Металличность и плотность используют расчёт среды GI. Счётчики исходной материи и этики Unity в текущей GI отсутствуют и здесь не добавляются. ## Первичная жизнь ```sh godot --headless --path Owniverse.GI --script res://tests/test_life.gd ``` Проверяются абиогенез, деградация, повторные попытки без создания дополнительных планет, появление C0.0 на прежней звезде, остатки вероятностей, сохранение/продолжение, однократная инициализация старых планет и отсутствие воскрешения после бластов. Проверка загрузки этических правил, распределений и отделения космических цивилизаций: ```sh godot --headless --path Owniverse.GI --script res://tests/test_civilization_state.gd ``` Тест цивилизаций дополнительно проверяет общий звёздный бюджет, повторное использование резерва при переходе, звёзды в структурах, массовый эквивалент SMBH, блокировку неподдержанного прогресса, каскад деградации и повторное применение ограничений без потерь. ## Полная эволюция цивилизаций ```sh godot --headless --path Owniverse.GI --script res://tests/test_civilization_evolution.gd ``` Проверяются реальные коэффициенты C0.2, войны и побочный ущерб C2.0, замена планет колониями с сохранением хозяина, дипломатическое влияние, бонус периода от структур и деградация при их отсутствии. Ускоренная модель проходит все стадии C0.2 → C4.4 → Ct. Проверки контролируют сохранение населения, целочисленность этик, валидацию сохранения и детерминированное продолжение после сериализации. ## Профиль цивилизаций на шестой скорости ```sh godot --headless --path Owniverse.GI --script res://tests/benchmark_civilizations.gd -- frames=20 profile godot --path Owniverse.GI --script res://tests/benchmark_civilizations.gd -- ui scenario=mixed frames=10 profile ``` Сценарии `mixed`, `large`, `fragmented` заполняют все стадии и этики, синхронизируя переходы; `large` использует по 6e20 цивилизаций, `fragmented` — десять партий. Это искусственные стрессовые сценарии. `scenario=stable` использует штатные сроки стадий. Параметр `snapshot=/tmp/civil` сохраняет состояние каждого сценария для сравнения до и после изменения. UI запускает настоящую сцену со страницей жизни и реальным delta, без загрузки и записи сохранения игрока. `profile` выводит суммарные микросекунды и число вызовов по этапам. Вложенные этапы входят во время родительских; складывать их нельзя. В игре сбор выключен. Кэши физических количеств принадлежат отдельному миру и проверяют изменения количеств, вложенных привязок и индексов каталога. Кэш структуры правил не входит в сохранение: при изменении Claim/бонусов нужно перестроить индексы каталога, при изменении home в существующей таблице — invalidate_civilization_layout(). После оптимизации на M4 Pro, по 20 шагов 1/60 секунды, среднее время: `mixed` 295 → 235 мс, `large` 666 → 539 мс, `fragmented` 359 → 282 мс. Полные состояния всех трёх сценариев после 20 шагов совпали до и после изменения. Дополнительно тест распределений сравнивает новый путь с прежним на 300 случайных наборах, включая 1e40 и накопленные долги округления. ## Расчёт между кадрами `test_flow_commit.gd` проверяет консолидированное применение переходов и колонизации: точные квоты получателей, сохранение количества у каждого владельца, профили этик, статистику создания и отсутствие привязок у Ct. Проверки используют несколько владельцев и численность 103, 10^40 + 103 и 10^400 + 103. Дополнительно проверяется точное совпадение обновляемых общих количеств с полным пересчётом после переходов, колонизации и каскадной деградации при нехватке ёмкости. Входные снимки остаются неизменными. `test_production_transaction.gd` проверяет, что успешное создание обновляет общий снимок, привязка сохраняет общий ресурс, а отклонённая операция сохраняет и состояние, и снимок. ```sh godot --headless --path Owniverse.GI --script res://tests/test_flow_commit.gd ``` `godot --headless --path Owniverse.GI --script res://tests/test_frame_evolution.gd` Stage вызывает `evolute_frame`: транзакция получает бюджет 40 мс и уступает следующему кадру внутри создания партий и обработки стадий. Расчёт остаётся на главном потоке, состояние публикуется только после полного завершения. Запрошенные игровые часы и счётчик UI обновляются независимо от тяжёлого расчёта; Stage показывает запрошенное время раз в 0,1 секунды (10 Гц), а опубликованное состояние мира остаётся на последнем завершённом цикле. Расчёт запускается после накопления выбранного интервала и обрабатывает весь ожидающий интервал. Время, поступившее во время расчёта, обрабатывается следующим циклом. Контроллер нагрузки меняет целевой шаг между 0,2, 0,25, 0,333 и 0,5 секунды (5/4/3/2 цикла в секунду): вниз при EMA расчёта выше 90% текущего шага, вверх после 30 циклов ниже 65%. Временное разрешение укрупнения партий масштабируется с шагом, исходная точность сохраняется на 5 Гц. Контроллер не пропускает накопленное игровое время, но 2/с — нижняя целевая частота, не гарантия при перегрузке. `benchmark_dynamic_cadence.gd` сравнивает пропускную способность полного цикла на двух диагностических сохранениях. `benchmark_dynamic_cadence_ui.gd` сравнивает 10 секунд работы полной сцены на сохранении со всеми объектами; результаты сохранены в `artifacts/dynamic_cadence_results.json` и `artifacts/dynamic_cadence_ui_results.json`. Производство во время транзакции выполняется после её завершения. Запрошенное время, активный интервал, скорость и очередь производства входят в сохранение; при загрузке незавершённый интервал пересчитывается с опубликованного состояния. Сброс мира отменяет отложенный расчёт. Тест проверяет точное совпадение непрерывного и распределённого расчёта, очереди, переключение скорости, восстановление и отмену. На M4 Pro, 180 кадров реального UI на шестой скорости: смешанный сценарий 15.9 FPS (максимальный кадр 98 мс), большие количества 17.5 FPS (106 мс), много партий 16.3 FPS (97 мс). Это FPS отрисовки, а не число завершённых транзакций симуляции в секунду. Индекс партий переиспользуется внутри одной транзакции, отдельно для каждого массива партий. Замена массива при изъятии или переход к другому состоянию сбрасывает индекс. Сортировка выполняется при нарушении порядка новых окон, а не после каждого добавления в уже существующее окно. `test_batch_boundaries` проверяет объединение, раннюю вставку, замену массива и изоляцию состояний. На копии текущего сохранения с 785 партиями шесть шагов на скорости 6: среднее время 167 → 145 мс; полное состояние после шести шагов совпало точно. Следующий этап исключает арифметическую работу при прибавлении нуля, временные знаменатели при умножении целых блоков и нулевые вклады этик. На той же копии сохранения среднее время шага скорости 6 снизилось с 145 до 138 мс; полный результат после шести шагов совпал с исходным состоянием. Тест появления разума задаёт свой progress явно, поскольку проверяет сохранение хозяина планеты, а не актуальное значение коэффициента баланса. Следующие шесть оптимизаций: однократное изъятие умирающих партий; поверхностные снимки неизменяемых таблиц связей; пропуск планирования цивилизаций без переходов; кэш нормализованных этик и произведений поклонения; пропуск укрупнения для нуля/одной партии; кэш вероятностей переходов. Кэши принадлежат миру и не входят в сохранение. Изменение количества, этик, коэффициентов правил или модификаторов обнаруживается по значениям. Суммирование поклонения сохраняет прежний порядок дробных операций. `test_simulation_optimizations` проверяет также обновление этих кэшей. Полное состояние совпало с реализацией до этих шести изменений после каждого из восьми шагов на скоростях 1, 4 и 6, отдельно на большом фрагментированном сценарии и на диагностической копии сохранения (48 сравнений). На M4 Pro шесть шагов по 0.1 секунды скорости 6: 103.3 мс первый, 70.9–76.0 мс последующие; пропускная способность без UI 1.278. Замер 600 кадров с реальным UI: 12.24 FPS, p95 97.16 мс, максимум 104.86 мс, 520 завершённых транзакций, отношение обработанного времени к поступившему 1.057. Замер включает сокращение накопленного отставания сохранения; это результат конкретного сценария на Mac, а не гарантия для Android. Целевая частота запуска — пять циклов в секунду. Размер шага равен всему накопленному интервалу. Checkpoint сохраняет границу активного расчёта и ожидающее реальное время. Тест проверяет все шесть скоростей, смену скорости, задержку запуска и накопление времени во время расчёта. Дипломатический drift использует шесть взвешенных вкладов вместо матрицы пар этик: вес = сумма вкладов + 195 × свой вклад − противоположный вклад. Для колониальной матрицы коэффициенты 9, 951 и 5 соответственно. Целая численность и механизм сохранения ошибок распределения не меняются, но порядок операций над дробными весами отличается от матричного варианта. Тест сравнивает с матрицей на точно представимых коэффициентах и проверяет целочисленное сохранение популяций до 1e40 на десятичных коэффициентах. Пустые популяции пропускают drift; численность выходов суммируется один раз. На профильной копии при шаге 0.5 число вызовов снизилось 1038 → 849 за шесть шагов, но время drift осталось около 303 мс суммарно; заметного ускорения полного расчёта в этом сценарии не зафиксировано. `test_split_optimizations.gd` сравнивает распределение и все сохранённые остатки с прежним scalar split в последовательных вызовах: пустые ветви, единственная ветвь, равные веса, десятичные веса, положительные/отрицательные остатки, количества до 1e80. Также проверяет grouped mul_div против scalar. Split переиспользует разложение количества и знаменателя между ветвями, кэширует ключи остатков (не более 512 наборов) и получает веса дрифта массивом. Вторая нормализация пропускается только при целых долях с нужной суммой: при дробях даже единичное отношение может изменить округление, поэтому прежний путь сохраняется. Единственная положительная ветвь обрабатывается напрямую только при целом количестве, неотрицательных весах и нулевых остатках. На профильной копии время drift за шесть шагов 0.5 снизилось примерно 303 → 287 мс, но общее время осталось около 1.31 секунды. Обычный drift сокращает целое количество из отношения count*weight/(200*count), колониальный — из отношения с 1000. Целая часть делится на малый знаменатель; для дроби используются исходные масштабированные числитель и знаменатель, чтобы сохранить float округление. `test_split_optimizations` проверяет совпадение со scalar mul_div до 1e80. Производство новорождённых сохраняет только created_from → amount вместо глубокой копии партий. Повторное мгновенное рождение с теми же сроками, длительностью и сеткой сразу добавляется в найденную партию. Индекс ближайшей границы позволяет повторным проходам пропускать будущие партии; замена массива, добавление и масштабирование сбрасывают индекс. Тест границ проверяет объединение и сброс обоих индексов. Адаптивный шаг дополнительно учитывает сглаженное отношение реальной длительности транзакции к обработанному времени. Если отношение выше 0.85 и долг больше одного шага, ступень увеличивается раньше порога двух шагов. Долг и граница сохраняются как прежде; измерения являются временным кэшем.