# Exchanger Exchanger — полноэкранное тач-приложение для напольного разменника, продающего и выдающего жетоны за наличную или безналичную оплату. Проект разрабатывается на Godot 4.7 Mono/C# для Windows x64 с расчётом на будущий перенос аппаратного транспорта на Android ARM64. Целевой аппарат использует портретный тач-экран, показывает цену и бинарное состояние запаса жетонов («много»/«мало»), принимает наличные через купюро- и монетоприёмник либо запускает оплату на отдельном банковском терминале, после чего безопасно инициирует выдачу. События оплаты, баланса, запаса и выдачи должны поступать от внешнего контроллера автомата. ## Текущее состояние Завершены Этапы 0–3, программная часть Этапа 4 и весь программный пакет Этапа 6; приложение проходит полный пользовательский сценарий на типизированном mock-контроллере и документированный wire-профиль 2.3.5 на детерминированном симуляторе. После обновления авторизации физическая плата на COM3 прошла отдельный строгий auth-probe 1/1 и безопасную автоматическую приёмку 8/9: startup-баннер, AES-handshake, точный `READY!`, цифровые входы, четыре status-запроса и три цикла переподключения работают; отдельный запрос состояния Вендиста `160` дважды остался без ответа. Интерактивные платежи, выдача и отключение USB остаются отдельными явно подтверждаемыми проверками. Указанные результаты COM3 относятся к предыдущему состоянию прошивки. После текущего обновления протокола плата отключена, поэтому новая разводка и команды пока проверены только симулятором; аппаратная приёмка отложена до её подключения. - главная сцена `src/scenes/App/App.tscn`; - портретный `HomeScreen` с ценой, запасом, двумя способами оплаты, промо-fallback и поддержкой; - рабочие ветки наличной и карточной оплаты, включая другую сумму, ожидание терминала, успех и отказ; - `ScreenManager` Autoload с централизованным реестром, fade-переходами и блокировкой повторного ввода; - единственная встроенная программная fallback-тема; - безопасная замена fallback-темы одним внешним DLC-пакетом `.pck` при запуске; - статический фон DLC-темы с отдельным слоем анимированных спрайтов; - облегчённый режим, отключающий необязательные фоновые анимации; - типизированная внешняя конфигурация; - файловый журнал с ротацией; - фильтрация вторичных касаний; - аппаратный интерфейс `IHardwarePort`; - рабочие `MockMachineController`, `PeripheryControllerSimulator`, Windows COM-транспорт и безопасная Android-заглушка; - конфигурируемые тарифы, бонусы, карточные пресеты, способы оплаты, пользовательские таймауты, брендинг, промо и оформление; - актуальная привязка цифровых входов платы: `IN8` — сервисная кнопка, `IN1` — сигнал «мало жетонов» первого хоппера, `IN24` — его датчик выдачи, `IN4` — сигнал «мало жетонов» второго хоппера; вход датчика выдачи второго хоппера пока не определён; - компонент состояния запаса жетонов `Enough/Low/Unknown` без фиктивного точного количества; - отдельный подтверждаемый владельцем учётный остаток жетонов: ручное пополнение, списание подтверждённой выдачи и автоматический пересчёт активного хоппера; - безопасное состояние соединения `Disconnected/Authenticating/Ready/Faulted` и ненавязчивый сервисный индикатор на домашнем экране; - `SessionController` с явными состояниями, таймаутами и защитой от повторной выдачи; - чистый `SessionFlow`, уникальный локальный контекст операции и управляемый монотонный источник времени для тестов; - типизированные безопасные ошибки и отдельный JSONL-журнал начала/итога операций; - `AdvertisementPlayer` с локальным плейлистом, постером/текстовым fallback и остановкой вне HomeScreen. - production kiosk-профиль с borderless fullscreen, скрытым курсором и восстановлением окна после потери фокуса либо изменения дисплея; - сервисный вход по аппаратному `SERVICE`: защита включается в самом приложении, первый защищённый вход предлагает дважды задать единый сервисный PIN, последующие входы требуют его проверки; допустимые изменения сохраняются автоматически, повторное нажатие сервисной кнопки возвращает на главный экран; - шестираздельный редактор настроек владельца автомата: общие данные, звук, промо, тарифы, оплата и сервисный учёт жетонов; технические параметры, диагностика и тестовая выдача в пользовательском UI отсутствуют; - Windows watchdog, задача автозапуска и process soak-runner. По документации контроллера 2.3.5 реализованы Windows COM-транспорт, framing, строгий startup-handshake и платёжные команды. Порядок авторизации сверен с `plate_config_app`: RTS-reset, баннер `periphery controller ver_`, пауза 2000 мс, `07 → CHAL → 08 → RAES` и допуск к работе только после точной строки `READY!`. Оба хоппера включены в целевой `DualHopperAuto`: первый использует `309N/3090` и `301/300`, второй — `409N/4090` и `401/400`; фактическая выдача подтверждается `HOPPER1/2 OK`, пересчёт — `HOPPER1/2 PULSE=1`. Учётные остатки хранятся отдельно, а выдача списывается с фактически сработавшего канала. Универсальная конфигурация репозитория сохраняет безопасный `Unsupported`, пока профиль конкретного автомата не выбран явно. Подробности: [docs/hardware/PERIPHERY_CONTROLLER_2_3_5.md](docs/hardware/PERIPHERY_CONTROLLER_2_3_5.md). Актуальный прогресс перечислен в [ROADMAP.md](ROADMAP.md). После завершения Этапа 1 были изучены визуальные референсы реального корпуса и интерфейса. Они уточнили целевую портретную ориентацию и две разные платёжные ветки. Ландшафтный `StartScreen` удалён и заменён портретным домашним экраном. Подробные выводы находятся в [docs/REFERENCE_ANALYSIS.md](docs/REFERENCE_ANALYSIS.md). Этап 0 завершён. Базовые договорённости, включая универсальность до появления целевого железа и отложенную интеграцию контроллера, зафиксированы в [docs/architecture/decisions.md](docs/architecture/decisions.md). Команды `309N` и `409N` ограничены 99 жетонами: `309100` и `409100` возвращают ошибки групп 30 и 40. Приложение автоматически разбивает крупную выдачу на последовательные пакеты максимум по 99 жетонов и маршрутизирует их между двумя учётными остатками. ## Целевой интерфейс по референсам - Портретный формат 9:16; нативная сетка интерфейса и preview — 720×1280. - Пиксельная космическая тема, согласованная с оформлением корпуса: сам фон статичен, а НЛО, персонажи и другие движущиеся элементы реализуются отдельными анимированными спрайтами. - Домашний экран без дополнительного шага «Начать»: цена жетона, запас, способы оплаты, кнопка выдачи и промо-ролик. - Наличная ветка: текущий баланс, расчёт жетонов/бонуса, инструкции для купюр и монет. - Карточная ветка: предустановленные суммы, ввод другой суммы, ожидание прикладывания карты и результат. - Постоянно видимые брендовые элементы и телефон техподдержки. - Маскот и фоновые анимации, не перекрывающие функциональные панели. Промо-ролик, показанный в референсах, является отдельным ограниченным блоком домашнего экрана и не используется как фон приложения. Конкретные числа и графические материалы из референсов пока считаются демонстрационными. Перед production необходимо подтвердить тарифы, бонусы, бренд и права на ассеты. ## Технологии | Компонент | Значение | | --- | --- | | Движок | Godot 4.7.1 Mono | | Язык | C# | | .NET | .NET 8; Android-конфигурация — .NET 9 | | Рендерер | Mobile (Vulkan) | | Основная платформа | Windows x64 | | Перспективная платформа | Android ARM64 | | Целевой профиль железа | Пока не определён; приложение проектируется аппаратно-независимым и конфигурируемым | | Логический viewport | 720×1280, портретный 9:16, `canvas_items/expand` | | Debug-window | 720×1280 | | Аппаратный транспорт MVP | COM/Serial на Windows | ## Требования для разработки - Godot 4.7.1 Mono или совместимая версия Godot 4.7 Mono. - .NET SDK 8. - Windows x64 для текущей целевой сборки. Путь к редактору, используемый локальной конфигурацией VS Code, хранится в `.vscode/settings.json`. На другой машине его необходимо изменить на фактический путь к Godot Mono. ## Запуск ### Через Godot Editor 1. Открыть `project.godot` в Godot 4.7.1 Mono. 2. Дождаться импорта ресурсов и сборки C#-проекта. 3. Запустить главную сцену клавишей `F6` либо весь проект клавишей `F5`. В debug- и production-сборках интерфейс использует нативную сетку 720×1280. В полноэкранном режиме Godot масштабирует её на портретную панель через `canvas_items/expand`. ### Проверка C#-сборки ```powershell dotnet build Exchanger.sln ``` ### Автоматические тесты ```powershell dotnet test Exchanger.sln ``` Отдельный проект `tests/Exchanger.Tests` использует NUnit 4. Обычный набор тестов выполняется без запуска окна Godot и проверяет конфигурацию, миграции, атомарное сохранение и восстановление настроек, сервисный доступ, учётный остаток и автоматический пересчёт жетонов, безопасный audit, ротацию журналов, настройки оборудования, штатную отмену реального COM-чтения, аудиопараметры, брендинг, промо, DLC-тему, полносценовый binding-контракт, owner-only состав настроек, production export, тарифы, бонусы, карточные суммы, способы оплаты и таймауты, конечный автомат, framing COM-протокола, команды и полные cash/card-сценарии контроллера, обязательные `ASK:*`, взаимоисключение приёмников, строгий startup-handshake `banner → 2000 ms → 07/CHAL/08/RAES → READY!`, AES и восстановление ключа, wire-симулятор, оба формата `INPUT`, актуальную разводку `IN8/IN1/IN4/IN24`, keep-alive Вендиста, at-most-once, раздельный учёт и выдачу обоими хопперами через `309N/409N → APPEND/OK` либо `ERROR/CREDIT`, строковые импульсы раздельного сервисного пересчёта, malformed-кадры, ошибки соединения, JSONL-восстановление и отсутствие накопления подписок. Ускоренный soak моделирует 1440 минут и 480 выдач, а отдельный стресс выполняет 10 000 жизненных циклов `SessionFlow`. Загрузка сцен и ресурсов дополнительно проверяется в Godot Editor через разрешённый проектом MCP-сервер `godot-ai`. Для физической платы подготовлен отдельный `Exchanger.HardwareTests` с 18 безопасными и подтверждаемыми приёмочными проверками, включая изолированный handshake probe. Без явного флага они пропускаются и никогда не открывают COM-порт. Порядок проверки handshake, status-команд, SERVICE, приёмников оплаты, Вендиста, датчика, хоппера и USB-reconnect описан в [docs/hardware/BOARD_ACCEPTANCE_TESTS.md](docs/hardware/BOARD_ACCEPTANCE_TESTS.md). ### Headless smoke-тест ```powershell Godot_v4.7.1-stable_mono_win64.exe --headless --path . --quit-after 10 ``` ### Windows x64 export ```powershell .\tools\export-windows.ps1 -Configuration Release .\tools\export-windows.ps1 -Configuration Debug ``` Пресеты `Windows x64 Release` и `Windows x64 Debug` разведены также в редакторе. Скрипт использует очищенную staging-копию и жёстко выбирает `--export-release` либо `--export-debug`; без параметра собирается Release. Готовый self-contained каталог создаётся в `artifacts/windows/`. Подробности и smoke-проверка: [docs/operations/WINDOWS_EXPORT.md](docs/operations/WINDOWS_EXPORT.md). ## Конфигурация Настройки, доступные владельцу автомата, по умолчанию находятся в: ```text res://config/default_settings.json ``` При первом запуске приложение создаёт пользовательскую копию: ```text user://config/appsettings.json ``` В пользовательской конфигурации задаются: - fullscreen/debug-window режим; - целевой FPS; - разрешение и путь к внешнему DLC-пакету темы; - облегчённый режим эффектов; - таймауты сессии; - общая громкость, master mute и отдельная громкость промо-ролика; - доступность наличной и карточной оплаты, карточные пресеты, максимальная сумма и шаг ручного ввода; - безопасные таймауты бездействия, ожидания оплаты и показа результата; - включение промо, звук промо-роликов, fallback-текст, локальный постер и плейлист OGV/MP4/MOV/M4V; - ограничения файлового журнала. Монтажные параметры оборудования вынесены в отдельный TOML-файл: ```text res://config/technical_settings.toml user://config/technical_settings.toml ``` Пользовательская копия создаётся при первом запуске и не показывается в интерфейсе приложения. Она задаёт основной последовательный порт (`COM*` в Windows, `/dev/ttyUSB*`, `/dev/ttyACM*` или `/dev/serial/by-id/*` в Linux), mock-режим, параметры связи, наличие обоих хопперов, индексы и полярность их датчиков, сервисную кнопку и интервалы опроса. TOML применяется поверх устаревшего раздела `Hardware` в JSON; аргумент `--mock-hardware` остаётся последним безопасным переопределением. Изменения вступают в силу только после полного перезапуска. Схема, диапазоны и порядок проверки: [docs/operations/TECHNICAL_CONFIGURATION.md](docs/operations/TECHNICAL_CONFIGURATION.md). Параметр `[controller].reconnect_interval_seconds` задаёт паузу между повторными открытиями порта. Штатно это 30 секунд: приложение повторяет подключение, если платы не было при запуске, и начинает новый полный отсчёт после отключения работавшей платы. Платёжные и выдающие команды при reconnect никогда не повторяются автоматически. Модель настроек содержит цену жетона, бонусные правила, карточные суммы, телефон поддержки и плейлист промо-роликов. Датчики обоих хопперов сообщают только «достаточно» или «мало». Точные подтверждаемые остатки хранятся раздельно в `user://data/token_inventory.json`, начинаются с нуля, уменьшаются после подтверждённой выдачи и меняются отдельными действиями для каждого хоппера в разделе «Сервис». Два независимых переключателя управляют поведением: «Учитывать остаток жетонов» включает ведение и показ точного остатка, а «Не продавать больше остатка» отдельно запрещает покупку сверх сохранённого количества с учётом бонуса. Если ограничение выключено, остаток продолжает отображаться и списываться, но не блокирует оплату. Если выключен сам учёт, скрываются пересчёт, пополнение и приблизительное количество Home, а файл остатка не изменяется. Перед повторным включением учёта после реальных выдач требуется пересчёт. Поддерживаются аргументы запуска: ```text --windowed --fullscreen --theme-dlc=D:\ExchangerThemes\active_theme.pck --no-theme-dlc --mock-hardware=true --reduced-effects ``` Конфигурация версии 19 сохраняется через `SettingsService` атомарной заменой файла. Перед изменением создаётся `appsettings.json.bak`; повреждённый пользовательский JSON восстанавливается из последней корректной копии либо безопасных значений по умолчанию и не прерывает запуск. В debug-сборке с домашнего экрана `F10` переключает тот же сервисный вход; если защита PIN выключена, редактор открывается напрямую, а при включённой защите сначала используется общая PIN-клавиатура. В редакторе владельца доступны свой текст для речевого bubble и контакт техподдержки без отдельного предпросмотра раздела, общая громкость, master mute, отдельная громкость промо-ролика, короткий проверочный сигнал, стоимость жетона, бонусные правила, карточные пресеты, предел и шаг ручной суммы, доступность наличной/карточной оплаты, пользовательские таймауты, промо-poster/fallback/плейлист, защита PIN, предварительный расчёт и автоматическое сохранение. Из всех подходящих бонусных правил применяется один наибольший бонус; значения разных строк не суммируются. Нулевая цена, значения вне диапазона, повторяющиеся суммы/пороги и отключение обоих способов оплаты блокируют сохранение и показываются рядом с соответствующим полем. Пустой набор карточных пресетов допустим: пользователь сохраняет доступ к пункту «Другая сумма». Обычная release-сборка игнорирует действие `F10`. В production экран PIN открывается только аппаратным событием `SERVICE`, только из `Home` и только при отсутствии операции. Единый сервисный PIN включается в разделе «Общие» и дважды задаётся на экране приложения при следующем входе. Открытый PIN нигде не сохраняется: в JSON находятся только случайная соль, PBKDF2-SHA256-хеш и число итераций; PIN и производные значения не записываются в журналы. Тема/DLC, COM-порт, mock-режим, параметры окна/FPS, журналирование, диагностика и тестовая выдача не показываются владельцу. Аппаратные параметры изменяются только через отдельный TOML при техническом развёртывании. Полная матрица: [docs/operations/SETTINGS_ACCESS_MATRIX.md](docs/operations/SETTINGS_ACCESS_MATRIX.md). При тапе на текстовое поле открывается встроенная в тему экранная клавиатура с русской и английской раскладками, Shift и отдельной страницей спецсимволов; для числового поля открывается цифровая клавиатура. Обе панели можно скрыть кнопкой справа сверху, а поле автоматически прокручивается выше клавиатуры. Системная виртуальная клавиатура для этих полей отключена. Подробности: [docs/operations/ON_SCREEN_KEYBOARD.md](docs/operations/ON_SCREEN_KEYBOARD.md). Master и промо разведены по Godot audio buses `Master` и `Advertisement`, поэтому громкость промо-ролика не влияет на остальные звуки. Внешняя тема остаётся частью deployment-конфигурации: отсутствующий или не прошедший проверку пакет оставит встроенную fallback-тему. Путь к `.pck`, включение DLC и `ReducedEffects` намеренно не показываются в настройках владельца. Промо-ресурсы принимаются только из `res://`, `user://` либо по абсолютному локальному пути. Постеры поддерживают PNG/JPG/JPEG/WEBP, видео — OGV (Ogg Theora), MP4, MOV и M4V. MP4/MOV/M4V воспроизводятся GDExtension `addons/native_video` через тот же `VideoStreamPlayer`; для них нужен Mobile или Forward+ (Vulkan), Compatibility не поддерживается. При выборе внешнего MP4/MOV/M4V ролик остаётся на месте и передаётся Native Video напрямую по исходному пути, включая Unicode и пробелы; копии, служебные ссылки и ASCII-псевдонимы не создаются. Существование файла проверяется до сохранения. Отключённое промо может временно хранить недоступный плейлист, но не читает файлы, не запускает декодирование и показывает на Home безопасный текст «ПРОМО ОТКЛЮЧЕНО». Смена плейлиста использует штатный lifecycle Godot: активный player получает `Stop()`, его `Stream` очищается, а новый источник начинается с позиции 0. То же завершение выполняется при скрытии HomeScreen и освобождении узла; старый decoder и его аудио не сохраняются в runtime-кэше. Регрессионная сцена `tests/runtime/NativeVideoLifecycleSmoke.tscn` проверяет 50 смен двух внешних MP4, скрытие/возврат и многократное удаление активного `AdvertisementPlayer`. ## Журналы Операции приложения записываются в: ```text user://logs/exchanger.log user://logs/operations.jsonl user://logs/settings-audit.jsonl ``` `exchanger.log` содержит диагностические сообщения с уровнем и категорией. `operations.jsonl` хранит структурированные события начала и единственного итога по локальному `OperationId`. `settings-audit.jsonl` фиксирует только время, роль, названия изменённых разделов и необходимость перезапуска. При перезапуске незавершённые операции безопасно закрываются как `UnknownAfterRestart`, без автоматического повтора оплаты или выдачи. Журналы автоматически очищаются по сроку хранения и общему лимиту; платёжные секреты, значения настроек, полные пути и сырые ответы оборудования не записываются. Подробные правила и mock-чек-лист: [docs/operations/RECOVERY_AND_MOCK_QA.md](docs/operations/RECOVERY_AND_MOCK_QA.md). ## Структура проекта ```text res:// ├── docs/ # Архитектурные решения и анализ референсов ├── config/ # Встроенная конфигурация ├── tests/Exchanger.Tests/ # NUnit-тесты чистого ядра и smoke-проверки файлов ├── src/ │ ├── core/ │ │ ├── Configuration/ # AppSettings и загрузка JSON │ │ ├── TechnicalConfiguration/ # Монтажный TOML и его строгая валидация │ │ ├── Logging/ # Файловый журнал │ │ ├── Navigation/ # Реестр и менеджер экранов │ │ ├── Session/ # Конечный автомат операции │ │ └── ThemeManager/ # Runtime-контракт, fallback и загрузчик DLC │ ├── hardware/ │ │ ├── Abstractions/ # IHardwarePort и IMachineController │ │ ├── MockHardware/ # Имитация сырого транспорта │ │ ├── MockMachine/ # Типизированный симулятор автомата │ │ ├── SerialPortHardware/ # Desktop-порт Windows/Linux │ │ └── UsbSerialHardware/ # Android-заглушка │ ├── scenes/ │ │ ├── App/ # Композиционный корень │ │ ├── ServiceAccessScreen/ # Защищённый PIN-вход │ │ ├── SettingsScreen/ # Настройки владельца и сервисный учёт │ │ ├── HomeScreen/ # Домашний экран │ │ ├── CashPaymentScreen/ # Оплата наличными │ │ ├── CardAmountScreen/ # Выбор карточной суммы │ │ ├── CardCustomAmountScreen/ # Цифровая клавиатура │ │ ├── CardTerminalScreen/ # Ожидание банковского терминала │ │ ├── SuccessScreen/ # Выдача и успех │ │ └── ErrorScreen/ # Отказ и ошибки │ └── ui/ │ ├── OnScreenKeyboard/ # Логика текстовой и цифровой клавиатур │ ├── StaticBackground/ # Статический фон и слой анимированных спрайтов │ ├── AdvertisementPlayer/ # Локальное видео и fallback │ ├── TokenStockStatus/ # Состояние запаса жетонов │ └── TouchInputGuard/ # Защита от мультитача └── addons/ # Сторонние плагины ``` Сцены, их C#-скрипты и специфичные ресурсы хранятся атомарно в одной тематической папке. ## Темы и DLC В Exchanger нет каталога разработки тем и встроенных `.tres`-тем. Единственная минимальная fallback-тема создаётся кодом, поэтому приложение сохраняет рабочий интерфейс без внешних файлов. При старте `ThemeManager` ищет один `.pck` по пути из пользовательского `appsettings.json`; успешно проверенный DLC полностью заменяет fallback до создания экранов. Manifest v2 предоставляет полные визуальные сцены всех девяти экранов: app-owned C#-контроллеры связываются с ними через metadata `exchanger_binding_id`, не передавая теме бизнес-логику. Исходники тем, manifest, фоновые изображения и сцены находятся только в соседнем Godot-проекте `../ExchangerThemeDlcBuilder`: `themes/example/` служит готовым шаблоном, `themes/space-pixel/` — текущей оформленной темой, остальные темы лежат в `themes//`. Основное приложение содержит runtime-контракт имён сцен, binding-resolver и безопасный загрузчик. Для manifest v2 приложение сверяет закреплённый SHA-256 машиночитаемого каталога и проверяет все 481 обязательный биндинг девяти сцен по ID и Godot-типу. Горячего переключения и списка тем нет; после замены `.pck` приложение нужно перезапустить. Современная production-сцена темы самодостаточна: её корень содержит metadata `exchanger_self_contained_visuals = true`, собственный `Theme`, прямой `TextureRect` `ThemeBackground` и прямой слой `BackgroundDecoration`. Все девять сцен пакета обязаны использовать один и тот же режим. Для такого пакета глобальные `BackgroundTexturePath` и `BackgroundDecorationPath` оставляют пустыми; приложение отклоняет смешанную или дублирующую визуальную композицию. Runtime не переоформляет самодостаточную сцену: он не назначает ей другой `Theme`, type variations, размеры или theme overrides. Приложение находит готовые элементы по binding-id, подключает события и обновляет только функциональные значения — текст, видимость, доступность и состояние полей. Тематические PCK считаются доверенными. Внутри production-сцен разрешены GDScript, сигналы, `AnimatedSprite2D`, `AnimationPlayer`, шейдеры, частицы, аудио, видео и другие runtime-возможности Godot. Формат, binding-id, пользовательские пути и сборка пакета описаны в [docs/THEME_DLC.md](docs/THEME_DLC.md). ## Аппаратный слой UI не обращается к порту напрямую. `SessionController` работает через типизированный `IMachineController`, а будущий протокольный адаптер будет использовать низкоуровневый `IHardwarePort`; поэтому экраны не зависят от COM-порта, Android USB API или строк wire-протокола. Сейчас доступны: - `MockHardwarePort` — минимальная служебная заглушка транспорта для UI mock-режима; - `PeripheryControllerSimulator` — детерминированный wire-симулятор handshake, `INPUT`, платежей и обоих хопперов для интеграционных тестов без COM-порта; - `MockMachineController` — детерминированно имитирует наличный баланс, карточный результат, бинарный уровень запаса и подтверждение выдачи; - `SerialPortHardware` — реальный Windows/Linux-транспорт с отдельным worker, очередями и reconnect; - `PeripheryMachineController` — типизированный адаптер протокола универсального контроллера 2.3.5; - `UsbSerialHardware` — явно неподдерживаемая Android-заглушка. COM-порт использует `System.IO.Ports` 8.0.0 и параметры 115200 8N1/None. Авторизация и платежи реализованы по спецификации 2.3.5. Обфусцированное представление ключа встроено в `PeripheryControllerSecurity` и восстанавливается XOR `0x37`. Целевой режим — `DualHopperAuto`: `309N` обслуживает первый хоппер, `409N` — второй, пакеты ограничены 99 жетонами, а подтверждения `OK/ERROR/CREDIT` принимаются только от активного канала. Шаблон TOML включает оба хоппера, но остаётся безопасным благодаря `use_mock = true`; реальный профиль активируется только техническим специалистом. Команда выдачи автоматически не повторяется. В актуальной разводке `IN8` active-low переключает сервисный вход, `IN1` сообщает уровень первого хоппера, `IN4` — второго. Обычная выдача использует `309N` и `409N` и считает только соответствующие кадры `HOPPER1/2 OK`. Сервисный пересчёт запускается отдельно командами `301/300` или `401/400`, считает только `HOPPER1/2 PULSE=1` выбранного канала и завершается после 10 секунд тишины. Физические импульсные входы не участвуют в программном счёте. Общая полоска объединяет точный учёт и бинарные датчики в состояния «нет жетонов», «мало», «достаточно» и «много». `IMachineController` также публикует безопасное сервисное состояние соединения. На главном экране штатный статус скрыт в production и виден в debug; авторизация, обрыв или аппаратная ошибка отображаются всегда, но без имени COM-порта, сырых кадров и деталей handshake. При потере связи запас становится `Unknown`, поэтому запуск новой оплаты блокируется. ## Универсальность оборудования Пока целевой ПК и тач-панель отсутствуют, проект не оптимизируется под одну модель устройства. UI проверяется на нескольких портретных разрешениях, параметры оборудования и сценариев выносятся в конфигурацию, а платформенные транспорты остаются за `IHardwarePort`. Фактические DPI, GPU, производительность, системный киоск-режим и работа в перчатках будут проверены на реальном аппарате без изменения границ UI и бизнес-логики. ## Киоск-режим Release-сборка настраивает borderless fullscreen, скрывает курсор и повторно применяет оконный профиль после потери фокуса либо изменения состава/разрешения дисплеев. Godot не может надёжно заблокировать Alt+Tab, Ctrl+Alt+Del и системные жесты, поэтому полный киоск дополнительно настраивается средствами Windows. Скрипты автозапуска/watchdog и безопасная процедура установки находятся в `tools/windows`; инструкция — [docs/operations/WINDOWS_KIOSK_SETUP.md](docs/operations/WINDOWS_KIOSK_SETUP.md). Ускоренный и процессный soak описаны в [docs/operations/SOAK_TEST.md](docs/operations/SOAK_TEST.md). ## Сторонние компоненты В проекте подключены: - Godot AI; - GodotX Toast; - Log.gd; - Reload Current Scene. Эти editor/development addons исключаются из production export; приложение не зависит от них. Лицензии сторонних компонентов находятся рядом с ними в `addons/` либо в отдельных сопроводительных файлах, например `godot-ai-LICENSE.txt`. ## Лицензия проекта Собственная лицензия Exchanger в настоящий момент не объявлена. Ошибочный корневой `LICENSE`, автоматически скопированный из GodotX Toast, удалён. Это не затрагивает лицензии сторонних компонентов.