Решили: завели scripts/fast-tests.ps1 — прогон EditMode-тестов мимо редактора: сборки берутся у compile-check.ps1, раннер (tools/FastTests) грузит их и зовёт методы с [Test] напрямую. 499 тестов проходят за 7 секунд, полный прогон через Unity остаётся за run-tests.ps1 и CI.

Почему: в batchmode платится не за тесты, а за запуск редактора — лицензия, импорт, domain reload, компиляция (замер: компиляция 7.5 с, domain reload 1.1 с, инициализация ~10 с), тогда как сами тесты считаются секунды. Отвергнутая альтернатива — ускорять сам batchmode (-nographics, -disable-assembly-updater): это экономит секунды из десятков, то есть лечит симптом. Вторая отвергнутая — переписать тесты, чтобы они не звали движок: тесты следуют за игрой, а не наоборот.

Сказано: «согласен, если никак хуже не сделает» (2026-08-03, сессия 87bf8fd2) — отсюда весь дизайн вокруг одного требования: не выдать ложный зелёный.

Как это требование выполнено:

  • Три исхода вместо двух. Тест, упавший на отсутствии движка, не «провал» и не «успех», а «нужен редактор» — их 611, и они НЕ зачтены. Признак движка: SecurityException со словом ECall (сборки Unity полны internal call’ов, которые линкует только её рантайм), DllNotFoundException, TypeLoadException, No log scope is available (это LogAssert из Unity Test Framework).
  • Пустой прогон — провал, как и в run-tests.ps1: опечатка в фильтре не должна выглядеть успехом. Отдельный код 3 на «пусто в одной сборке» — при фильтре это нормально, и решает вызывающий, который видит все наборы.
  • Список «недостоверных вне редактора» печатается поимённо. Сейчас там один тест — он считает аллокации через GC, а вне Unity сборщик другой. Молча выкинутый тест неотличим от забытого.
  • Проверено саботажем: сломанный AttackTiming.AttacksPerSecond (×1.5) прогон поймал и вернул 1. Без такой проверки «зелёный за 7 секунд» — это вера, а не факт.

Грабли:

  • Финализатор движкового объекта убивает процесс целиком. VisualElement.Finalize зовёт Debug.LogError → internal call → SecurityException в потоке сборщика мусора. Поймать нельзя, процесс падает в случайный момент, унося отчёт по всем тестам. Лечится глушением логгера (Debug.unityLogger.logEnabled = false) плюс разведением сборок по отдельным процессам.
  • Покрытие оказалось вдвое ниже оценки по файлам. Считала по «файлы, где нет GameObject» — вышло 148 из 178; на деле запускается 499 из 1121 теста (45%). Причина: ScriptableObject.CreateInstance для контентных SO рассыпан по тестам, которые в остальном чистая логика.
  • Прогон видит и чужую поломку. На проверке всплыл CursorSkinData (домен cursor) — незакоммиченный тип соседней сессии без папки в ContentPaths. Полезно, но красный не всегда твой.

Владелец правды: tools/FastTests/Program.cs, scripts/fast-tests.ps1.