Критерии рефакторинга и форматирования скриптов (TLA)
Единый стандарт для приведения Scripts/*.fos к читаемому, аккуратному и
консистентному виду. Документ — рабочий чек-лист для рефакторинга «раунда 2»
(полировка + заголовки + комментарии + багфиксы + тесты). План и статус ведутся в
Refactoring.md; этот файл описывает как должен выглядеть код.
Язык комментариев — русский (решение владельца, 2026-06-20). Это сознательный разворот прежней англоязычной конвенции; см. AGENTS.md. Сериализуемые имена (
///@ Property/Enum/Setting/Event, прото-id, ключи) остаются на английском — их трогать нельзя (риск миграции данных).
0. Принципы (поверх всего)
- Поведение сохраняем, если явно не чиним баг. Косметика (формат, заголовки, комментарии, переименование локалей) — строго без смены поведения. Багфиксы — отдельным осознанным шагом, с восстановлением исходного замысла (чтение кода + git, где это несущее).
- Без бездумных пакетных правок. Воркфлоу-агенты анализируют и предлагают; применяем обдуманно, малыми порциями, с верификацией. Спорное и меняющее геймплей — выносим списком на решение владельца. См. [feedback в памяти про careful-refactor].
- Закомментированный код не удаляем пакетно. Часто это breadcrumb к миграции
(отключённые
init-ы подсистем, ссылочные алгоритмы Fallout2). Каждый блок — точечное решение: оставить / правильно мигрировать / удалить только если точно мёртв. - Нельзя ломать сборку. Каждая порция проходит верификацию (раздел 9). Предупреждения = ошибки.
- Не трогаем генерируемое:
Content.fos,GuiScreens.fos(правка — через.fogui+ генератор),Baking/,Cache/,VERSION.
1. Заголовок файла (обязательно во всех скриптах)
В начало каждого Scripts/*.fos (кроме генерируемых) добавляем блок-описание на
русском — что делает модуль, его зона ответственности и сторона исполнения. Ставится
над строкой namespace.
Формат:
// <Имя модуля> — <одно-два предложения: за что отвечает модуль>.
// <Опционально: ключевые обязанности списком, связи с другими модулями,
// особенности (сторона SERVER/CLIENT/MAPPER, async, точки подписки)>.
namespace ModuleName
{
Правила:
- Первая строка — суть в одном предложении (отвечает на «зачем этот файл»).
- Если модуль и серверный, и клиентский — отметить, что где живёт.
- Без баннеров (
// Author:, даты,vanilla,////////-разделителей) — их вычистили раньше, не возвращаем. - Заголовок описывает намерение, а не построчный пересказ кода.
Пример:
// Reputation — пороги и уровни репутации/кармы игрока.
// SERVER: чистые предикаты уровня репутации по числовому значению.
// CLIENT: преобразование числа в KarmaLevel/ReputationLevel для интерфейса.
namespace Reputation
{
2. Комментарии к блокам кода
Цель — чтобы по комментариям читалось намерение, а не механика строк.
- Язык — русский. Существующие английские комментарии переводим на русский по ходу правки файла (раунд 2 разрешает перевод; см. решение владельца).
- Комментируем «почему», а не «что» очевидное.
i++ // увеличить i— мусор. - Над логическим блоком — короткая фраза-заголовок блока (
// Считаем урон с учётом брони). - Несущие инварианты, неочевидные edge-case’ы, причины «странного» кода (миграционные, совместимость, баги движка) — комментируем обязательно.
- Стиль:
// текст(один пробел после//). Не////, не ASCII-рамки. - Закомментированный код, если оставляем как breadcrumb, помечаем причиной
(
// отключено до домиграции подсистемы X), а не оставляем «голым». - Кириллица в комментариях допустима и ожидаема (валидатор это учитывает).
3. Нейминг
| Сущность | Стиль | Пример |
|---|---|---|
namespace |
PascalCase == имя файла | namespace Combat в Combat.fos |
| Функции | PascalCase | GetKarmaLevel, IsReputationLiked |
| Локали/параметры | camelCase | playerCr, targetHex, apCost |
| Новые константы | PascalCase | const int MaxRetries |
| Легаси флаг-группы (сериализуемые/битовые) | UPPER_SNAKE — не трогаем | HF_DEAD, USE_RELOAD, AP_DIVIDER |
///@ Property/Enum/Setting |
PascalCase, владеет TLA | CurrentHp, MaxLife |
Правила:
- Булевы функции/переменные — префиксы
Is/Has/Can/Should. - Имена осмысленные:
cr,npc,item,map,loc— приняты и допустимы; одиночныеa,b,xвне коротких циклов — переименовать. - Сериализуемые имена не переименовываем ради красоты (свойства, прото-id, enum’ы,
ключи текст-паков) — это контракт сохранёнок/сети/контента. Опечатки в них правим
только через
///@ MigrationRuleи подтверждение владельца. - Согласуем имена внутри модуля: один концепт — одно имя (не
cnt/count/numвперемешку).
4. Структура модуля (порядок сверху вниз)
Внутри namespace держим стабильный порядок — от «контракта» к деталям:
- Метаданные
///@ Setting / Enum / Property / Event / RemoteCall / RefType— вверху. - Константы модуля (
const ...). [[ModuleInit]]+ подписки на события (точка входа модуля).- Публичные функции (то, что зовут другие модули) — высокоуровневое раньше.
- Обработчики событий (
[[Event]],[[TimeEvent]],[[*RemoteCall]]). - Приватные хелперы — низкоуровневые детали внизу.
Дополнительно:
- Группируем по
#if SERVER/#if CLIENT/#if MAPPER; не размазываем серверную и клиентскую логику вперемешку. Проверяем баланс#if/#endif. - Связанные функции держим рядом (реорганизация в логичные расположения — одна из целей раунда). Перенос функции между файлами допустим, когда она по смыслу принадлежит другому домену — но это поведенческая правка: верифицируем (раздел 9) и делаем малой порцией.
- Одна пустая строка между функциями; внутри функции — пустая строка отделяет логические блоки. Максимум одна пустая строка подряд (enforced clang-format).
5. Механическое форматирование (отдаём форматтеру)
Источник правды — Scripts/.clang-format + постобработка Tools/Formatter/format_project.py
(VS Code task Format :: Scripts). Руками не воюем с форматтером. Ключевое из конфигурации:
- Ширина строки 160, отступ 4 пробела, табы не используем.
- Тело верхнего
namespaceне отступается (NamespaceIndentation: Inner). - Скобка функции — на новой строке; скобка управляющей конструкции (
if/for/while) — на той же строке (if (x) {). - Однострочные
ifполучают скобки автоматически (InsertBraces: true) — пишем со скобками всегда. - Ровно одна пустая строка в конце файла (валидатор это проверяет).
- Нельзя гонять
Tools/clang-format-20.exeнапрямую по.fos/.fogui— он ломает nullable-суффиксT?. Только черезformat_project.py(он чинитT?). - Концы строк — CRLF (как во всём
.fos-дереве). Точечные правки (Edit) сохраняют CRLF; при полной перезаписи файла проверь, что не получился LF (форматтер сравнивает без учёта концов строк и LF не поймает — приводи к CRLF явно).
6. Идиомы AngelScript (приводим к современным)
- Нет
#include. Кросс-модульные вызовы —Namespace::Function(). - Nullable — по контракту
T?(см. Nullability.md). РезультатыGame.GetCritter/GetItem/..., словарныхget(...)— вT?-локаль, потом сужаем (if (x == null) return;). Не сравниваем компонент-аксессор сnull— проверяемHas<Component>(item.HasRadio,cr.HasDialogContext).Game.Chosen— гардHasChosen, не== null. - Компилятор строго проверяет nullability (warnings = errors): убираем избыточные
== null, сужаемT?перед разыменованием,cast<T?>(x)когда даункаст может не удаться. - Native/engine вместо самописного: массивы —
.find/.insertLast/.exists(неUtilsForArray); математика поint—Math::Clamp/Min/Max/Abs(дляfloat—*F;double/anyоставляем как есть, если перегрузки нет). - Геометрия гексовая:
Game.GetDistance/GetDirection, пасфайндинг, трейс — не изобретать прямоугольную математику. - Время:
Time::Milliseconds/Seconds/Asap. - EventResult: обработчики возвращают
void(неявный continue) илиEventResult::ContinueChain/StopChain. Следим за полярностью (исторический источник багов:? StopChain : StopChain, инверсии после миграций) — выводим из потребителя+git. - Async: воркер-колбэки помечаем
[[Async]]; перед доступом к map-видимому состоянию криттера —Sync::Lock....Game.Sync(...)заменяет весь набор локов — покрываем всё нужное одним вызовом. - Параметры: не передаём вход по
const &;&только для настоящих out/inout value-типов. not/and/or— валидные кейворды AngelScript, в проекте используются; не «чиним» на!/&&/||ради единообразия (это churn без пользы).
7. Антипаттерны (выпрямляем)
if (cond) { return true; } else { return false; }→return cond;(только для чисто-условных, без сайд-эффектов/комментов).- Двойное деление/умножение на
AP_DIVIDER, потерянная полярность тернарника, инверсии гардов после миграций — частые баги, проверяем прицельно. - Защитные «глушилки» инвариантов (широкий
if (x == null) return;там, гдеnullневозможен) — убираем; падаем громко (verify(...)) на нарушении контракта. - Магические числа текст-паков (
""+1234) — именованный ключ, только если можно сопоставить id по.fotxt(иначе риск показать не тот текст; оставляем + TODO). - Мёртвый код (недостижимые ветки, неиспользуемые приватные функции) — удаляем (это не «закомментированный код» из п.3; настоящий мёртвый код выпиливаем).
8. Багфиксы (раунд агрессивный — применяем, но верифицируем)
- Каждый фикс перепроверяем чтением кода; для несущей логики — сверяем с git-историей (восстановить замысел до миграции движка).
- Меняющее сериализуемый контракт (Property/прото/сеть/сейв) — через
///@ MigrationRuleи подтверждение владельца. - Меняющее геймплей-баланс/квестовую логику так, что headless-smoke не поймает — применяем, но явно отмечаем в отчёте порции (нужен плейтест владельца).
- Кластерные баги (одинаковый паттерн в N местах) ищем грепом, чиним пачкой с единой верификацией полярности.
9. Верификация (каждая порция — обязательно)
Порядок (предупреждения трактуем как ошибки):
Compile AngelScript→TLA_ASCompiler.log/Build/_errors.txt(0 warnings).Bake Resources(после переноса///@ Property— Force Bake).- Затронутые
Build :: TLA_*(минимумTLA_Server, при клиентских правкахTLA_Client). - Серверо-поведенческое —
TLA_ServerHeadlessдо"Start server complete!"без исключений. Analyze :: Script Quality(--ratchet: без новых нарушений) +validate_nullable.py.- Затронут движок/нативная граница —
TLA_UnitTests. Format :: Scriptsперед сдачей порции.
Не коммитим/не стейджим/не пушим — владелец ревьюит и коммитит сам.
10. Тесты («обкладываем тестами»)
Сейчас в TLA нет скриптового геймплей-харнесса (только C++ Test_*.cpp в движке).
Эталон H:/lf-7 имеет полноценный AngelScript-харнесс (Scripts/Testing.fos + Scripts/Tests/Test_*.fos,
~72 сьюта); движок TLA нужные API уже предоставляет (Game.GetEntityRegistryCount,
CreateUnloginedPlayer, GetAllocatorMemoryUsage, verify(...) и т.д.).
Стратегия тестирования выбирается владельцем (см. план в Refactoring.md, раздел «Round 2»). Базовый принцип: пишем детерминированные тесты, начиная с чистых функций-хелперов (Reputation, Math/Flags, GameTime, WeaponHelpers), затем — критичные серверные флоу через фикстуры (создать изолированную локацию → заспавнить криттера/предмет → проверить → прибрать без утечек).
11. Готовность порции (Definition of Done)
- Заголовок-описание файла на месте (русский).
- Комментарии к блокам осмысленные, на русском; «почему», а не «что».
- Нейминг по разделу 3; сериализуемые имена не тронуты.
- Структура модуля по разделу 4;
#if-секции сгруппированы и сбалансированы. - Идиомы/nullability по разделам 6; антипаттерны выпрямлены.
- Багфиксы верифицированы; спорное/геймплейное вынесено в отчёт.
- Форматтер прогнан; верификация (раздел 9) зелёная.
- Закомментированный код не вырезан вслепую; миграционные breadcrumbs сохранены.
</content> </invoke>