Полный рефакторинг проекта в стабильную расширяемую базу рогалика. Почему: после разнесения single-file на модули потерялся вызов генерации карты (RoomMap не генерировал комнаты) → игра падала на старте; баг скрывался тем, что Bun-бандлер не проверяет типы. Архитектура: - Логика игры (src/core/) полностью отделена от рендера: без three.js и DOM, тестируется без браузера. - Рендер мира на three.js с ортокамерой (2D-вид); HUD/миникарта — 2D-канвас поверх. - Фиксированный игровой цикл 60 Гц + интерполяция (раньше скорость зависела от частоты кадров). - Seeded-RNG, ввод через абстрактные «намерения» (InputState). Возможности: - Стартовое меню с выбором уровня (Esc → меню). - Конфигуратор уровней: LevelRules + 5 пресетов (размер карты, плотность/сила врагов, HP, фиксированный seed). - Тема внешнего вида (render/theme.ts) — задел под кастомные ассеты. Качество: - 27 юнит-тестов ядра (генерация, симметрия дверей, коллизии, спавн, правила). - Два круга adversarial-ревью; исправлено 6 реальных багов (кнокбэк сквозь стены → софт-лок; незакрываемая сокровищница; фикс-сид после рестарта; перенос ввода между забегами; нет source maps; неточности в доках). - Документация: README, docs/ARCHITECTURE.md, CLAUDE.md, docs/HOWTO.md. - dist/ исключён из гита; bun.lock зафиксирован. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.7 KiB
7.7 KiB
CLAUDE.md — как работать с этим проектом
Инструкции для ИИ-агентов и разработчика. Прочитай целиком перед правками. Цель проекта — держать рабочую, расширяемую базу рогалика. Не ломать то, что работает; добавлять — по правилам ниже.
Что это
Top-down рогалик (в духе Binding of Isaac). Логика — чистый TypeScript
(src/core/), рендер — three.js с ортокамерой (src/render/), сборка/тесты — Bun.
Обзор — README.md, детали — docs/ARCHITECTURE.md,
рецепты доработки — docs/HOWTO.md.
🔑 Золотые правила (нарушать = ломать архитектуру)
- Ядро без рендера и DOM. В
src/core/**НЕЛЬЗЯ импортироватьthree, обращаться кwindow/document/canvas. Логика общается с миром только черезInputState(вход) и публичные поляGame(для чтения рендером). - Рендер ничего не меняет в игре.
src/render/**только ЧИТАЕТGameи рисует. Любая мутация состояния из рендера — баг. - Случайность только через
Rng. НикакихMath.random()вcore/. Это сохраняет детерминизм (тесты, отладка по seed). - Время — в «шагах» (1/60 c), а не в кадрах. Скорости — «пиксели/шаг», перезарядки — «шаги». Не двигай ничего в коде рендера или прямо в rAF.
- Числа — не в коде. Константы движка (размеры, геометрия дверей, базовый
баланс) — в
src/config.ts; параметры конкретного забега (размер карты, сила врагов, HP игрока, seed) — в правилах уровняsrc/core/rules.ts. Не раскидывай «магические числа» по логике. - Освобождай ресурсы three.js. Создаёшь геометрию/материал на сущность —
обеспечь
dispose()при её удалении (см.sync*/sweepвThreeRenderer). - Комментарии и текст для игрока — по-русски, как в существующем коде.
Команды
bun run dev # дев-сервер + watch → http://localhost:3000
bun run build # прод-сборка в dist/
bun run typecheck # tsc --noEmit (строгий) — НЕ ловится при bun build!
bun test # юнит-тесты ядра
bun run check # typecheck + test
⚠️
bun buildне проверяет типы. Поэтомуbun run typecheckобязателен — именно отсутствие тайп-чека когда-то скрыло рантайм-регрессию.
Definition of Done (для любой правки)
bun run checkзелёный (типы + тесты).- Если менял логику — добавил/обновил тест в
tests/. - Если менял геймплей/рендер — проверил в браузере (
bun run dev, открыть страницу, увидеть, что играется, в консоли нет ошибок). Юнит-тесты не видят рендер — визуальную проверку не пропускать. - Обновил доки, если поменялось поведение или структура.
Где что лежит (карта для навигации)
| Хочешь поменять… | Иди в… |
|---|---|
| константы движка (размер тайла/комнаты, геометрия дверей, базовый баланс) | src/config.ts |
| правила уровня / пресеты в меню (размер карты, сила врагов, HP, seed) | src/core/rules.ts |
| поведение за один шаг (движение, атака, ИИ, переходы) | src/core/Game.ts |
| данные сущности | src/core/entities/* |
| генерацию карты | src/core/world/RoomMap.ts |
| форму комнаты/тайлы | src/core/world/tiles.ts, Room.ts |
| коллизии | src/core/systems/collision.ts |
| расстановку врагов | src/core/systems/spawner.ts |
| как рисуется мир | src/render/ThreeRenderer.ts |
| цвета/внешний вид/ассеты мира | src/render/theme.ts |
| HUD/миникарту | src/render/HudOverlay.ts |
| стартовое меню | src/ui/StartMenu.ts |
| раскладку клавиш | src/input/KeyboardController.ts |
| тайминг/цикл, поток меню↔игра | src/engine/GameLoop.ts, src/main.ts |
Пошаговые рецепты («добавить врага», «новое оружие», «тип комнаты», «сменить
рендер») — в docs/HOWTO.md.
Грабли, на которые уже наступали (не повторять)
- Пустая карта.
new RoomMap(rng)ДОЛЖЕН генерировать карту в конструкторе. Если карта пустая —curRoomбудетundefinedи всё упадёт на старте. OPPнаправлений. Противоположное кup— этоdown, кleft—right. Любая другая раскладка ломает встречные двери и связность карты.- Чёрный экран при работающем рендере. Ортокамера инвертирует Y → нужен
DoubleSideна материалах, иначе грани отсекаются. - Canvas вылезает за рамки. Холстам нужен CSS-размер (
width/height:100%), иначе они показываются в размер HiDPI-буфера. - Комната без врагов не открывается. Если в комнате 0 врагов (сокровищница) —
она должна стать
clearedсразу при входе, иначе двери не появятся.
Стиль кода
- TypeScript strict, без
any(кроме узких мест вродеwindow as …вmain.ts). - Маленькие чистые функции для логики; классы — для сущностей/состояния.
- Имена и комментарии осмысленные, по-русски. Комментарий объясняет «почему», а не «что».
- Перед коммитом —
bun run check.
Чего НЕ делать без явной просьбы
- Не добавлять тяжёлые зависимости (физдвижки, фреймворки). База намеренно лёгкая.
- Не переписывать архитектуру «ядро ↔ рендер».
- Не коммитить
dist/иnode_modules/(см..gitignore). - Не превращать игру в полноценное 3D, пока этого не попросили (рендер для этого готов — он изолирован, — но это отдельная большая задача).