Загрузка данных
Отличный проект! Задумка с бесшовным консольным дашбордом на базе WebView2, перехватом мыши через ClipCursor и аутентичными каскадами PlayStation — отличная инженерная работа. Физика взаимодействия уже на хорошем уровне, но предыдущий ассистент из-за перегруженного контекста действительно допустил ряд неприятных ошибок и срезал углы в критических местах.
Давайте разложим всё по полочкам на понятном языке: что было сделано хорошо, где ИИ ошибся и почему это сейчас может портить впечатление от лаунчера на реальном стенде.
Анализ архитектуры: Что произошло в последних правках
В кодовой базе видна правильная концептуальная задумка:
Быстрое меню питания (слева) вынесли в отдельную PS4-панель (#quick-menu), отвязали от кнопки Guide/PS и повесили на кнопку View (PAD.SELECT) и F10.
Меню параметров (справа) оформили в виде парящей карточки с мини-обложкой и контекстным скрытием кнопки запуска.
На странице игры (Game Hub) вернули крупную акцентную кнопку «Начать» и выстроили левую колонку (Обложка → Кнопка → Статистика).
Но из-за того, что правки вносились фрагментарно в огромном диалоге, возникло 9 критических и логических багов.
Найденные критические ошибки и узкие места
1. Сломанная навигация аналоговым стиком вниз во ВСЕХ меню (Quick Menu, Options, выбор обложек)
В чём суть: Вы поставили задачу: «не пускать игрока на страницу игры по случайному отклонению стика вниз на карусели дашборда».
Как ошибся предыдущий чат: Он просто взял и глобально вырезал проверку стика вниз (y > 0.5) из всего опроса геймпада:
JavaScript
const isDpadDown = b(PAD.DOWN);
const downActive = page ? false : isDpadDown;
fire("down", downActive, () => handle("down"), true);
Последствия: Стик вниз перестал работать вообще везде! Игрок открывает левое меню питания, меню настроек или правое меню опций: стик вверх (y < -0.5) переключает пункты, а стик вниз — наглухо мёртв! Чтобы спуститься вниз по списку, приходится убирать палец со стика и жать на крестовину (D-pad). Это ломает привычный консольный комфорт.
Решение: Стик вниз должен игнорироваться только на главном экране карусели (!page && !quickMenu && !menu && !picker). Внутри любых меню оба направления стика обязаны работать симметрично.
2. Фатальный сбой после выхода из игры: игры перестают запускаться повторно
В чём суть: В обработчике закрытия игры window.onGameExit предыдущий ИИ написал:
JavaScript
const games = await api.get_games();
refresh(games, it ? it.id : null);
if (page && it) {
const updated = games.find(g => g.id === it.id);
if (updated) {
items[cur] = updated; // <-- Фатальная строка
drawPage(false);
}
}
Почему это критично: api.get_games() возвращает сырой список из games.json. Функция refresh() уже сама правильно обновляет библиотеку, добавляя в память служебные поля (включая type: "game").
Затем код находит сырой объект updated (где поля type нет) и затирает им рабочий элемент items[cur].
Последствия: При повторной попытке запустить игру срабатывает проверка if (it.type !== "game") return beep(...) — игра больше не стартует, лаунчер просто издает системный писк! А меню Options для этой игры перестает открываться.
3. Потеря навигации «Назад» (B / Esc) в подменю Quick Menu
В чём суть: Переменную quickMenuBack объявили, но логику возврата не подключили.
Последствия: Если игрок открывает «Управление системой», выбирает «Настройки», а затем решает вернуться назад по стандартной консольной кнопке B (или Esc), его не возвращает в меню системы, а полностью выкидывает из Quick Menu на дашборд. Вернуться на шаг назад можно только стрелками через пункт списка «Назад в меню», что непривычно для консольного интерфейса.
4. Мигание и дергание панели питания при переключении настроек
В чём суть: В функции выбора пункта Quick Menu (quickMenuPick) перед выполнением действия жестко вызывается closeQuickMenu().
Последствия: Когда игрок щелкает по пункту «Автозапуск» или «Формат времени», панель начинает анимацию закрытия (уезжает влево), затем Python применяет настройку, и панель тут же выезжает заново. Вместо мгновенного переключения надписи прямо на карточке панель дергается и мигает.
5. Тупик при отмене удаления игры в меню Options
В чём суть: При выборе «Удалить из библиотеки» открывается подтверждение: «Да, удалить» и «Отмена».
Однако функция openMenu при открытии диалога стирает ссылку на возврат (menuBack = null).
Последствия: Если игрок передумал и нажал «Отмена» (или кнопку B / Esc), меню Options полностью исчезает, вместо того чтобы вернуться к общему списку действий игры («Переименовать», «Выбрать обложку» и т.д.).
6. Пропущенный преддекодинг фонового арта при холодном старте (причина микрофриза)
В чём суть: Была создана функция preloadActiveGameArt(), декодирующая фоновые арты через img.decode().
Но предыдущий чат вызвал её только при пролистывании карусели (select) и обновлении (refresh). В функции первичного запуска loadGames() её забыли вызвать.
Последствия: При первом входе в лаунчер фоновый арт первой игры не подготовлен в видеопамяти. Если сразу нажать крестовину вниз для перехода в Game Hub, Chromium декодирует тяжелый постер прямо во время анимации входа, вызывая просадку кадров.
7. Синтетический писк вместо звуков PS4 в Настройках
В чём суть: В лаунчере настроен качественный Web Audio движок с оригинальными сэмплами PS4 (sfx("click"), sfx("decide"), sfx("dlg_yes")).
Но в меню настроек предыдущий ИИ почему-то вызвал архаичный генератор частот beep(700, .08), который звучит как системный динамик старого ПК и выбивается из общей звуковой картины.
8. Рудименты старой логики на странице игры
В чём суть: На странице игры переменная page теперь содержит единичный объект текущей игры (page = it).
Однако в файле остались неиспользуемые функции pageMove и pagePick, которые рассчитывают на то, что page — это массив кнопок. Если какая-то логика к ним обратится, произойдет необработанное исключение TypeError.
Кроме того, на странице игры сейчас отключена прокрутка длинного описания стиком или крестовиной.
9. Архитектурный риск в main.py: Слишком агрессивный таймаут на сворачивание (Alt-Tab)
В чём суть: В цикле отслеживания watch_game_unified:
Python
if user32.IsWindow(current_game_hwnd) and user32.IsWindowVisible(current_game_hwnd) and not user32.IsIconic(current_game_hwnd):
window_missing = 0
else:
window_missing += 1
if window_missing >= 3: # Всего 120 миллисекунд!
Если игра на доли секунды свернется при смене видеорежима, проигрывании вступительного ролика или если игрок случайно нажмет Alt-Tab, окно становится IsIconic == True. Буквально через 120 мс лаунчер считает, что игра закрылась, принудительно возвращает себя на экран и запускает музыку, пока игра всё ещё висит в фоне. Имеет смысл ориентироваться на уничтожение окна или реальное завершение процессов игры.
План дальнейших действий
Все эти моменты устраняются точечно и аккуратно, без ломки уже достигнутых результатов:
Нормализовать обработку осей в poll(): стик вниз блокируется только на корневой карусели, но полноценно работает во всех меню и списках.
Убрать паразитное перетирание items[cur] в onGameExit (дать refresh() делать свою работу).
Добавить полноценный стек возврата (quickMenuBack и menuBack), чтобы кнопка B / Esc всегда возвращала на предыдущий уровень меню.
Убрать дергание панели Quick Menu при щелчке по тумблерам (обновлять контент без скрытия карточки) и заменить beep на нативные звуки PS4.
Вызвать preloadActiveGameArt() при старте в loadGames().
Очистить рудименты на странице игры и сделать таймаут сворачивания в main.py более устойчивым к смене видеорежимов.
Дайте знать, в каком порядке вам удобнее двигаться: разобрать конкретный блок подробнее или подготовить файлы с исправлениями.