Newer
Older
rpg / CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Язык общения

Всегда общайся с пользователем на русском.

Команды

npm install                          # установка зависимостей (npm workspaces)
npm run dev                          # Vite dev-сервер с HMR (apps/game)
npm run build                        # прод-сборка игры
npm test                             # все тесты (Vitest)
npx vitest run packages/engine/src/map/__tests__/pathfinding.test.ts  # один файл тестов
npm run typecheck                    # tsc --noEmit для обоих пакетов
npm run art                          # перегенерация пиксель-арта из tools/pixelart
node tools/smoke.mjs                 # смоук-тест в реальном Chromium (скриншот + консоль)

Dev-сервер по умолчанию на http://localhost:5173; для отдельного порта: npm run dev -- --port 5199.

Архитектура

Monorepo из двух workspace-пакетов с жёсткой границей API:

  • packages/engine (@rpg/engine) — жанронезависимый движок-библиотека. Не должен знать ничего о RPG-контенте (квесты, предметы, сюжет). Вся игра импортирует движок только через packages/engine/src/index.ts — другие внутренние пути движка импортировать нельзя.
  • apps/game (@rpg/game) — сама RPG: контент, сцены, геймплейные системы.

Ключевые решения, которые нельзя сломать

  1. PixiJS — только рендер-бэкенд. Игровой цикл, сцены, ввод, камера, изометрия — собственные (packages/engine/src/core, scene, input, render). Pixi используется как низкоуровневый слой отрисовки спрайтов.
  2. Пиксель-арт pixel-perfect. Рендер в виртуальном разрешении 480×270 (Game.VIRTUAL_W/H), канвас растягивается целым числом (computeScale в @rpg/engine, движок сам слушает window.resize) с image-rendering: pixelated, roundPixels: true, antialias: false. При изменении разрешения следи, чтобы масштаб всегда оставался целым.
  3. Фиксированный шаг. GameLoop (60 Гц update) + аккумулятор с ограничением 5 шагов/кадр; InputManager.endTick() очищает «just pressed» в конце каждого тика — не вызывать update вне цикла движка.
  4. Изометрия 2:1. Конверсии isoToScreen/screenToIsoExact в math/iso.ts — источник истины для позиций. Тайловые координаты всюду именуются {x, y} (как Grid в pathfinding), экранные тоже {x, y}.
  5. Собственные классы движка с инъекцией хранилища. SaveManager принимает StorageLike (в браузере — localStorage), чтобы тесты работали без DOM. Чистую математику (iso, A*, ECS) держи без зависимостей от Pixi — она тестируется в Vitest без браузера.

Где что лежит

  • docs/engine/документация движка (по-русски): README.md (архитектура и принципы), getting-started.md, core.md, render.md, input.md, maps.md, ui-and-dialogue.md, assets-audio-save.md, art-pipeline.md, recipes.md. При изменении API движка обновляй соответствующий файл.
  • docs/world.md — библия мира (сеттинг, локации, персонажи, сюжет). Любой новый контент сверять с ней.
  • docs/art-style.md — арт-библия: палитра (32 цвета), размеры спрайтов, правила стиля, чеклист ассетов.
  • packages/engine/src/ — модули движка: core/ (Engine, GameLoop, Tween, GameState, StateMachine, Settings, EventBus), scene/ (SceneManager + fade-переходы), render/ (Renderer, Camera, IsoDepthLayer, Particles, scale), input/ (действия, геймпад, VirtualJoystick), map/ (изометрия, A*, mapFormat, Tiled-импорт), ui/ (PixelText/VT323, Panel, Button, MenuList, DialogueBox), dialogue/ (DialogueRunner — view-агностик), audio/ (шины master/music/sfx), assets/ (AssetLoader + атласы), save/, math/ (iso, rng, easing).
  • apps/game/src/data/ — весь контент: карта (map.ts), диалоги (dialogues.ts — графы DialogueGraph), NPC (npcs.ts).
  • apps/game/src/scenes/ — BootScene (грузит ассеты и шрифт) → MenuScene (MenuList) → LocationScene; сцены меняются через SceneManager.replace/push/pop (опционально с fade).
  • apps/game/src/systems/ — геймплейные механики: движение героя (A* + плавный путь + анимация из атласа), диалоги (обёртка над DialogueRunner + DialogueBox).
  • Флаги/переменные сюжета — в GameState (game.state), сериализуются в автосейв autosave (Esc в локации); настройки — game.settings (отдельный слот, не в сейвах).

Пиксель-арт

  • tools/pixelart/gen.mjs — генератор ассетов (PNG из кода, без зависимостей): node tools/pixelart/gen.mjs.
  • Выход — apps/game/assets/ (tiles/*.png, chars/*.png); загружается AssetLoader в BootScene, ключ = путь без расширения.
  • Атласы (кадры героя): gen.mjs собирает chars/hero_sheet.png + hero_sheet.json (Pixi Spritesheet); загрузка — assets.loadAtlas('chars/hero_sheet.json'), кадры — assets.frames(key, prefix).
  • Палитра — только из tools/pixelart/palette.mjs (она же в docs/art-style.md); тёплые цвета (B/F) — только «жизнь»: цветы, бронза, огонь.
  • Имена файлов и размеры спрайтов фиксированы арт-библией — при замене сгенерированного арта нарисованным руками их не менять.
  • Тайл «дерево» — высокий объект: TileMapData.tall c TallSpec { height, ground } (спрайт якорится низом в центр ромба, под ним рисуется ground-тайл).

Известные грабли (уже собранные)

  • Pixi Assets.load без Assets.init() виснет навсегда (без ошибок!) — AssetLoader делает init сам, не обходи его.
  • app.renderer недоступен до завершения app.init()Renderer.setup() ждёт init-промис; не трогай renderer раньше.
  • Изометрический «ромб» уходит в минус по X (западный угол карты при x≈-448) — границы камеры задаются как CameraBounds { x, y, width, height } от западного угла, а не от (0,0). Координаты тайлов/героя/NPC — единое пространство мировых пикселей, ничего дополнительно не сдвигать.
  • Vite-алиас @rpg/engine — только regex ({ find: /^@rpg\/engine$/, replacement: ... }). Строковый алиас перехватывает и под-пути (@rpg/engine/assets/...), ломая их в index.ts/....
  • Игровые PNG должны попадать в dist: в apps/game/vite.config.ts стоит publicDir: 'assets', а resolveUrl не добавляет префикс assets/. Без этого прод-сборка пустая.
  • Шрифт движка подключается ?url-импортом (import fontUrl from '@rpg/engine/assets/fonts/VT323-Regular.ttf?url') — для этого в exports движка есть "./assets/*". Обычный new URL(...) вне корня Vite не обрабатывает.
  • Атласы грузятся только через assets.loadAtlas('<ключ>.json') — ключ с .json резолвится как есть, без .png. Assets.load на JSON вернёт Texture без .textures — не перепутай с texture().
  • Fade-оверлей переходов должен быть поверх UI: SceneManager поднимает его наверх uiRoot в begin() — сцены добавляют свои view позже, и «наивный» оверлей из Engine.init() оказывается под ними (карты просвечивают сквозь меню).
  • TweenManager.tick учитывает пересечение границы delay: часть dt, приходящаяся на delay, вычитается из движения — не упрощай тик до последовательных прибавлений.
  • Headless-скриншоты с --virtual-time-budget зависают на загрузке картинок (колбэки декодинга не срабатывают) — для визуальных проверок использовать tools/smoke.mjs (puppeteer-core + системный Chromium, реальное время).
  • Мир/арт сверять с docs/world.md и docs/art-style.md: тёплые цвета — только жизнь; палитра — только из tools/pixelart/palette.mjs.

Рабочие привычки

  • Для задач из 3+ шагов веди список задач (todowrite/TaskCreate) и иди по нему автономно, не спрашивая подтверждения на каждый шаг.
  • Подражай существующему стилю кода; комментарии и докстринги — по-русски; хелперы держи до ~70 строк.
  • Плейсхолдеры графики рисуются Graphics-ом в коде (пока нет арта); настоящий пиксель-арт в будущем кладётся в apps/game/assets/.