SusAudit — встроенные проверки (Debug / QA)
Автоматические модули аудита, встроенные в
sus-coreиsus-router. Все они включаются через#if UNITY_EDITOR || DEVELOPMENT_BUILD. В release-сборке компилятор полностью вырезает их — нулевые накладные расходы в рантайме.

Сводная таблица
| # | Модуль | Где встроен | Триггер | Автоматически |
|---|---|---|---|---|
| 1 | ClickAudit | SusComponent + ClickAuditService | Каждый ClickEvent на зарегистрированных элементах | ✅ |
| 2 | BoundsAudit | конструктор SusComponent | 2 кадра после mount | ✅ |
| 3 | CallbackAudit | методы SusComponent.cs + 16 .sharq | условия guard / тайминг в обработчике ClickEvent | ⚠️ .sharq |
| 4 | OverlayAudit | OverlayHost.AddToOverlay() | Добавление элемента в overlay | ✅ |
| 5 | StateAudit | .sharq (SusButton/Toggle/Dropdown/ListGroup/Modal) | WatchEffect на противоречивых props | ⚠️ .sharq |
| 6 | LifecycleAudit | SusComponent.OnDetachFromPanelHandler() | Detach от panel | ✅ |
| 7 | NavigationAudit | SusRouter.Push/Replace/PushNamed/ReplaceNamed | Resolve() вернул null | ✅ |
| 8 | PerformanceAudit | конструктор SusComponent | 500 мс после mount | ✅ |
| 9 | DebounceAudit | конструктор SusComponent | Каждый ClickEvent на интерактивных элементах | ✅ |
| 10 | ClickTargetSizeAudit | конструктор SusComponent (вместе с BoundsAudit) | 2 кадра после mount | ✅ |
| 11 | StackDepthAudit | SusRouter.PushRecord | После _history.Add() | ✅ |
| 12 | GuardAudit | SusRouter.Navigate() | После NavigateCore — если результат Aborted | ✅ |
| 13 | ModalStackAudit | OverlayHost.AddToOverlay() | Добавление модалки в overlay | ✅ |
| 14 | EmptyStateAudit | .sharq (SusListGroup/SusDropdown) | Items.Count == 0, но элемент открыт/виден | ⚠️ .sharq |
| 15 | RemountLoopAudit | SusComponent.OnAttachToPanelHandler() | >5 Attach за 1 секунду | ✅ |
| 16 | OverflowAudit | конструктор SusComponent | Дети выходят за границы родителя (Unity не делает clip) | ✅ |
| 17 | DeadRouteAudit | SusRouter (ручной вызов AuditUnusedRoutes()) | Зарегистрированные, но никогда не использованные маршруты | 🔧 вручную |
| 18 | SusTable StateAudit | .sharq (SusTable) | WatchEffect — ItemsPerPage > Items или Page > TotalPages | ⚠️ .sharq |
| 19 | LayoutReentryAudit | SusComponent.OnGeometryChangedForBreakpoint() | >20 GeometryChanged за 500 мс | ✅ |
| 20 | IdleGuardAudit | конструктор SusComponent | Элемент виден 30+ секунд без кликов | ✅ |
| 21 | FocusTrapAudit | .sharq (SusModal) | FocusEvent — Tab уводит фокус из модалки | ⚠️ .sharq |
✅ = полностью автоматически, ничего добавлять в компоненты не нужно. ⚠️ = требует одной строки
SetClickAuditDescription("Name")илиWatchEffectв.sharq(уже встроено во все компоненты). 🔧 = ручной вызов метода.
1. ClickAudit — доходит ли клик до элемента
Файлы: sus-core/Runtime/Diagnostics/ClickAuditService.cs, sus-core/Runtime/SusComponent.cs
Проблема: элемент зарегистрирован как кликабельный, но при клике мышью событие до него не доходит — его перехватывает другой элемент сверху (tooltip, overlay, модалка с неправильным pickingMode).
Как это работает:
ClickAuditService.Instance.Install(panel)— вызывается автоматически при первомSusBootstrap.Mount<T>().- Сервис регистрирует глобальный
ClickEventнаpanel.visualTree. - При каждом клике сравнивает
evt.targetс зарегистрированными элементами. - Если клик не попал ни в один зарегистрированный элемент, логируется слой перехватившего элемента.
Пример предупреждения:
[ClickAudit] Active: 'SusButton' blocked at center. Reason: Covered by 'SusTooltip'Интеграция: элемент регистрируется через SetClickAuditDescription("Name") в Created(). Уже встроено в 50 downstream-компонентов (kit 40 + game 10) .
2. BoundsAudit — ненулевые ли размеры
Файл: sus-core/Runtime/SusComponent.cs (конструктор)
Проблема: Unity UITK не вызывает ContainsPoint для элементов с нулевыми размерами. Клик проходит насквозь. Причины:
- Нет
flexGrow,width,heightв USS/стилях - Родитель не задал размер
display: none/visible: false- Элемент не добавлен в дерево (не attached к panel)
Как это работает: отложенная проверка через schedule.Execute(...).StartingIn(150) — ждёт 2-3 кадра, пока посчитается layout, затем проверяет worldBound.width и worldBound.height.
Пример предупреждения:
[BoundsAudit] 'SusUnitCard' has zero bounds after mount (0×0).
display=Flex, visible=True, pickingMode=Position.
Clicks will NOT reach this element.3. CallbackAudit — сработал ли OnClick
Файлы: sus-core/Runtime/SusComponent.cs, 16 .sharq
Проблема: ClickEvent зарегистрирован, элемент доступен, клик проходит — но обработчик НЕ вызывается. Причины:
Disabled.Value = true→ обработчик выходит раньше времениLoading.Value = true→ обработчик выходит раньше времени- Guard-условие:
if (someCondition) return; - Обработчик выполнился, но занял > 50 мс (подозрительно долго)
Два подрежима:
3a. Guard-lock (AuditClickBlocked)
// In SusButton.sharq:
if (Disabled.Value)
{
AuditClickBlocked("Disabled"); // ← warning to the console
return;
}Пример: [CallbackAudit] 'SusButton' click blocked: Disabled
3b. Тайминг (AuditClickStart / AuditClickEnd)
var t0 = AuditClickStart();
OnClick?.Invoke();
AuditClickEnd(t0); // if > 50ms → warningПример: [CallbackAudit] 'SusButton' OnClick took 127.3ms
Встроено в (16 .sharq): SusAlert, SusButton, SusDropdown, SusEmptyState, SusForm, SusLink, SusListGroup, SusMenuButton, SusNumberInput, SusPagination, SusRating, SusSnackbar, SusStepper, SusTabs, SusToggle, SusUnitCard.
4. OverlayAudit — перекрытие UI
Файл: sus-core/Runtime/OverlayHost.cs
Проблема: при добавлении элемента в OverlayHost (tooltip, dropdown, модалка) новый элемент может перехватывать клики своими pickable-детьми.
Как это работает: в AddToOverlay() после вставки элемента Query<VisualElement>() ищет внутри всё с pickingMode == Position, и если такие находятся (а категория не Modal) — выводится предупреждение.
Пример предупреждения:
[OverlayAudit] 'SusTooltip' added to overlay in Tooltip category
with 3 pickable children. It may block clicks to underlying UI.
Consider setting pickingMode=Ignore on children.5. StateAudit — консистентность props
Файлы: SusButton.sharq, SusToggle.sharq, SusDropdown.sharq, SusListGroup.sharq, SusModal.sharq
Проблема: несогласованные состояния — Disabled=true и Loading=true одновременно (спиннер никогда не появится), IsOpen=true при Disabled=true (dropdown открыт, но неактивен), Selected не входит в Items (нет выделенного элемента), Model=false, но модалка видна.
Как это работает: WatchEffect внутри .sharq-компонентов следит за комбинациями props.
| Компонент | Правило |
|---|---|
SusButton | Disabled && Loading → оба true одновременно |
SusToggle | Disabled && Loading → оба true одновременно |
SusDropdown | IsOpen && Disabled → открыт, но disabled |
SusListGroup | Selected не входит в Items → нет выделенного элемента |
SusModal | Model=false, но display ≠ None → скрытая модалка видна |
Примеры предупреждений:
[StateAudit] SusButton: both Disabled and Loading are true.
Loading spinner will never be visible.
[StateAudit] SusDropdown: IsOpen=true while Disabled=true.
Dropdown should close when disabled.
[StateAudit] SusListGroup: Selected value is not in Items.
List will have no highlighted item.
[StateAudit] SusModal: Model=false but element is still visible
(display=Flex). Modal should be hidden.6. LifecycleAudit — утечки подписок
Файл: sus-core/Runtime/SusComponent.cs (OnDetachFromPanelHandler)
Проблема: элемент удалён из panel, но подписки на Prop<T>.Changed остались — утечка памяти и потенциальные ошибки при remount.
Как это работает: при detach запоминается _bindings.Count, через 1 секунду проверяется, что panel всё ещё null, а число подписок не уменьшилось → утечка.
Пример предупреждения:
[LifecycleAudit] 'SusUnitCard' detached but 3 bindings were not disposed.
Possible leak.7. NavigationAudit — неразрешённые маршруты
Файл: sus-router/Runtime/SusRouter.cs
Проблема: вызван SusRouter.Push("/settings"), но путь не разрешился (Resolve вернул null) — пользователь нажимает кнопку, ничего не происходит.
Как это работает: в каждом методе навигации (Push, Replace, PushNamed, ReplaceNamed) — если record == null → предупреждение.
Пример предупреждения:
[NavigationAudit] Push('/battle/unknown') — route not found.8. PerformanceAudit — слишком много элементов
Файл: sus-core/Runtime/SusComponent.cs (конструктор)
Проблема: глубокое дерево или много детей (>500 VisualElements) → медленный PickAll, долгий layout, тормозящий UI.
Как это работает: через 500 мс после mount Query<VisualElement>() считает все элементы в поддереве. Если > 500 → предупреждение.
Пример предупреждения:
[PerfAudit] 'SusTable' has 1423 VisualElements.
Consider virtualization (SusTable) or paging.9. DebounceAudit — двойные клики
Файл: sus-core/Runtime/SusComponent.cs (конструктор)
Проблема: пользователь быстро нажимает кнопку дважды (< 300 мс между кликами) — возможен double-submit (два API-вызова, двойное списание валюты и т.д.).
Как это работает: в конструкторе SusComponent регистрируется callback ClickEvent (срабатывает ДО обработчиков в Created() — UITK гарантирует порядок регистрации). Для каждого интерактивного элемента (с SetClickAuditDescription) запоминается время последнего клика. Если прошло <300 мс — предупреждение.
Пример предупреждения:
[DebounceAudit] 'SusButton' rapid double-click (87ms).
Possible unintended double-submit.Важно: это аудит, а не блокировка. Он не предотвращает второй клик — только предупреждает разработчика о необходимости debounce в бизнес-логике.
10. ClickTargetSizeAudit — маленькая цель
Файл: sus-core/Runtime/SusComponent.cs (конструктор, вместе с BoundsAudit)
Проблема: интерактивный элемент (кнопка, иконка, чекбокс) меньше 30×30px — трудно попасть пальцем/мышью. HIG рекомендует минимум 44×44px.
Как это работает: вместе с BoundsAudit, для элементов с SetClickAuditDescription — если worldBound > 0, но < 30px по любой оси → предупреждение.
Пример предупреждения:
[ClickTargetAudit] 'SusIcon' tap target is small (16×16px).
HIG recommends ≥44×44. Consider padding or min-size.11. StackDepthAudit — глубина навигационного стека
Файл: sus-router/Runtime/SusRouter.cs
Проблема: история навигации растёт неограниченно (> 50 записей) — возможна циклическая навигация (A → B → A → B...) или забытый Replace() вместо Push().
Как это работает: после каждого _history.Add() в PushRecord проверяется _history.Count > 50.
Пример предупреждения:
[StackDepthAudit] Router history has 67 entries.
Possible circular navigation or unbounded stack growth.
Consider using Replace() instead of Push().12. GuardAudit — отклонённая навигация
Файл: sus-router/Runtime/SusRouter.cs
Проблема: пользователь нажимает кнопку перехода, навигация молча отклоняется guard'ом или lifecycle-хуком — экран не меняется, и сообщений об ошибке нет.
Как это работает: в Navigate() — единая точка перехвата после NavigateCore. Если результат NavigationResult.Aborted — предупреждение с путями from→to.
Пример предупреждения:
[GuardAudit] Nav from '/lobby' → '/battle' was rejected by a guard
or lifecycle hook (BeforeLeave/CanLeave/BeforeEach/CanEnter/BeforeResolve/BeforeEnter).Покрывает все точки прерывания: BeforeRouteUpdate, BeforeLeave, CanLeave (ISusRouteGuard), BeforeEach (глобальный), CanEnter (ISusRouteGuard), BeforeResolve (глобальный), BeforeEnter.
13. ModalStackAudit — слишком много модалок
Файл: sus-core/Runtime/OverlayHost.cs
Проблема: на экране одновременно > 5 модалок — возможен баг (незакрытые модалки) или плохой UX (пользователь не понимает, какая модалка активна).
Как это работает: в AddToOverlay() после добавления считается _stack.Count(e => e.Category == OverlayCategory.Modal). Если > 5 — предупреждение.
Пример предупреждения:
[ModalStackAudit] 7 modals on screen.
Deep modal stacking may indicate a flow bug or unclosed modals.Архитектура
sus-core/Runtime/
├── SusComponent.cs ← BoundsAudit, ClickTargetAudit, PerfAudit,
│ DebounceAudit, LifecycleAudit, RemountLoopAudit,
│ OverflowAudit, IdleGuardAudit, LayoutReentryAudit,
│ AuditClickBlocked/Start/End
├── Diagnostics/
│ └── ClickAuditService.cs ← ClickAudit (global click interception)
├── OverlayHost.cs ← OverlayAudit, ModalStackAudit
│
sus-router/Runtime/
└── SusRouter.cs ← NavigationAudit, GuardAudit, StackDepthAudit,
DeadRouteAudit
Downstream UI packages may register additional component-level audits
(CallbackAudit, StateAudit, EmptyStateAudit, FocusTrapAudit, …).Включение / выключение
Все модули активны, когда:
#if UNITY_EDITOR || DEVELOPMENT_BUILDВ release-сборке (!UNITY_EDITOR && !DEVELOPMENT_BUILD) код полностью вырезается компилятором — ни единого байта, ни единого вызова.
14. EmptyStateAudit — пустой список / dropdown
Файлы: SusListGroup.sharq, SusDropdown.sharq
Проблема: Items.Count == 0, но элемент открыт или виден — пользователь видит пустой контейнер.
Как это работает: WatchEffect внутри .sharq следит за этой комбинацией.
Примеры предупреждений:
[EmptyStateAudit] SusListGroup: Items is empty but element is visible.
[EmptyStateAudit] SusDropdown: IsOpen=true but Items is empty.15. RemountLoopAudit — циклические remount'ы
Файл: sus-core/Runtime/SusComponent.cs (OnAttachToPanelHandler)
Проблема: > 5 Attach за 1 секунду → баг реактивности (WatchEffect меняет visibility/layout).
Пример предупреждения:
[RemountLoopAudit] 'SusUnitCard' attached 7 times in 1s.16. OverflowAudit — дети выходят за границы
Файл: sus-core/Runtime/SusComponent.cs (конструктор)
Проблема: дети выходят за границы родителя. Unity UITK не поддерживает overflow: hidden.
Пример предупреждения:
[OverflowAudit] 'SusCard' has 3 child(ren) exceeding parent bounds (320×180).17. DeadRouteAudit — неиспользуемые маршруты
Файл: sus-router/Runtime/SusRouter.cs
Проблема: зарегистрированные, но никогда не используемые маршруты — мёртвый код.
Вызов: router.AuditUnusedRoutes() (вручную, из dev-панели или тестов).
Пример предупреждения:
[DeadRouteAudit] 3 registered routes were never navigated to:
- /settings/audio (settings-audio)18. SusTable StateAudit — несогласованная пагинация
Файл: SusTable.sharq
Проблема: ItemsPerPage > Items.Count — одна страница, лишняя пагинация. Page > TotalPages — таблица показывает пустые строки.
Как это работает: WatchEffect следит за Controller.Value.Items, ItemsPerPage, Page, TotalPages.
Примеры предупреждений:
[StateAudit] SusTable: ItemsPerPage=25 but only 3 items. Single page.
[StateAudit] SusTable: Page=5 exceeds TotalPages=3. Table may show empty rows.19. LayoutReentryAudit — рекурсивный layout
Файл: sus-core/Runtime/SusComponent.cs (OnGeometryChangedForBreakpoint)
Проблема: WatchEffect меняет размер/позицию → GeometryChangedEvent → Updated() → WatchEffect → бесконечный цикл. UI зависает с высокой нагрузкой на CPU.
Как это работает: скользящее окно 500 мс, счётчик GeometryChangedEvent. Если > 20 за окно — предупреждение.
Пример предупреждения:
[LayoutReentryAudit] 'SusCard' 47 geometry changes in 500ms.20. IdleGuardAudit — по элементу ни разу не кликнули
Файл: sus-core/Runtime/SusComponent.cs (конструктор)
Проблема: интерактивный элемент виден 30+ секунд, но ни одного клика — возможно, pickingMode=Ignore, элемент перекрыт прозрачным overlay или забыт ClickEvent.
Как это работает: одноразовая проверка через 30 секунд после mount. Если _lastClickTime == 0 — предупреждение с диагностической информацией (pickingMode, worldBound).
Пример предупреждения:
[IdleGuardAudit] 'SusButton' visible but never clicked.
pickingMode=Ignore, worldBound=(320,180 80×32).21. FocusTrapAudit — фокус вне модалки
Файл: SusModal.sharq
Проблема: когда модалка открыта, Tab уводит фокус на элементы позади неё — нарушение accessibility.
Как это работает: FocusEvent + this.Contains(focused). Если Model=true, а evt.target не внутри модалки — предупреждение.
Пример предупреждения:
[FocusTrapAudit] SusModal: focus escaped outside modal to 'TextField'.Связанные документы
- OverlayHost и порталы — как работает OverlayHost, категории, z-order
- API Reference — полный API SusComponent
- Встроенные аудиты (Debug / QA) — полный каталог аудитов с примерами кода
22. ScreenAudit — текстовые дампы структуры экрана
Файл: sus-core/Runtime/Diagnostics/ScreenAudit.cs
Проблема: нужно понять «что видит пользователь на экране?» без запуска Unity. AI-агент не может посмотреть на экран — ему нужен текстовый отчёт.
Как это работает: три режима текстового дампа, вывод в Debug.Log (читается через read_console MCP).
22.1 LayoutDump — карта всех элементов с координатами
══════════ LayoutDump ══════════
Root: TemplateContainer panel=True
Screen: 1920×1080
│SusApp ⚙ 🖱 [3ch] flex (0,0 1920×1080)
│ classes: .sus-app .theme-dark
│ 📍clickable area: (0,0)→(1920,1080)
││SusMainMenu ⚙ 🖱 [1ch] flex (0,0 900×1080)
││ 📍clickable area: (0,0)→(900,1080)
│││SusButton ⚙ 🖱 [1ch] flex (300,200 200×40)
│││ 📍clickable area: (300,200)→(500,240)
││SusSettings ⚙ ⊘ HIDDEN [0ch] none (0,0 0×0)Расшифровка иконок:
⚙= SusComponent,🖱= кликабельно,⊘= прозрачно для кликовHIDDEN= не видим (display: none / visible=false / нулевой размер)📍clickable area= фактическая кликабельная область
22.2 PickableLayerAudit — z-order кликабельных элементов
══════ PickableLayerAudit ══════
Z-order = DOM order (last sibling = topmost). No z-index in Unity.
[LAYER 000] SusMainMenu ⚙ ⚠OVERLAPPED
area: (0,0 900×1080)
picking=Position visible=True enabled=True
← blocked by [001]SusOverlayPanel
[LAYER 001] SusOverlayPanel ⚙
area: (0,0 900×1080)
picking=Position visible=True enabled=True
⚠ 2 elements have overlapping bounds with higher z-order elements.Чтение: [LAYER 000] — чем больше число, тем выше (ближе к глазам). ⚠OVERLAPPED — кто-то выше по z-order перекрывает этот элемент: клик уйдёт наверх.
22.3 FullPropsDump — все значения Prop всех SusComponent'ов
══════ FullPropsDump ══════
── SusMainMenu #main-menu (900×1080)
IsOpen = true
Mode = "deployment"
── SusButton #play-btn (200×40)
Text = "PLAY"
Disabled = false
Loading = false
Variant = "primary"
Total: 2 SusComponents dumped.Горячая клавиша
Ctrl+Shift+~ — сразу три дампа в консоль. Устанавливается автоматически через SusBootstrap.
Автоматический дамп
При каждой навигации роутера (Push, Replace, PushNamed, ReplaceNamed, Back, Forward, Go) — после успешного перехода автоматически выводятся:
LayoutDump— карта элементов на новом экранеFullPropsDump— значения всех Prop всех SusComponent'ов
При каждом клике по SusButton — после выполнения OnClick?.Invoke() автоматически выводится FullPropsDump. Это даёт агенту снапшот состояния UI сразу после завершения действия кнопки.
Оба механизма — под
#if UNITY_EDITOR || DEVELOPMENT_BUILD, полностью вырезаются в release.
Использование агентом
# Via MCP: read Unity console
read_console -> filter_text="[LA]" # LayoutDump (element map)
read_console -> filter_text="[PA]" # PickableLayer (z-order)
read_console -> filter_text="[FP]" # FullPropsDump (props values)Агент получает текстовую картину экрана и может:
- Понять структуру: какие компоненты, в каком порядке
- Найти перекрытия: какие элементы блокируют клик
- Увидеть состояние: текущие значения props всех компонентов