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 npm run maps # перегенерация карт-файлов (tools/maps + encodeMap) node tools/smoke.mjs # смоук-тест в реальном Chromium (скриншот + консоль) node tools/smoke-ponds.mjs # смоук перехода луга -> Серые пруды node tools/smoke-quest.mjs # смоук диалога с Ирвином и сумки/журнала
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: контент, сцены, геймплейные системы.packages/engine/src/core, scene, input, render). Pixi используется как низкоуровневый слой отрисовки спрайтов.Game.VIRTUAL_W/H); CSS-размер канваса — виртуальное × целое (computeScale в @rpg/engine, движок сам слушает window.resize), а бэкинг-стор — в пикселях устройства (scale × dpr): текст/UI резкие, мир пиксельный (текстуры AssetLoader сэмплируются nearest). roundPixels: true, antialias: false. При изменении разрешения следи, чтобы CSS-масштаб оставался целым.GameLoop (60 Гц update) + аккумулятор с ограничением 5 шагов/кадр; InputManager.endTick() очищает «just pressed» в конце каждого тика — не вызывать update вне цикла движка.worldToScreen/screenToWorld/worldDist/worldNorm в math/iso.ts. Две «линейки»: точка проецируется анизотропно (tileW/2 px по X, tileH/2 по Y), скаляры (дистанция, радиус, скорость, высота) — через unitsToPx (px = units·tileW, 32 px/юнит). Округление до целого px — только на границе мир→экран. Тайловые координаты всюду именуются {x, y} (как Grid в pathfinding); для них есть мосты tileToWorld/worldToTile.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/demo.md — внутренняя проектная дока среза: матрица «подсистема движка → где показана в игре» + статус, боевая модель, архитектура, дорожная карта.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-тайл).Assets.load без Assets.init() виснет навсегда (без ошибок!) — AssetLoader делает init сам, не обходи его.app.renderer недоступен до завершения app.init() — Renderer.setup() ждёт init-промис; не трогай renderer раньше.map.worldBounds ({0, 0, w, h}), клэмп через проекцию сам учитывает отрицательный угол.@rpg/engine — только regex ({ find: /^@rpg\/engine$/, replacement: ... }). Строковый алиас перехватывает и под-пути (@rpg/engine/assets/...), ломая их в index.ts/....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().SceneManager поднимает его наверх uiRoot в begin() — сцены добавляют свои view позже, и «наивный» оверлей из Engine.init() оказывается под ними (карты просвечивают сквозь меню).delay: часть dt, приходящаяся на delay, вычитается из движения — не упрощай тик до последовательных прибавлений.--virtual-time-budget зависают на загрузке картинок (колбэки декодинга не срабатывают) — для визуальных проверок использовать tools/smoke.mjs (puppeteer-core + системный Chromium, реальное время).ctx.resume() без флага --autoplay-policy=no-user-gesture-required не резолвится — смоук-скрипты обязаны передавать этот флаг, а навигация не должна ждать .then у audio.play.bindActions дополняет, а не перезаписывает): Space = advance в диалоге и attack вне его — не развести обратно нельзя, гардится уровнем сцены.экран = мир + worldRoot.position (перевод в CSS — домножением на scale). Цепочка клика по миру: pointer px − worldRoot.position → screenToWorld → юниты → worldToTile. Не смешивай пространства: позиция героя/врагов — юниты, вью — px.worldToScreen(delta)), не по мировым: экранные оси — мировые диагонали, кадры down/up/side соответствуют экранному направлению.docs/world.md и docs/art-style.md: тёплые цвета — только жизнь; палитра — только из tools/pixelart/palette.mjs.Graphics-ом в коде (пока нет арта); настоящий пиксель-арт в будущем кладётся в apps/game/assets/.