Files
owniverse/tests/README.md
T
Kirill c4e7302567 Add new tests and implement frame budget management
- Introduced `civilization_benchmark.gd` and `legacy_quantity_math.gd` for benchmarking and legacy quantity calculations.
- Created `test_dynamic_math.gd`, `test_frame_evolution.gd`, and `test_sparse_batches.gd` to validate mathematical operations and frame evolution logic.
- Implemented `frame_budget.gd` to manage cooperative work on the main thread, ensuring no partially calculated state is published.
- Added UID files for all new scripts to maintain unique identifiers.
2026-10-01 23:43:34 +03:00

24 KiB
Raw Blame History

Проверки поэтапного переноса из Owniverse.U

Производительность на сохранении

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, а не сравнение ссылок на большие числа. Границы точности и новый формат сохранения описаны в документации арифметики.

Модули симуляции

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, количества свободных и связанных планет без двойного учёта, переключение подпанелей и очистка счётчика при исчезновении стадии. Сохранение игрока не используется.

Параметры мира и индикаторы

godot --headless --path Owniverse.GI --script res://tests/test_world_indicators.gd

Проверяются сроки жизни существующих и новых звёзд при смене горючести, вероятности и мощность взрывов, влияние агрессии/порядка/просвещения на жизнь, накопление тенденций по времени и этикам, несколько событий за шаг, продолжение после сериализации, валидация сохранения, границы шкал и описания на двух языках. Сцена использует фикстуру без доступа к сохранению игрока.

Прямой расчёт завершившихся сверхновых

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.

Фоновое сохранение и частота обновления интерфейса

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 игрового года

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: начало преобразования — через lifespan, длительность окна — lifespan / 40 при lifespan > 10, иначе lifespan.

Вероятностные исходы используют накопление дробного остатка GI, без случайного округления. Этот же подход выбран для будущего создания планет. Шансы планет записаны долями (0.01 = 1%), а альтернативные исходы звёзд — процентами (%0.1 = 0.1%). Парсер планет сохраняет дроби и поддерживает один элемент без &.

Уязвимость к бластам читается из blastability таблицы GI: HP, EON и цивилизации до C1.2 включительно; C1.3 и выше неуязвимы. Это ограничение относится к бластам уровня 1; локальное уничтожение планет уровня 2 в Unity охватывает все планеты.

godot --headless --path Owniverse.GI --script res://tests/test_stellar_rules.gd

Проверяются парсер планет, граница уязвимости, накопление редких исходов, сериализация остатков и постепенное преобразование реальной партии RD в TWD.

Создание планет подключено к ручному и автоматическому производству звёзд. Бласты обрабатываются как события, а не ресурс. Выжившие планеты переходят преемнику звезды пропорционально числу реально полученных преемников; при отсутствии преемника освобождаются. Локальное разрушение выполняется перед передачей.

Дополнительно проверяются частичная передача, альтернативные преемники, уровни разрушения 1/2, накопление слабых бластов, сохранения, ручное/автоматическое создание планет и несколько поколений за длинный шаг. Правила и формулы описаны в ../simulation_rules.md.

Описания выбранных объектов

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 отсутствуют и здесь не добавляются.

Первичная жизнь

godot --headless --path Owniverse.GI --script res://tests/test_life.gd

Проверяются абиогенез, деградация, повторные попытки без создания дополнительных планет, появление C0.0 на прежней звезде, остатки вероятностей, сохранение/продолжение, однократная инициализация старых планет и отсутствие воскрешения после бластов.

Проверка загрузки этических правил, распределений и отделения космических цивилизаций:

godot --headless --path Owniverse.GI --script res://tests/test_civilization_state.gd

Тест цивилизаций дополнительно проверяет общий звёздный бюджет, повторное использование резерва при переходе, звёзды в структурах, массовый эквивалент SMBH, блокировку неподдержанного прогресса, каскад деградации и повторное применение ограничений без потерь.

Полная эволюция цивилизаций

godot --headless --path Owniverse.GI --script res://tests/test_civilization_evolution.gd

Проверяются реальные коэффициенты C0.2, войны и побочный ущерб C2.0, замена планет колониями с сохранением хозяина, дипломатическое влияние, бонус периода от структур и деградация при их отсутствии. Ускоренная модель проходит все стадии C0.2 → C4.4 → Ct. Проверки контролируют сохранение населения, целочисленность этик, валидацию сохранения и детерминированное продолжение после сериализации.

Профиль цивилизаций на шестой скорости

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 и накопленные долги округления.

Расчёт между кадрами

godot --headless --path Owniverse.GI --script res://tests/test_frame_evolution.gd

Stage вызывает evolute_frame: транзакция получает бюджет 40 мс и уступает следующему кадру внутри создания партий и обработки стадий. Расчёт остаётся на главном потоке, состояние публикуется только после полного завершения. Длительность одной транзакции ограничена обычным шагом 1/60 секунды выбранной скорости. Запрошенное время хранится отдельно, поэтому отставание не превращается в один всё более тяжёлый шаг. При предельной нагрузке время симуляции может отставать от запрошенного; это не ограничение FPS интерфейса и не удаление событий.

Производство во время транзакции выполняется после её завершения. Запрошенное время, активный интервал, скорость и очередь производства входят в сохранение; при загрузке незавершённый интервал пересчитывается с опубликованного состояния. Сброс мира отменяет отложенный расчёт. Тест проверяет точное совпадение непрерывного и распределённого расчёта, очереди, переключение скорости, восстановление и отмену.

На M4 Pro, 180 кадров реального UI на шестой скорости: смешанный сценарий 15.9 FPS (максимальный кадр 98 мс), большие количества 17.5 FPS (106 мс), много партий 16.3 FPS (97 мс). Это FPS отрисовки, а не число завершённых транзакций симуляции в секунду.