18 KiB
Raw Permalink Blame History

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