183 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектурные решения Exchanger
Дата первичной фиксации: **2026-08-20**.
Документ хранит решения, которые определяют границы подсистем и не должны заново пересматриваться без явной причины. Неизвестные параметры будущего оборудования не заполняются предположениями: они оформляются как входные данные соответствующего этапа интеграции.
## DEC-001. Универсальность до появления целевого оборудования
Статус: **принято**.
Целевой ПК, GPU, тач-контроллер и точная версия Windows пока отсутствуют. До получения реального аппарата приложение разрабатывается без привязки к конкретной модели оборудования:
- базовый рендерер — Godot Mobile (Vulkan): это требуется runtime-расширением Native Video для
MP4/MOV/M4V; UI сохраняет логический пиксельный viewport и nearest-фильтрацию;
- UI использует логический портретный viewport 720×1280 — тот же, что и theme-preview; на панели другого размера применяется `canvas_items/expand`;
- размеры, таймауты, тарифы, аппаратный транспорт и контент задаются конфигурацией;
- ввод поддерживает тач и мышь без функций, доступных только по hover;
- код не полагается на конкретный DPI, частоту обновления, GPU или имя COM-порта;
- тяжёлые необязательные элементы должны отключаться конфигурацией;
- фактическое железо позже добавляется как профиль проверки, а не как основание для переработки UI и бизнес-логики.
Следствие: отсутствие целевого оборудования не блокирует интерфейс, mock-сценарии и основную архитектуру. Финальная проверка производительности и эксплуатации остаётся обязательной после появления устройства.
## DEC-002. Граница аппаратной интеграции
Статус: **принято**.
Приложение взаимодействует с автоматом только через абстракцию `IHardwarePort` и типизированный слой протокола. UI и `SessionController` не открывают COM-порт, не знают wire-формат сообщений и не обращаются напрямую к купюроприёмнику, монетоприёмнику, банковскому терминалу или диспенсеру.
До появления документации контроллера используется `MockHardwarePort`. Реальный COM-транспорт подключается позднее отдельным адаптером без изменения экранов.
## DEC-003. Протокол контроллера не проектируется заранее
Статус: **принято**.
Список команд и документация контроллера будут добавлены в `references/` на более позднем этапе. До этого запрещено придумывать окончания строк, кодировку, heartbeat, названия команд, ответы, идентификаторы операций и семантику ошибок.
После появления документации:
1. На Этапе 3 создаётся версия внутреннего документа протокола и типизированный адаптер.
2. Команды и события проверяются по документации и примерам реального обмена.
3. На Этапе 4 подключается реальный Windows COM-транспорт.
4. Любая неподтверждённая возможность контроллера остаётся недоступной, а не имитируется в production.
Это входная зависимость Этапов 3–4, но не незавершённая задача Этапа 0.
## DEC-004. Контроллер — единственный источник аппаратных событий
Статус: **принято**.
Контроллер сообщает ПК все сведения об аппаратуре по протоколу из будущей документации. В их число по потребности сценария входят:
- связь и готовность контроллера;
- запас жетонов;
- принятая наличная сумма и состояние приёмников;
- результат карточной оплаты;
- начало, прогресс и результат выдачи;
- отмена, таймауты и аппаратные ошибки.
Конкретный набор сообщений и их wire-формат определяются только документацией контроллера. На стороне приложения они преобразуются в типизированные события, с которыми работает `SessionController`.
## DEC-005. Статический фон и отдельные анимированные спрайты
Статус: **заменено DEC-009**.
Фоновая композиция интерфейса статична. Тема предоставляет fallback-цвет и при необходимости неподвижную текстуру. НЛО, маскот и другие движущиеся элементы подключаются отдельной пассивной `PackedScene` с анимированными спрайтами. Фоновые шейдеры, частицы и видео не используются. Промо-ролик является отдельным UI-блоком `HomeScreen`.
## DEC-006. Проектные инструкции агентов версионируются
Статус: **принято**.
Файл `.agents/AGENTS.md` является частью проекта и должен храниться в Git. Это позволяет новым агентам восстановить правила работы, требования к документации, использованию Godot MCP и `agentmemory`. Каталог `.agents/` не исключается через `.gitignore`.
## DEC-007. Состояние репозитория
Статус: **принято**.
Локальный Git-репозиторий инициализирован 2026-08-20. Удалённый репозиторий, стратегия веток и первый коммит на этом этапе не назначаются без отдельного решения владельца проекта.
## DEC-008. Полные визуальные сцены принадлежат теме, бизнес-логика — приложению
Статус: **дополнено DEC-009**.
Новая DLC-тема формата v2 предоставляет полные визуальные сцены всех девяти экранов. Она может
свободно менять композицию и вложенность `Control`, но не содержит исполняемых скриптов и не
обращается к сессии, оплате, настройкам или оборудованию.
Приложение сохраняет app-owned C#-контроллер каждого экрана. До `_Ready` встроенный визуальный
корень контроллера заменяется сценой DLC, после чего кнопки, поля и динамические области находятся
по metadata `exchanger_binding_id`. Имена файлов, scene binding-id и root-имена зафиксированы
`ThemeSceneContract`; фактические NodePath внутри темы частью runtime-контракта не являются.
Следствия:
- встроенные сцены остаются единственным безопасным fallback без внешнего пакета;
- manifest v1 продолжает работать как стилевой DLC без замены сцен;
- manifest v2 активируется только полным набором экранов, а не смесью разных тем;
- новые сцены могут быть самодостаточными: root содержит собственный `Theme`, прямой статический
`ThemeBackground` и прямой `BackgroundDecoration`; все девять сцен обязаны использовать один
режим, а глобальные background/decoration пути manifest в нём остаются пустыми;
- host не меняет визуальные свойства самодостаточной сцены после загрузки: запрещены повторное
назначение Theme, type variations, геометрии и theme overrides; контроллеры только находят
готовые узлы по binding-id, подключают события и обновляют функциональные значения;
- визуальные узлы запаса, связи и промо принадлежат сцене темы; приложение находит их по
биндингам, передаёт только состояние и медиаресурсы и не добавляет дочерние Control-узлы;
- production-сцены темы не содержат GDScript/C#; допустимы только данные, стандартные Godot-узлы,
`AnimationPlayer` и `AnimatedSprite2D`;
- точный каталог содержит 481 обязательный биндинг и входит в PCK по каноническому пути;
приложение закрепляет его SHA-256, поэтому DLC не может объявить себе ослабленный контракт;
- небольшой набор supplemental runtime-биндингов проверяется приложением поверх каталога:
состояния полоски запаса, приблизительный остаток на Home и сервисные действия двух хопперов;
- `ReducedEffects` и эксплуатационный бюджет применяются как к прежней глобальной decoration,
так и к `BackgroundDecoration` внутри самодостаточной сцены;
- проект создания тем находится отдельно в `../ExchangerThemeDlcBuilder/themes/`, а единый
каталог — в `../ExchangerThemeDlcBuilder/contracts/screen_bindings.v2.json`.
## DEC-009. Внешние темы являются доверенным исполняемым кодом
Статус: **принято**.
Владелец приложения самостоятельно разрабатывает и устанавливает все PCK-темы; стороннее
распространение пакетов не предполагается. Поэтому Exchanger не ограничивает тему пассивной
графикой и считает всё содержимое `res://exchanger_theme_dlc/` доверенным.
Следствия:
- production-сцены, вложенные компоненты, legacy decoration и visual slots могут содержать
GDScript и любые стандартные runtime-узлы Godot;
- разрешены сигналы, method tracks, таймеры, аудио, видео, частицы, шейдеры, процедурная графика,
сетевые и файловые API;
- удалён эксплуатационный decoration budget и ограничение размера PCK;
- manifest v2, девять канонических сцен, 481 binding-id, их типы и закреплённый SHA-256 контракта
остаются обязательными для подключения app-owned C#-логики;
- пакет по-прежнему монтируется с `replace_files=false` и живёт в namespace
`res://exchanger_theme_dlc/`, чтобы его ресурсы имели стабильные пути;
- старое требование статического фона и запрет исполняемых скриптов больше не действуют.
Скрипт темы исполняется при инстанцировании PackedScene. Контрактная проверка также временно
инстанцирует сцену, поэтому код `_init()` может выполниться до фактического показа экрана; логику,
которой нужен SceneTree, следует размещать в `_ready()`.
## DEC-010. Первый хоппер имеет строгий приоритет при обычной выдаче
Статус: **принято**.
В профиле `DualHopperAuto` приложение строит план по точному программному остатку, а не выбирает
стартовый хоппер по бинарному датчику уровня или по сравнению запасов. Для запроса `N`:
- из hopper 1 планируется `min(N, остаток hopper 1)`;
- из hopper 2 планируется только разница `N - hopper 1`;
- команда hopper 2 отправляется после подтверждения всех запланированных жетонов hopper 1;
- объём одного пакета ограничен 99 жетонами; при большем объёме пакеты одного хоппера идут
последовательно, и следующий отправляется только после всех `OK` предыдущего (`APPEND=N`
подтверждает лишь постановку в очередь);
- если суммарного учётного остатка меньше `N`, выдача отклоняется до отправки `309N/409N`.
Бинарные датчики `Low/Enough` продолжают описывать уровень, но не меняют порядок маршрутизации.
Если уже активный хоппер возвращает ошибку или связь теряется, автоматического перехода на другой
хоппер нет: сохраняется at-most-once-политика, учитывается только подтверждённая выдача и операция
завершается безопасной ошибкой.
## DEC-011. Точный учёт жетонов является отключаемой бизнес-политикой
Статус: **принято**.
`AppSettings.TokenInventory.Enabled` отделяет сохранённый точный остаток от бинарных аппаратных
датчиков. `TokenInventory.PurchaseLimitEnabled` независимо определяет, должен ли этот остаток
ограничивать покупку. Оба флага включены по умолчанию ради совместимости. При активном лимите
полное количество к выдаче, включая бонус, проверяется до карточного запроса оплаты и повторно
перед выдачей; наличная ветка проверяется до команды хопперу.
При включённом учёте и выключенном лимите остаток продолжает отображаться и уменьшаться по
подтверждённой выдаче, но не блокирует оплату. Доступность Home определяется датчиками. В
`DualHopperAuto` записанный остаток hopper 1 используется как первая часть плана, а вся разница
направляется в hopper 2 независимо от его записанного остатка.
При выключенном учёте `user://data/token_inventory.json` сохраняется, но не читается как лимит
и не уменьшается. Сервисные действия пересчёта/пополнения и числовая индикация скрываются, тогда
как качественное состояние `Low/Enough/Unknown` продолжает определяться датчиками. Для
`DualHopperAuto` запрос направляется в hopper 1 последовательными пакетами до 99 жетонов.
Автоматический переход на другой хоппер после ошибки запрещён прежней at-most-once-политикой.
Перед повторным включением точного учёта оператор должен пересчитать физические остатки.