183 lines
18 KiB
Markdown
183 lines
18 KiB
Markdown
# Архитектурные решения 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-политикой.
|
||
Перед повторным включением точного учёта оператор должен пересчитать физические остатки.
|