No description
  • TypeScript 82.6%
  • JavaScript 16.2%
  • CSS 0.9%
  • HTML 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-18 18:02:33 +05:00
.idea Прототип прецизионного платформера в духе Super Meat Boy на Phaser 3 2026-09-26 14:08:18 +05:00
docker Docker: multi-stage сборка nginx + compose с dev-профилем (Vite HMR) 2026-10-12 21:47:48 +05:00
docs Гусеница-хищник crawler (глиф c): смертельный ползун на всю клетку — горизонтальный едет по верху '#' с падением с края и разворотом от ступеньки, вертикальный (axis 'y') цепляется боком за стену '#' и ползёт вверх/вниз с разворотом на конце стены; лапки-ступни, костяные шипы, голова с красным глазом (отдельная вертикальная текстура без поворота — без обрезанной головы и зазора до стены), DeathCause 'crawler'; редактор (палитра с иконкой, инспектор ось/направление, зеркало/поворот), ур. 42 «Шипастый сад», смоук-пробы ползания/падения/вертикального патруля 2026-10-18 18:02:33 +05:00
src Гусеница-хищник crawler (глиф c): смертельный ползун на всю клетку — горизонтальный едет по верху '#' с падением с края и разворотом от ступеньки, вертикальный (axis 'y') цепляется боком за стену '#' и ползёт вверх/вниз с разворотом на конце стены; лапки-ступни, костяные шипы, голова с красным глазом (отдельная вертикальная текстура без поворота — без обрезанной головы и зазора до стены), DeathCause 'crawler'; редактор (палитра с иконкой, инспектор ось/направление, зеркало/поворот), ур. 42 «Шипастый сад», смоук-пробы ползания/падения/вертикального патруля 2026-10-18 18:02:33 +05:00
tools Гусеница-хищник crawler (глиф c): смертельный ползун на всю клетку — горизонтальный едет по верху '#' с падением с края и разворотом от ступеньки, вертикальный (axis 'y') цепляется боком за стену '#' и ползёт вверх/вниз с разворотом на конце стены; лапки-ступни, костяные шипы, голова с красным глазом (отдельная вертикальная текстура без поворота — без обрезанной головы и зазора до стены), DeathCause 'crawler'; редактор (палитра с иконкой, инспектор ось/направление, зеркало/поворот), ур. 42 «Шипастый сад», смоук-пробы ползания/падения/вертикального патруля 2026-10-18 18:02:33 +05:00
.dockerignore Docker: порт web по умолчанию 8082, берётся из WEB_PORT (.env) 2026-10-12 22:00:20 +05:00
.env.example Docker: порт web по умолчанию 8082, берётся из WEB_PORT (.env) 2026-10-12 22:00:20 +05:00
.gitignore Docker: порт web по умолчанию 8082, берётся из WEB_PORT (.env) 2026-10-12 22:00:20 +05:00
AGENTS.md Гусеница-хищник crawler (глиф c): смертельный ползун на всю клетку — горизонтальный едет по верху '#' с падением с края и разворотом от ступеньки, вертикальный (axis 'y') цепляется боком за стену '#' и ползёт вверх/вниз с разворотом на конце стены; лапки-ступни, костяные шипы, голова с красным глазом (отдельная вертикальная текстура без поворота — без обрезанной головы и зазора до стены), DeathCause 'crawler'; редактор (палитра с иконкой, инспектор ось/направление, зеркало/поворот), ур. 42 «Шипастый сад», смоук-пробы ползания/падения/вертикального патруля 2026-10-18 18:02:33 +05:00
docker-compose.yml Docker: порт web по умолчанию 8082, берётся из WEB_PORT (.env) 2026-10-12 22:00:20 +05:00
Dockerfile Docker: копирование tools/ в build-стейдж — vite.config.ts импортирует dev-плагин, без него tsc падает с TS2307 2026-10-13 00:17:23 +05:00
editor.html Редактор: манипуляции с объектами #66–70 — ручки длины/оси у линейных объектов, выравнивание и распределение, док «Объекты» с Alt-циклом, сопряжение linkId по клику и снэп к объектам с направляющими, смоук-пробы 2026-10-06 19:10:49 +05:00
index.html Пауза на Esc, экран результата с автопереходом, мгновенный ретрай с hit-stop и процедурный чиптюн-луп 2026-09-27 01:50:14 +05:00
package-lock.json Игровые шрифты OFL: Balsamiq Sans для заголовков и Rubik для UI через @fontsource, единый стиль текста gameTextStyle с обводкой и тенью 2026-10-07 23:01:08 +05:00
package.json Перенос правок редактора из localStorage в файлы уровней; скрипт migrate-edits, CDP-проба босса и план улучшений Колосса 2026-10-13 00:12:22 +05:00
README.md Полная ледяная кампания 21–40: 18 уровней и босс «Морозный страж» (frost) с выделенной смоук-пробой 2026-10-16 16:11:35 +05:00
tsconfig.json Прототип прецизионного платформера в духе Super Meat Boy на Phaser 3 2026-09-26 14:08:18 +05:00
vite.config.ts Редактор: сохранение уровня сразу в JSON-файл через dev-бекенд Vite; реестр уровней на import.meta.glob с авторегистрацией новых файлов 2026-10-12 23:59:47 +05:00

SUPER MEW — прототип

Прецизионный платформер в духе Super Meat Boy на Phaser 4 + TypeScript + Vite. Вся графика и звук генерируются процедурно во время выполнения — внешних файлов-ассетов нет.

Требования

  • Node.js 20.19+ или 22.12+ (минимум для Vite 7);
  • npm 10+.

Проверено на Node v24.16.0 и npm 11.13.0.

Команды (Windows PowerShell)

В PowerShell всегда используйте npm.cmd, а не npm: .ps1-shim блокируется политикой выполнения скриптов.

npm.cmd install            # установка зависимостей
npm.cmd run dev            # dev-сервер: игра -> http://localhost:5173, редактор -> /editor.html
npm.cmd run dev:editor     # dev-сервер и сразу открыть редактор (/editor.html)
npm.cmd run build          # tsc --noEmit && vite build -> dist/ (index.html + editor.html)
npm.cmd run preview        # предпросмотр собранной версии
npm.cmd run preview:editor # предпросмотр собранного редактора (/editor.html)

Smoke-тест

npm.cmd run smoke собирает проект (tsc --noEmit && vite build) и прогоняет игру в headless-браузере через Chrome DevTools Protocol (CDP) — без единой npm-зависимости (только встроенные node:http/node:fs и глобальные fetch/WebSocket из Node 22+).

npm.cmd run smoke:fast запускает тот же тест против уже собранного dist/ без пересборки (~2–3 с) — удобно для быстрых итераций после npm.cmd run build.

Скриншоты по умолчанию отключены: обычный прогон не создаёт tools/.smoke/ и печатает строку Скриншоты: отключены. Включить можно флагом node tools/smoke.mjs --screenshots (после npm.cmd run build; либо npm.cmd run smoke:fast -- --screenshots) или переменной окружения SMOKE_SCREENSHOTS=1 (в PowerShell: $env:SMOKE_SCREENSHOTS=1; npm.cmd run smoke). Когда скриншоты включены, PNG (smoke.png, level-N.png, dpr2.png) сохраняются в tools/.smoke/; этот каталог игнорируется git (см. .gitignore).

Что проверяется в реальном браузере:

  • нет ли ошибок и исключений в консоли;
  • на старте активна MenuScene (выбор уровня): getMenuState() возвращает число уровней, совпадающее с LEVELS, и текущий выбранный индекс;
  • после старта уровня из меню GameScene действительно активна;
  • персонаж двигается вправо и прыгает (по измеренной дельте координат);
  • прогон по всем уровням через scene.startLevel(i): уровень активируется, размеры сетки больше нуля, ровно одна цель; механики проверяются по метаданным уровня (пилы/рельсы из objects, T пружины, B блоки) — созданы там, где объявлены, и отсутствуют там, где нет; для каждого уровня сохраняется скриншот tools/.smoke/level-N.png (только при включённых скриншотах);
  • механики проверяются в рантайме: пружина реально подбрасывает игрока вверх (скорость по Y), разрушаемый блок исчезает от касания, пила по рельсу двигается;
  • HUD действительно на экране: сцена HudScene активна, а все её тексты (уровень, попытки, время, лучшее) целиком лежат внутри буфера [0, width] x [0, height];
  • рендер-схема проверяется напрямую: DPR=1 (1920x1080, zoom 3) и DPR=2 (1280x720 CSS → буфер 2560x1440, zoom 4, HUD целиком в буфере), плюс скриншот tools/.smoke/dpr2.png (только при включённых скриншотах); в отчёте печатаются строки DPR=1/DPR=2, а замер FPS идёт 1:1;
  • Esc из игры открывает паузу: активна PauseScene, GameScene на паузе; Enter («Продолжить») возвращает в игру, а ↓ ×2 + Enter («В меню») выводит в MenuScene;
  • экран результата: finishLevel() поднимает ResultScene с временем/попытками, а «Дальше» (Enter) запускает следующий уровень;
  • таблица лидеров (#27): завершённые забеги пишутся в localStorage (supermew.runs.<имя>), а оверлей LeaderboardScene отдаёт состояние и JSON-экспорт (exportLeaderboard);
  • отдельное приложение-редактор (/editor.html) открывается, getEditorState() возвращает загруженный встроенный уровень с валидной структурой; правка клетки и сохранение переопределения в localStorage затем отражаются в игре после перезагрузки (метаданные уровня получают ожидаемую механику), а смена размера пилы через свойства объекта доходит до GameScene (sawDiameter = size × TILE); legacy-правка {rows} мигрирует с сохранением объектов, число уровней в меню совпадает;
  • общий скриншот tools/.smoke/smoke.png (только при включённых скриншотах);
  • в конце печатается таблица таймингов фаз (запуск браузера, загрузка, готовность сцены, геймплей, прогон уровней, скриншот, завершение); при отключённых скриншотах фаза скриншот занимает ~0 мс.

Нужен локальный Chromium (Chrome или Edge); при необходимости путь задаётся переменной окружения CHROME_PATH. Exit code 0 — все уровни активны и ошибок нет.

Управление

Действие Клавиши
Движение ← → или A D
Прыжок (высота зависит от удержания) Space / W / ↑
Прилипание к стене / медленное сползание автоматически при падении у стены (ввод в стену не нужен)
Прыжок от стены (wall-jump) прыжок у стены; короткий лок позволяет зацепиться за ту же стену и подниматься
Пауза (меню «Продолжить/Заново/В меню») Esc (в паузе: ↑↓/W S + Enter, Esc — продолжить)
Рестарт уровня R
Музыка вкл/выкл M
Таблица лидеров (в меню) L (оверлей: Tab/←→ — уровень, E — экспорт, Esc/Enter — назад)

Редактор — отдельное приложение, см. раздел «Редактор уровней»; в меню игры дополнительно появляются сохранённые свои уровни (★).

Меню и прогресс

При запуске BootScene генерирует текстуры и сразу открывает MenuScene — выбор уровня: вверх/вниз (↑ ↓ или W S) двигают выделение, Enter (или Space) запускает выбранный уровень, а цифры 1–9 прыгают сразу к уровню с этим номером (если он есть). Во время игры Esc открывает паузу («Продолжить/Заново/В меню»). Пройденный уровень показывает экран результата (время/лучшее/попытки, секрет, «Дальше») с автопереходом на следующий, после последнего — возврат в меню. Встроенных уровней — 40: 1 «Первый шаг» (40×22) — обучение, 2–19 — прогрессивная лесная кампания (палитра forest: платформы и mover, шипы, пружины, пилы и рельсы, исчезающие блоки и прессы, лёд и прыгучие стены, конвейеры, порталы, лестницы, ключи и замки, ящики, ветер и лазеры, финал «Сердце леса»), 20 «Колосс» — боссфайт, 21–40 — полная ледяная кампания (палитра ice: скользкий и тонкий лёд, пружины, конвейеры, кнопки и двери, порталы, ключи и замки, орбиты, ветер-порывы и лазеры, чейзер-лазер, секреты) с финальным боссом 40 «Морозный страж» (kind 'frost').

Прогресс сохраняется в localStorage: пройденные уровни отмечаются галочкой ✓, а лучшее время каждого уровня (supermew.best.<имя уровня>) показывается в строке уровня и суммируется в общий результат. Чтение и запись — в src/progress.ts. Список уровней меню строится из resolveLevels() (встроенные с правками редактора + свои).

Завершённые забеги встроенных уровней складываются в локальную таблицу лидеров (supermew.runs.<имя уровня>, хранятся LEADERBOARD_MAX лучших по времени). В меню клавиша L открывает оверлей таблицы: ранг/время/смерти/дата, переключение уровней Tab/←→, E — экспорт JSON (в буфер обмена, иначе файл leaderboard.json), Esc/Enter — назад.

Редактор уровней (отдельное приложение)

Редактор — отдельное SPA editor.html (исходники в src/editor/), в духе Paint: UI на DOM/CSS, карта рисуется на 2D-canvas. Phaser в рантайме редактора не участвует. Dev: http://localhost:5173/editor.html (или npm.cmd run dev:editor); сборка: dist/editor.html. Общий origin с игрой → общий localStorage, поэтому редактор меняет текущие, встроенные уровни, и правка сразу видна игре.

Раскладка: сверху CSS-меню (новый, сохранить, сбросить, импорт, экспорт, .ts, отмена, вернуть, тест, справка); слева список уровней (встроенные, правки помечены ●, свои ★); по центру холст с зумом и панорамой; справа свойства (имя, размер, выделенный объект, проверка); снизу панель инструментов и палитра из 37 символов в 4 группах («Основные», «Тайлы», «Объекты», «Механики»).

Инструменты (V/B/E/H) и палитра из 37 элементов в 4 группах:

Инструмент Клавиша Что делает
Выделение V выбирает объекты и клетки тайлов (клик или рамка; Shift — добавить к выделению); свойства — в инспекторе
Кисть B рисует символом; для X/R/H/V ставит объект (протяжка задаёт размер/длину)
Ластик E очищает клетки
Панорама H / пробел / СКМ тащит холст
Управление Клавиши / мышь
Стереть клетку / удалить объект ПКМ (протяжка стирает тайлы)
Зум Ctrl + колесо, + / −, кнопки в углу холста
Вписать в экран Ctrl+0 или кнопка ⤢
Панорама ←↑↓→ (Shift — быстрее), колесо, СКМ
Сетка вкл/выкл G
Выбор символа палитры 1–9, 0, [ / ]
Следующий/предыдущий уровень Tab / Shift+Tab
Дубликат выделения (при переносе) Ctrl / Cmd + протяжка центра
Сохранить правку Ctrl+S
Отменить / вернуть Ctrl+Z / Ctrl+Y
Новый уровень (имя и размер) N
Переименовать K (фокус на поле имени)
Тест без сохранения P
Тест с сохранением правки Shift+P
Удалить объект / сбросить к встроенному Del
Очистить нутро / вернуть рамку кнопки в свойствах
Импорт/экспорт JSON, экспорт .ts кнопки меню (модалка с текстом)

Символы тайлов и правила — как в контракте уровня (см. ниже); пилы/рельсы/платформы — это объекты objects[] (пила X, рельс R, платформа H/V), у них есть собственные свойства. Спавн S и цель G уникальны — постановка нового стирает прежний. Инспектор показывает размер, тип уровня (встроенный / правка / свой), число объектов, результат проверки (format.ts; невалидный уровень помечается ⚠ — живой чип у холста и бейдж в списке уровней) и — при выборе объекта инструментом V — его свойства (тип, позиция, диаметр пилы в тайлах, длина рельса/платформы, ось) с кнопкой удаления. Выделенные рамкой клетки тайлов подсвечиваются и переносятся/удаляются вместе с объектами.

Сохранение (Ctrl+S) пишет переопределение по имени уровня в localStorage (supermew.leveledits, см. src/levels/edits.ts). Игра собирает итоговый список resolveLevels() — встроенные с правками плюс свои — и сразу их использует. Del удаляет правку и возвращает встроенную версию, а экспорт .ts даёт готовый модуль для src/levels/, если правку нужно закрепить в исходниках. Кнопка «Тест» (P) пишет текущий уровень во временный канал (supermew.preview) и открывает игру по адресу /?preview=1 — стартует незакоммиченная правка, минуя сохранение и меню; Shift+P сохраняет правку и открывает /?play=<имя> (тоже сразу, минуя меню).

Проверка «Теста» в смоуке: приложение /editor.html поднимается, window.__supermewEditor отдаёт состояние, UI содержит панель инструментов/палитру/холст; правка клетки и сохранение применяются в игре после перезагрузки.

Механики

  • Coyote time — прыжок ещё возможен короткое время после схода с края;
  • Jump buffer — нажатие прыжка чуть раньше приземления не теряется;
  • Variable jump — высота прыжка зависит от того, как долго удерживается клавиша;
  • Wall slide + wall jump — автоприлипание к стене и медленное сползание (жать в стену не нужно, ввод от стены отпускает её), а короткий input-lockout после отталкивания позволяет зацепиться за ту же стену и подниматься по ней;
  • Вращающиеся пилы — статичные диски-ловушки: объект saw задаёт центр и диаметр в тайлах (size); касание убивает, а вращение — чисто визуальный эффект;
  • Пилы по рельсу — объект rail задаёт рельс (length клеток) и диаметр пилы (size), по рельсу пила ездит туда-обратно (касание убивает);
  • Пружины — тайл T подбрасывает игрока вверх; импульс не режется variable jump и рассчитан на высокие платформы, недостижимые обычным прыжком;
  • Разрушающиеся блоки — тайл B твёрдый: по нему можно пройтись, но спустя короткую задержку после касания он рассыпается (с предупреждающим оттенком) и восстанавливается при респауне;
  • Исчезающие блоки — тайл P мигает по циклу (период VANISH_PERIOD_MS): твёрдый только в активной фазе, вне неё становится призрачным и не держит игрока;
  • Пресс-давилка — объект press бьёт вниз или вбок по таймеру (покой → удар → пауза → возврат); опасен на всём ходу, хитбокс — динамическое тело; фаза phase рассинхронивает несколько прессов;
  • Чекпоинты — тайл F запоминает точку респавна: после смерти игрок возвращается к последнему взятому флагу (активный флаг подсвечивается зелёным), а R/начало уровня сбрасывают чекпоинт;
  • Секретные зоны — объект secret (?) ставит зону-собирашку: вход засчитывает секрет, а при входе не позже окна time (мс, по умолчанию SECRET_REWARD_MS) — временную награду; находка отмечается ★ в меню, награда — ★!, а в localStorage (supermew.secret.<имя уровня>) хранится лучшее время сбора;
  • Мгновенный респаун — смерть с hit-stop (короткая заморозка кадра + тряска и вспышка камеры), счётчик попыток и почти мгновенный возврат к точке спавна (DEATH_DELAY_MS);
  • Персистентные декали — пятна крови от смертей остаются на уровне между попытками (слой RenderTexture, до DECAL_MAX штампов) и сбрасываются при перезагрузке уровня;
  • Глобальный time-scale — единый масштаб логики и физики (GameScene.timeScale), на котором построены hit-stop и bullet-time смерти;
  • Экран результата — время/лучшее/попытки и «Дальше» с автопереходом на следующий уровень;
  • Фоновая музыка — процедурный чиптюн-луп на Web Audio API (без файлов), вкл/выкл по M;
  • Таймер уровня и лучшее время — сохраняется в localStorage (supermew.best.<имя уровня>).

Внутреннее разрешение и HUD

Canvas рендерится в физических пикселях: backing store равен CSS-размер × devicePixelRatio, а canvas показывается 1:1 (scale.zoom = 1/DPR, Scale.NONE) — без CSS-масштабирования и nearest-downscale, поэтому картинка остаётся резкой при любом DPR, включая дробные 125 %/150 %. Размер буфера и зум пересчитываются на resize/смене DPR через refit() в src/main.ts (буфер = round(innerWidth × DPR) × round(innerHeight × DPR)).

Мир (GameScene) рендерится с целочисленным зумом камеры worldZoomForHeight(bufferHeight) = max(1, round(bufferHeight / 360)) — при буфере 1080px это zoom = 3, при 1440px — zoom = 4. Видимая область мира остаётся 640x360, но пиксель-арт ложится на сетку физических пикселей без полупикселей. Пилы создаются как текстуры diameter × zoom и показываются в мировом размере diameter, то есть на экране попадают в буфер 1:1 (без downscale); текстуры пересоздаются при смене зума (refreshSawTextures).

HUD вынесен в отдельную сцену src/scenes/HudScene.ts с зумом 1, поэтому его координаты — физические пиксели буфера, а верстка пересчитывается под текущий целочисленный зум мира. Крутилки RENDER_SCALE больше нет: итоговое разрешение полностью определяется DPR окна и высотой буфера.

Структура проекта

index.html                 точка входа игры, монтирует Phaser в #game
editor.html                отдельное приложение-редактор уровней
vite.config.ts             конфиг сборки: две страницы (index/editor) + Phaser отдельным чанком
src/
  main.ts                  конфиг Phaser.Game игры, сцены, физика, масштаб; хук __supermew
  editor/main.ts           контроллер редактора: DOM/CSS-UI, инструменты, выделение/свойства, хоткеи, история, API
  editor/render.ts         2D-canvas: сетка, тайлы, объекты, выделение, «призрак», свотчи
  editor/editor.css        тема редактора (CSS, без внешних зависимостей)
  constants.ts             все настраиваемые константы (физика, палитра, ключи текстур)
  levels/                  по одному JSON-уровню (tiles + objects) + реестр/формат/правки
    index.ts               реестр LEVELS, реэкспорт типов/формата/объектов
    types.ts               LevelDef/LevelObject и контракт формата
    objects.ts             геометрия/умолчания объектов (пилы, рельсы, платформы)
    format.ts              валидация, JSON<->LevelDef, миграция legacy-ASCII, экспорт в .ts
    edits.ts               правки встроенных/свои уровни в localStorage, resolveLevels()
    level-NN-*.ts          отдельные встроенные уровни
  textures.ts              процедурная генерация всех текстур (Graphics -> generateTexture)
  sfx.ts                   процедурный звук + чиптюн-луп на Web Audio API (синглтон sfx)
  fx.ts                    системы частиц: кровь, пыль, прыжковый puff
  progress.ts              прогресс в localStorage: прохождение, лучшее время, смерти, ghost, таблица лидеров
  entities/Player.ts       контроллер персонажа (coyote/buffer/wall-jump)
  scenes/BootScene.ts      создаёт текстуры и запускает MenuScene
  scenes/MenuScene.ts      выбор уровня (resolveLevels), прогресс
  scenes/GameScene.ts      сборка уровня (индекс или customLevel), коллизии, смерть/респаун, таймер
  scenes/HudScene.ts       отдельная сцена HUD с зумом 1 (поверх игрового мира)
  scenes/PauseScene.ts     меню паузы (зум 1), открывается по Esc, ставит GameScene на паузу
  scenes/ResultScene.ts    экран результата (зум 1): время/лучшее/попытки, «Дальше», автопереход
  scenes/LeaderboardScene.ts таблица лидеров (зум 1): забеги уровня, Tab/←→, экспорт JSON (E)

Настройка

Все игровые параметры собраны в src/constants.ts: гравитация, скорости, ускорения, сила прыжка и wall-jump, coyote/buffer-тайминги, палитра и ключи текстур. Меняйте значения там — остальной код импортирует имена из этого файла.

Как добавить уровень

Каждый встроенный уровень — отдельный файл src/levels/level-NN-*.ts, экспортирующий LevelDef ({ name, tiles, objects, palette? }); реестр src/levels/index.ts собирает их в LEVELS, а resolveLevels() (см. src/levels/edits.ts) накладывает правки редактора и добавляет свои уровни. Добавьте новый файл и подключите его в реестре — или нарисуйте уровень в редакторе и нажмите кнопку «В .ts», чтобы получить готовый модуль для этой папки.

tiles — статичные тайлы (строки одинаковой длины):

Символ Значение
# сплошной блок
= односторонняя платформа (проходима снизу, стоит только сверху)
T пружина: подбрасывает игрока вверх при касании
B разрушающийся блок: остаётся твёрдым, пока по нему идут, и рушится с задержкой после касания; восстанавливается после смерти
i тонкий лёд: твёрдая плита, крошится через ICE_BREAK_MS после приземления сверху (в отличие от B — боковое/нижнее касание не срабатывает); трещины нарастают перед обвалом, восстанавливается после смерти
P исчезающий блок: мигает по циклу и твёрдый только в активной «фазе»
F флаг-чекпоинт: промежуточный респавн; порядок — слева-вправо, сверху-вниз; после смерти возвращает на последний взятый флаг
S точка спавна (ровно одна)
G цель-триггер (ровно одна)
^ v < > шипы вверх / вниз / влево / вправо
пусто

objects — параметризованные сущности:

Объект Поля Значение
saw x, y, size вращающаяся пила: x/y — центр диска в клетках (может быть дробным), size — диаметр в тайлах (по умолчанию 1)
rail x, y, length, size пила по рельсу: рельс от клетки x вправо на length клеток, по нему ездит пила диаметром size тайлов
mover x, y, length, axis движущаяся односторонняя платформа длиной length клеток; axis — 'x' или 'y' (ось колебания)
press x, y, length, axis, dir, phase пресс-давилка: исходный габарит от x/y на length клеток; axis — ось удара ('y' вниз/вверх, 'x' вбок), dir — 1/-1 направление, phase (0..1) — сдвиг фазы; периодически бьёт и опасен
secret x, y, time? секретная зона (объект ?): x/y — клетка; вход засчитывает секрет, time (мс, по умолчанию SECRET_REWARD_MS) — окно от старта уровня для временной награды

palette — необязательная тема оформления из PALETTES (src/constants.ts): default, forest, ice, sawmill, hell, void. Задаёт фон и цвета тайлов, платформ и шипов; отсутствие поля равнозначно default. Выбирается в редакторе («Свойства карты»).

tiles должна быть прямоугольной, замкнутой по периметру # и иметь сплошной пол. Обновляйте ASCII-комментарий над уровнем при изменении геометрии.

Примечание

Внешних файлов изображений и звуков нет: текстуры рисуются в src/textures.ts, звуки синтезируются в src/sfx.ts через Web Audio API.