# Плеер Ren'Py: что он делает так же, как Ren'Py Плеер (`frontend/src/player/`) исполняет сценарий Ren'Py из IR в браузере. Здесь — то, что повторяет поведение Ren'Py, а не его упрощение. Принцип: если игра приносит свой код (библиотеку текстовых эффектов, свои displayable, свои настройки), плеер запускает **её код**, а не заменяет его собственной копией. ## Геометрия трансформаций `rendertransform.ts` — порт `RenderTransform` из `renpy/display/accelerator.pyx`: обрезка (`crop`), `size`/`fit`, `zoom` и отражения, `rotate` с `rotate_pad`, 3D-повороты (`xrotate`, `yrotate`, `zrotate`) вокруг `matrixanchor`, `zpos`, `zzoom`, `matrixtransform`, якорь по `transform_anchor`. Матрицы — Ren'Py (`Matrix4`: векторы-столбцы, ось z от зрителя); в CSS уходит `matrix3d` с отражённым z. `xpan`/`ypan`/`xtile`/`ytile` — отдельный элемент с повторяющимся фоном. `blend` и `additive` переводятся в `mix-blend-mode`. Тесты — `frontend/tests/rendertransform.test.mjs`. ## Слои и камера `config.layers` задаёт порядок слоёв снизу вверх; слой `screens` — собственный интерфейс плеера (текстбокс, меню, экраны), слои до него лежат под интерфейсом, после — над ним. Игра управляет слоями так же, как в Ren'Py: * `show`/`hide`/`scene ... onlayer имя`, `renpy.show/hide/scene(layer=...)`, `renpy.showing`, `renpy.get_showing_tags`, `renpy.get_attributes` с `layer`; * `show screen`/`hide screen`/`call screen ... onlayer имя`, свойство `layer` экрана, `renpy.show_screen(_layer=...)`, `renpy.get_screen(layer=...)`; * `renpy.add_layer(имя, above=, below=)` — порядок сохраняется в сохранении; * `camera имя at ...` и `renpy.show_layer_at(список, layer=)` — трансформация всего слоя (анимируемая, с теми же свойствами, что у картинки); * `perspective True|число|(near, p, far)` на слое: картинки со `zpos` уходят в глубину с перспективой (CSS `perspective` на слое). Смена сцены (`with`) снимает копию всех слоёв под интерфейсом. `preferences.transitions = 0` отключает переходы. ## Откат Плеер хранит снимок состояния на каждом взаимодействии (до 50) и возвращается к предыдущему по колесу вверх, `PageUp` и действию `Rollback`. Журнал очищается при новой игре и загрузке. ## Текст * `{font}`, `{alpha}`, `{k}`, `{plain}`, `{outlinecolor}`, `{cps}` (в том числе `{cps=*2}` и `{/cps}`), `{a=...}` (адреса, `show:`, `hide:`, `jump:`, `call_in_new_context:` или `config.hyperlink_handler`), `{rb}`/`{rt}` (ruby), `{space=N}`, `{vspace=N}`, `{alt}`, `{image=...}`. * **Свои теги игры** (`config.custom_text_tags[...]`, `config.self_closing_custom_text_tags[...]`): плеер вызывает функцию игры с `(tag, argument, contents)`, где `contents` — пары `(renpy.TEXT_TEXT | TEXT_TAG, текст)`; ответ — тот же список, а `renpy.TEXT_DISPLAYABLE` вставляет displayable в строку как один символ. Так работают Kinetic Text Tags (`{bt}`, `{fi}`, `{sc}`, `{rotat}`, `{chaos}`, `{swap}`, `{move}`, `{omega}`), `{glitch}`, `{gradient}`, `{explode}` и `{ruby}` в *That New Teacher*: их библиотеки — обычные скрипты игры. * История, сохранения и перевод видят текст без вызова функций тегов (`parseText(text, false)`). ## API отрисовки для своих displayable `render.ts`: `renpy.Displayable`, `renpy.Render` (`blit`, `subpixel_blit`, `fill`, `canvas`, `subsurface`, `zoom`), `renpy.render`, `renpy.redraw`, `Text.set_text`. Дерево рендера превращается в DOM и перерисовывается, пока элемент на странице. ## Настройки игры `preferences` — объект настроек плеера: `text_cps`, `afm_time`, `afm_enable`, `fullscreen`, `transitions`, `language`, `volumes[...]`, `get_volume`/`set_volume`, `get_mute`/`set_mute`, `get_all_mixers`; `default preferences.x = ...` добавляет поле. `Color(...)` — настоящий `Color` Ren'Py (`interpolate`, `tint`, `shade`, `hexcode`, `replace_*`, `rgb`/`hls`/`hsv`). `renpy.fetch` отправляет запрос из браузера (нужен CORS на сервере); без сети игра получает `FetchError`, как в Ren'Py. Шрифты: `FontGroup().add(файл, от, до)` собирается в одно CSS-семейство с `unicode-range`. `config.mouse_displayable = MouseDisplayable(...)` и `config.mouse` меняют курсор. ## Чего пока нет * Шейдеры и сетки (`renpy.register_shader`, ATL `shader`/`mesh`, `u_*`, `gl_*`), поэтому шейдерные эффекты текста (`{wave}`) и размытие через `blur.rpy` в профилях пропускаются; частичное `additive` (0 < x < 1). * Live2D. * Снимки экрана в слотах сохранения и импорт `.save` Ren'Py. * Пропуск только прочитанного текста (`preferences.skip_unseen` сохраняется, но не действует).