Godot 4.7 project for authoring, previewing, validating, and building Exchanger PCK themes. Includes the example and space-pixel themes, binding contracts, editor tooling, validation/build scripts, and project documentation.
368 lines
30 KiB
Markdown
368 lines
30 KiB
Markdown
# Manifest v2 и binding-контракт
|
||
|
||
## Зачем нужны биндинги
|
||
|
||
Тема управляет внешним видом и может выполнять собственный доверенный GDScript. Exchanger находит
|
||
нужную кнопку, Label или поле по `exchanger_binding_id`, проверяет класс узла и подключает
|
||
app-owned обработчик. Поэтому автор темы может свободно менять иерархию, имена и внутреннюю логику,
|
||
но не ID и требуемый тип обязательных узлов.
|
||
|
||
## Manifest v2
|
||
|
||
`themes/<id>/package/manifest.json` содержит `Format: ExchangerThemeDlc`, `FormatVersion: 2` и
|
||
ровно девять `ScreenScenes`:
|
||
|
||
| Screen binding | Файл | Имя корня |
|
||
| --- | --- | --- |
|
||
| `screen.home` | `screen_home.tscn` | `ThemeScreen_Home` |
|
||
| `screen.cash_payment` | `screen_cash_payment.tscn` | `ThemeScreen_CashPayment` |
|
||
| `screen.card_amount` | `screen_card_amount.tscn` | `ThemeScreen_CardAmount` |
|
||
| `screen.card_custom_amount` | `screen_card_custom_amount.tscn` | `ThemeScreen_CardCustomAmount` |
|
||
| `screen.card_terminal` | `screen_card_terminal.tscn` | `ThemeScreen_CardTerminal` |
|
||
| `screen.success` | `screen_success.tscn` | `ThemeScreen_Success` |
|
||
| `screen.error` | `screen_error.tscn` | `ThemeScreen_Error` |
|
||
| `screen.service_access` | `screen_service_access.tscn` | `ThemeScreen_ServiceAccess` |
|
||
| `screen.settings` | `screen_settings.tscn` | `ThemeScreen_Settings` |
|
||
|
||
Каждый файл находится в `res://exchanger_theme_dlc/scenes/`. Корень — `Control` с metadata
|
||
`exchanger_screen_binding_id = screen.<key>`.
|
||
|
||
Manifest может дополнительно объявить интерактивного питомца:
|
||
|
||
```json
|
||
"InteractivePetScene": "res://exchanger_theme_dlc/components/interactive_pet.tscn"
|
||
```
|
||
|
||
Поле необязательно. Если оно задано, путь обязан находиться внутри
|
||
`res://exchanger_theme_dlc/`, указывать на существующую `.tscn`, а корень сцены должен наследовать
|
||
`Node2D` и реализовывать нейтральный pet API из [SCRIPTING.md](SCRIPTING.md). Путь к самой сцене
|
||
задаётся manifest, а её изображения подключаются обычными resource-ссылками из любого каталога
|
||
внутри `package/`: имя папки с текстурами не является частью контракта.
|
||
|
||
## Навигация питомца
|
||
|
||
Если manifest объявляет `InteractivePetScene`, текущий Builder требует невидимую навигацию версии
|
||
1 во всех восьми production-сценах, кроме `screen.settings`. Host находит её только по metadata и
|
||
не анализирует имена узлов или геометрию визуальных `Control`:
|
||
|
||
- единственный `Node2D`-корень: `exchanger_pet_navigation_root = true`,
|
||
`exchanger_pet_navigation_version = 1`, `exchanger_pet_default_surface_id` и
|
||
`exchanger_pet_roaming_bounds: Rect2`;
|
||
- горизонтальная поверхность — любой вложенный `Node2D` с `exchanger_pet_surface = true`,
|
||
уникальным `exchanger_pet_surface_id`, числовыми `exchanger_pet_surface_from_x`,
|
||
`exchanger_pet_surface_to_x`, `exchanger_pet_surface_y` и необязательным
|
||
`exchanger_pet_surface_sit_points: PackedFloat32Array`;
|
||
- вертикальный переход — любой вложенный `Node2D` с `exchanger_pet_climb_edge = true`, уникальным
|
||
`exchanger_pet_climb_edge_id`, ссылками `exchanger_pet_climb_from_surface_id` /
|
||
`exchanger_pet_climb_to_surface_id`, координатами `exchanger_pet_climb_x`,
|
||
`exchanger_pet_climb_from_y`, `exchanger_pet_climb_to_y` и стороной
|
||
`exchanger_pet_climb_side = left|right`.
|
||
|
||
Climb edge является двунаправленной связью: `from`/`to` фиксируют две стыкуемые поверхности и
|
||
соответствующие им Y, но host может пройти её вверх или вниз и выбрать прямую анимацию по
|
||
фактическому направлению движения.
|
||
|
||
Все координаты задаются в локальной системе соответствующего экрана и обозначают реальные
|
||
экранные точки контакта,
|
||
а не позицию верхнего левого угла сцены питомца. Host получает смещение лап через optional
|
||
`get_pet_ground_offset()` и размещает root по формуле
|
||
`contact_point - ground_offset * runtime_scale`. Поверхностями могут быть пол, верхние грани
|
||
кнопок и окон; climb edges явно связывают их в граф. Навигационные `Node2D` ничего не рисуют и не
|
||
меняют визуальное дерево/геометрию `Control`. Host сохраняет ограниченный fallback для старых тем
|
||
без v1-маркеров, но новая тема с питомцем без полного набора восьми графов не проходит Builder
|
||
Validator.
|
||
|
||
Builder дополнительно требует `exchanger_self_contained_visuals = true`: это не binding для
|
||
бизнес-логики, а признак того, что root сам применяет package Theme и содержит фон/decoration.
|
||
Preview не монтирует оформление и не изменяет дерево production-сцены.
|
||
|
||
## Каталог 481 элемента
|
||
|
||
`contracts/screen_bindings.v2.json` — авторитетная копия builder. Для каждого экрана она хранит
|
||
ID, допустимый Godot-класс, обязательность и fallback path host. Каждый package содержит точную
|
||
байтовую копию в `package/contracts/screen_bindings.v2.json`; host сверяет SHA-256 до загрузки сцен.
|
||
|
||
Текущий immutable-контракт:
|
||
|
||
| Экран | Биндингов в JSON |
|
||
| --- | ---: |
|
||
| `screen.home` | 14 |
|
||
| `screen.cash_payment` | 9 |
|
||
| `screen.card_amount` | 22 |
|
||
| `screen.card_custom_amount` | 18 |
|
||
| `screen.card_terminal` | 11 |
|
||
| `screen.success` | 4 |
|
||
| `screen.error` | 4 |
|
||
| `screen.service_access` | 19 |
|
||
| `screen.settings` | 380 |
|
||
| **Всего** | **481** |
|
||
|
||
Все 481 записи имеют `Required: true`. SHA-256 канонического файла:
|
||
`DDDC89118BEEA78A820561464001656136ED42AC1AD5710FB109D25ADAF44143`.
|
||
|
||
На нужном узле задаётся metadata:
|
||
|
||
```text
|
||
exchanger_binding_id = home.scroll.content.payment_panel.margin.content.buttons.cash_button
|
||
```
|
||
|
||
ID уникален во всём пакете. Допускается наследник указанного класса. Все визуальные узлы принадлежат
|
||
теме: Exchanger не добавляет, не удаляет, не переносит и не стилизует `Control` внутри ThemeView.
|
||
Host только подключает сигналы и обновляет данные, видимость и доступность существующих узлов.
|
||
|
||
### Supplemental runtime bindings
|
||
|
||
После фиксации immutable JSON приложению понадобились дополнительные готовые theme-owned узлы.
|
||
Чтобы не менять байтовый контракт выпущенных тем, их ID проверяются Builder Validator отдельно.
|
||
Для новой темы обязательны ещё 24 element-binding; два дополнительных compatible binding ниже
|
||
могут отсутствовать в уже выпущенной теме:
|
||
|
||
| Экран | Binding ID | Требуемый тип |
|
||
| --- | --- | --- |
|
||
| Home | `home.scroll.content.advertisement_panel` | `Control` |
|
||
| Home | `home.scroll.content.stock_status.meter.empty` | `Control` |
|
||
| Home | `home.scroll.content.stock_status.meter.low` | `Control` |
|
||
| Home | `home.scroll.content.stock_status.meter.enough` | `Control` |
|
||
| Home | `home.scroll.content.stock_status.meter.much` | `Control` |
|
||
| Home | `home.scroll.content.stock_status.approximate_count` | `Label` |
|
||
| CardAmount | `card_amount.content.reward_panel.content.bonus` | `Label` |
|
||
| CashPayment | `cash_payment.content.top_bar.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| CardAmount | `card_amount.content.top_bar.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| CardCustomAmount | `card_custom_amount.content.top_bar.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| CardTerminal | `card_terminal.content.top_bar.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| Success | `success.content.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| Error | `error.content.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| ServiceAccess | `service_access.content.home_navigation_button` | `Button` (`KeypadButton`; в `space-pixel` — `button_square`) |
|
||
| Settings | `settings.footer` | `PanelContainer` |
|
||
| Settings | `settings.footer.save_and_exit_button` | `Button` |
|
||
| Settings | `settings.scroll.content.footer_clearance` | `Control` |
|
||
| Settings | `settings.scroll.content.service_inventory_panel.margin.content.description` | `Label` |
|
||
| Settings | `settings.scroll.content.service_inventory_panel.margin.content.inventory_enabled` | `CheckButton` |
|
||
| Settings | `settings.scroll.content.service_inventory_panel.margin.content.purchase_limit_enabled` | `CheckButton` |
|
||
| Settings | `settings.scroll.content.menu_visibility_panel` | `PanelContainer` |
|
||
| Settings | `settings.scroll.content.menu_visibility_panel.margin.content.show_stock_status` | `CheckButton` |
|
||
| Settings | `settings.scroll.content.menu_visibility_panel.margin.content.show_right_character` | `CheckButton` |
|
||
| Settings | `settings.scroll.content.menu_visibility_panel.margin.content.show_promotion_block` | `CheckButton` |
|
||
|
||
Полностью обновлённая тема содержит **509 element-binding**: 481 из JSON, 24 обязательных и четыре
|
||
optional supplemental. Metadata девяти корней `exchanger_screen_binding_id` считается отдельно.
|
||
|
||
`tools/Sync-HostBindingContract.ps1` намеренно исключает эти 28 ID из генерируемого
|
||
immutable JSON, даже если host обращается к их каноническому `FallbackPath` через буквальный
|
||
`GetNode`. Иначе обычная синхронизация ошибочно превращала бы supplemental-узел в новый элемент
|
||
зафиксированного каталога и ломала обратную совместимость выпущенных тем. Полный список выше
|
||
одновременно защищён `Test-PackagedBindingContract.ps1`.
|
||
|
||
Дополнительные compatible binding:
|
||
|
||
| Экран | Binding ID | Требуемый тип |
|
||
| --- | --- | --- |
|
||
| CashPayment | `cash_payment.content.banner_text` | `Label` |
|
||
| Settings | `settings.scroll.content.animated_banner_texts_panel` | `Control` |
|
||
| Settings | `settings.scroll.content.advertisement_poster_panel.margin.content.select_button` | `Button` |
|
||
| Settings | `settings.scroll.content.menu_visibility_panel.margin.content.show_interactive_pet` | `CheckButton` |
|
||
|
||
`cash_payment.content.banner_text` — подпись нижней анимированной плашки с НЛО на экране
|
||
наличной оплаты. `settings.scroll.content.animated_banner_texts_panel` — готовая панель четырёх
|
||
полей настройки текстов нижних баннеров: наличные, выбор суммы картой, ручной ввод суммы и шаблон
|
||
таймера терминала. В последнем поле `{seconds}` заменяется текущим числом секунд.
|
||
`select_button` открывает системный выбор файла изображения для промо-блока, а
|
||
`show_interactive_pet` — готовый переключатель с подписью «ИНТЕРАКТИВНЫЙ ПИТОМЕЦ»:
|
||
он включает или отключает питомца активной темы. Если manifest не объявляет
|
||
`InteractivePetScene`, host делает этот переключатель недоступным, не меняя сохранённое
|
||
предпочтение владельца. Все четыре ID получают
|
||
встроенный fallback в ранее выпущенных PCK.
|
||
|
||
`home_navigation_button` — готовая компактная theme-owned кнопка с изображением дома. Её основа
|
||
— `KeypadButton` (для `space-pixel` — `button_square`). На
|
||
CashPayment, CardAmount, CardCustomAmount и CardTerminal она стоит рядом с `BackButton`; на
|
||
Success, Error и ServiceAccess, где верхней кнопки «Назад» нет, занимает её обычное левое место.
|
||
Host только подключает безопасный возврат на Home. До завершения подтверждённой выдачи на Success
|
||
кнопка остаётся видимой, но недоступной. Старый PCK без ID получает встроенный fallback по
|
||
каноническому пути.
|
||
|
||
Один дополнительный биндинг необязателен:
|
||
|
||
| Экран | Binding ID | Тип для Builder Validator |
|
||
| --- | --- | --- |
|
||
| Home | `home.decoration.custom_text_block` | `Control` |
|
||
| Home | `home.decoration.right_character` | `TextureRect` |
|
||
|
||
Если Home содержит пользовательскую речевую плашку и персонажа рядом с ней, назначьте
|
||
`home.decoration.custom_text_block` готовой группе из фона плашки и персонажа. Host одновременно
|
||
переключает эту группу и Label `home.scroll.content.speech_text`; он не создаёт и не перестраивает
|
||
визуальные узлы. Для совместимости со старыми темами можно оставить
|
||
`home.decoration.right_character` на самом персонаже: тогда host скроет персонажа и текст, но
|
||
старая тема не обязана скрывать отдельный фон речевой плашки. Если персонажа или плашки нет, не
|
||
создавайте пустые узлы только ради optional ID.
|
||
|
||
### Что не является element-binding
|
||
|
||
Не добавляйте вымышленные `exchanger_binding_id` для фоновой графики, частиц, анимационных
|
||
контроллеров или питомца. Для них используются другие точки интеграции:
|
||
|
||
- production-сцена определяется через `ScreenScenes` и `exchanger_screen_binding_id` корня;
|
||
- интерактивный питомец подключается полем manifest `InteractivePetScene` и публичным pet API;
|
||
- составные действия питомца используют методы и сигналы composite API, а не binding metadata;
|
||
- поверхности, sit points и climb edges используют navigation v1 metadata;
|
||
- полностью автономные декоративные узлы не требуют связи с host.
|
||
|
||
Практический процесс создания составного действия описан в
|
||
[COMPOSITE_PET_ANIMATIONS.md](COMPOSITE_PET_ANIMATIONS.md).
|
||
|
||
Устаревшее описание приложения `subtitle` не входит в контракт и не должно создаваться темой.
|
||
Вместо него предусмотрена самостоятельная покупательская фраза: Label
|
||
`home.scroll.content.speech_text` расположен внутри авторской речевой плашки Home, а SETTINGS
|
||
содержит поле `...branding_panel...short_text`, Label ошибки `...short_text_error` и строку
|
||
предпросмотра `...branding_preview_panel...short_text`. Поле ограничено 48 символами; ни один из
|
||
этих ID не содержит `subtitle`. Переключатель SETTINGS по историческому ID
|
||
`...show_right_character` отображается пользователю как «ПОКАЗЫВАТЬ ПОЛЬЗОВАТЕЛЬСКИЙ ТЕКСТ» и
|
||
управляет всей optional-группой, а не только персонажем.
|
||
|
||
Home содержит готовые theme-owned Label состояния жетонов и подключения, а промо-блок —
|
||
`TextureRect`, `VideoStreamPlayer` и fallback-`Label`. `VideoStreamPlayer` не содержит
|
||
скрипта и управляется C#-логикой Exchanger через биндинг.
|
||
|
||
Home также использует supplemental runtime bindings, которые валидатор проверяет отдельно от
|
||
481 элемента контракта: готовый root промо-панели, четыре визуальных состояния
|
||
полоски `home.scroll.content.stock_status.meter.empty/low/enough/much` и Label
|
||
`home.scroll.content.stock_status.approximate_count`. Тема заранее располагает состояние слева,
|
||
полоску по центру и приблизительный остаток справа; host меняет только текст и видимость. Полный
|
||
список supplemental ID приведён выше.
|
||
|
||
На экране наличной оплаты итог разделён на три независимых обязательных `Label`:
|
||
|
||
- `cash_payment.content.summary.margin.values.balance` — внесённая сумма в рублях,
|
||
например `1000 РУБЛЕЙ`;
|
||
- `cash_payment.content.summary.margin.values.tokens` — базовое количество жетонов без бонуса,
|
||
например `100 ЖЕТОНОВ`;
|
||
- `cash_payment.content.summary.margin.values.bonus` — бонус отдельным текстом, например
|
||
`+ 50 ЖЕТОНОВ\nВ ПОДАРОК`; если бонуса нет, host выводит `БЕЗ БОНУСА`.
|
||
|
||
Не объединяйте рубли и жетоны в одном из этих узлов и не скрывайте `tokens` или `bonus`: Exchanger
|
||
заполняет все три значения независимо, а тема полностью определяет их положение и оформление.
|
||
Заголовок этой панели — «К ВЫДАЧЕ».
|
||
|
||
Экран выбора карточной суммы использует ту же композицию базовых и бонусных жетонов:
|
||
|
||
- `card_amount.content.reward_panel.content.value` — обязательный `Label` immutable-контракта по
|
||
прежнему пути `SafeMargin/Content/RewardPanel/Content/Value`; host выводит в нём только базовое
|
||
количество жетонов;
|
||
- `card_amount.content.reward_panel.content.bonus` — supplemental runtime `Label` по пути
|
||
`SafeMargin/Content/RewardPanel/Content/Bonus`; host выводит `+ N ЖЕТОНОВ\nВ ПОДАРОК` или
|
||
`БЕЗ БОНУСА`.
|
||
|
||
`Value` располагается слева, `Bonus` — справа. Supplemental ID намеренно не входит в immutable
|
||
JSON-каталог из 481 элемента. Это сохраняет совместимость старых тем: если отдельного `Bonus` нет,
|
||
host использует legacy fallback и выводит совмещённый итог в существующем `Value`. Новые темы,
|
||
которые проходят текущий Builder Validator, обязаны содержать оба готовых theme-owned `Label`.
|
||
|
||
Экран ручного ввода карточной суммы также разделяет платёжные данные:
|
||
|
||
- `card_custom_amount.content.keypad_panel.content.input_value` — введённая сумма в рублях без
|
||
единиц измерения, например `1000`; это Label внутри поля под заголовком «ВВЕДИТЕ СУММУ»;
|
||
- `card_custom_amount.content.amount_panel.margin.values.amount` — базовое количество жетонов;
|
||
- `card_custom_amount.content.amount_panel.margin.values.tokens` — бонусные жетоны, например
|
||
`+ 50 ЖЕТОНОВ\nВ ПОДАРОК`; при отсутствии бонуса host выводит `БЕЗ БОНУСА`.
|
||
|
||
Названия двух последних ID сохранены для совместимости существующих тем. Визуально оба верхних
|
||
Label относятся только к жетонам, располагаются слева и справа как на CashPayment; рубли выводятся
|
||
исключительно через `input_value`.
|
||
|
||
Динамические коллекции представлены фиксированными слотами:
|
||
|
||
- 16 кнопок `card_amount...preset_grid.slot_00..15`;
|
||
- 16 строк бонусов `settings...bonus_panel...rows.slot_00..15`;
|
||
- 16 строк готовых сумм `settings...preset_rows.slot_00..15`;
|
||
- 12 строк промо-роликов `settings...advertisement_playlist...rows.slot_00..11`.
|
||
|
||
Ещё 57 биндингов относятся к двум theme-owned экранным клавиатурам SETTINGS:
|
||
|
||
- текстовая панель с 33 фиксированными буквенными слотами в строках `12 + 11 + 10`;
|
||
- переключатели языка, регистра и спецсимволов, пробел, очистка, удаление и перемещение каретки;
|
||
- цифровая панель с десятью цифрами, очисткой и удалением;
|
||
- отдельная кнопка `СКРЫТЬ` в правом верхнем углу каждой панели.
|
||
|
||
Обе панели существуют в production-сцене заранее и имеют `visible = false`. Exchanger может
|
||
показывать одну панель, менять текст/видимость отдельных клавиш и подключать `pressed`, но не
|
||
добавляет клавиши и не меняет их расположение. Все кнопки обязаны иметь `focus_mode = None`,
|
||
чтобы нажатие экранной клавиши не забирало фокус у редактируемого поля.
|
||
|
||
Ещё 14 биндингов образуют theme-owned интерфейс учёта жетонов в SETTINGS:
|
||
|
||
- кнопка вкладки `settings.scroll.content.section_tabs.service_button`;
|
||
- скрытая панель `settings.scroll.content.service_inventory_panel` с остатком, статусом и
|
||
действиями автоматического пересчёта/ручного добавления;
|
||
- два Label `...hopper1_label` и `...hopper2_label`, в которых host показывает точный остаток
|
||
соответствующего хоппера рядом с его названием;
|
||
- скрытый overlay `settings.service_dialog_overlay` с динамическими заголовком и описанием,
|
||
редактируемым `SpinBox amount`, отменой и подтверждением.
|
||
|
||
Обе темы обязаны заранее содержать эту панель и диалог. Exchanger может менять значения,
|
||
`Visible`/`Disabled` и подключать сигналы, но не создаёт диалог, поля или кнопки и не изменяет
|
||
их геометрию. Значение `amount` не имеет текстового suffix, чтобы не обрезать многозначные числа.
|
||
|
||
Помимо этих 14 каталогизированных ID, Builder требует в сервисной панели три
|
||
supplemental runtime-binding. `settings.scroll.content.service_inventory_panel.margin.content.description`
|
||
— это `Label` по каноническому пути
|
||
`SafeMargin/Scroll/Content/ServiceInventoryPanel/Margin/Content/Description`; host обновляет
|
||
его описание в зависимости от режима учёта.
|
||
`settings.scroll.content.service_inventory_panel.margin.content.inventory_enabled` — это
|
||
`CheckButton` по каноническому пути
|
||
`SafeMargin/Scroll/Content/ServiceInventoryPanel/Margin/Content/InventoryAccountingEnabled`,
|
||
расположенный сразу после `Description`, с подписью «УЧИТЫВАТЬ ОСТАТОК ЖЕТОНОВ» и включённым
|
||
состоянием по умолчанию. Сразу после него расположен включённый по умолчанию `CheckButton`
|
||
`settings.scroll.content.service_inventory_panel.margin.content.purchase_limit_enabled` по каноническому
|
||
пути `SafeMargin/Scroll/Content/ServiceInventoryPanel/Margin/Content/PurchaseLimitEnabled` с
|
||
подписью «НЕ ПРОДАВАТЬ БОЛЬШЕ ОСТАТКА». Host подключает к готовым переключателям сохранение
|
||
режима учёта и ограничения продаж доступным остатком; создавать эти узлы в runtime нельзя.
|
||
Supplemental ID не входят в `contracts/screen_bindings.v2.json`, поэтому
|
||
число 481 и байтовый SHA-256 каталога не изменяются.
|
||
|
||
Каждая строка целиком создана дизайнером и имеет binding root для переключения `Visible`, а её
|
||
поля, кнопка удаления и Label ошибки размечены отдельными индексированными ID. Неиспользуемые
|
||
слоты скрыты и поэтому не занимают место в Container. Для пустых коллекций предусмотрены
|
||
theme-owned `empty_state` Label.
|
||
|
||
## Как читать overlay
|
||
|
||
В preview включите `BINDINGS: ON`. Оранжевая рамка показывает геометрию размеченного узла, тёмная
|
||
подпись — точный ID, который увидит Exchanger. Подпись корня начинается с `screen.`. Наложение
|
||
подписей допустимо на плотных экранах и не влияет на PCK; для оценки композиции выключите overlay.
|
||
|
||
На вкладке SETTINGS кнопка `KEYBOARD: OFF/TEXT/NUM` последовательно показывает скрытое,
|
||
текстовое и цифровое состояния. Это только диагностическое переключение `Visible` уже
|
||
существующих theme-owned панелей; preview не добавляет клавиатуру в production-сцену.
|
||
Кнопка `INVENTORY: OFF/PANEL/DIALOG` аналогично показывает готовую сервисную панель и диалог,
|
||
скрывая на время просмотра прочие секции SETTINGS и не меняя их дерево или геометрию.
|
||
|
||
Если рамка отсутствует, у узла нет metadata. Если одна рамка неожиданно покрывает большую область,
|
||
binding назначен контейнеру, а не ожидаемому дочернему контролу — сверьте `Types` и `FallbackPath`
|
||
в JSON.
|
||
|
||
## Синхронизация без рассинхронизации
|
||
|
||
Обычная разработка темы не требует соседнего Exchanger:
|
||
|
||
```powershell
|
||
.\tools\Test-PackagedBindingContract.ps1
|
||
```
|
||
|
||
При согласованном изменении C# host используйте точную папку Exchanger:
|
||
|
||
```powershell
|
||
.\tools\Sync-HostBindingContract.ps1 -HostProject ..\exchanger
|
||
.\tools\Import-HostSceneTemplates.ps1 -HostProject ..\exchanger -ThemeIds example
|
||
.\tools\Sync-HostBindingContract.ps1 -HostProject ..\exchanger -Check
|
||
.\tools\Test-PackagedBindingContract.ps1
|
||
```
|
||
|
||
`Sync-HostBindingContract.ps1` генерирует каталог из вызовов `GetNode(SafeMargin/...)` host и
|
||
копирует его во все packages. `Import-HostSceneTemplates.ps1` перезаписывает `package/scenes` у
|
||
указанных тем: не запускайте его для оформленной темы без резервной копии/осознанного плана
|
||
переноса дизайна. После миграции валидируйте `example` и каждую тему, затем обновите закреплённый
|
||
SHA-256 и тесты в Exchanger в рамках отдельной host-задачи.
|
||
|
||
Нельзя вручную менять или переформатировать package-копию JSON: байтовый хеш изменится, даже если
|
||
смысл данных сохранится.
|