Files
Binding-Fignyaac/docs/HOWTO.md
T
mayatnikovandClaude Opus 4.8 0684368ac7 refactor: рабочая база на three.js + меню/правила уровней + доки
Полный рефакторинг проекта в стабильную расширяемую базу рогалика.

Почему: после разнесения 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>
2026-06-18 14:07:06 +03:00

9.2 KiB
Raw Blame History

HOWTO — рецепты доработки

Практические инструкции «как сделать X». Каждый рецепт перечисляет все файлы, которые нужно тронуть. После любой правки — bun run check и проверка в браузере (bun run dev). Общие правила — в CLAUDE.md.


1. Покрутить баланс (скорость, HP, урон, число врагов)

Всё в одном файле — src/config.ts. Например:

  • игрок быстрее: PLAYER.speed;
  • больше HP у босса: ENEMY_STATS.boss.hp;
  • больше врагов в комнате: SPAWN.normalMin / SPAWN.normalExtra;
  • крупнее данжен: MIN_ROOMS / EXTRA_ROOMS / MAP_RADIUS.

Код менять не нужно. Это и есть смысл config.ts.


2. Добавить новый тип врага (например, tank)

  1. src/core/types.ts — добавь в union: export type EnemyType = 'normal' | 'fast' | 'boss' | 'tank';
  2. src/config.ts — строка характеристик в ENEMY_STATS:
    tank: { size: 40, hp: 8, speed: 0.7, damage: 2 },
    
  3. src/core/systems/spawner.ts — реши, когда он спавнится (логика выбора типа в начале цикла). Напр. с шансом: rng.chance(0.15) ? 'tank' : rng.chance(ENEMY.fastChance) ? 'fast' : 'normal'.
  4. src/render/ThreeRenderer.ts — цвет в COLOR и выбор цвета/геометрии в syncEnemies (квадрат unitPlane или круг unitCircle).

ИИ, урон, отбрасывание, мигание при попадании — общие, их трогать не нужно. Добавь тест в tests/spawner.test.ts, если ввёл особое правило спавна.


3. Добавить новое поведение врага (особый ИИ)

Сейчас все враги ведут себя одинаково (преследование игрока) в Game.updateEnemies (src/core/Game.ts). Чтобы развести поведение:

  • по e.type внутри updateEnemies развилкой (просто, для 2–3 типов), или
  • вынеси ИИ в src/core/systems/ai.ts как функции update<Type>(enemy, player, room) и диспетчеризуй по типу (чище, когда типов много).

Держи это в core/ (без рендера) и старайся писать чистыми функциями — их легко покрыть тестом.


4. Добавить оружие / третий режим боя

Режимы — это MODE_RANGED = 0 / MODE_MELEE = 1 и тип CombatMode = 0 | 1. Для третьего режима:

  1. src/config.ts — константа MODE_X = 2 и блок баланса.
  2. src/core/types.ts — расширь CombatMode (0 | 1 | 2).
  3. src/core/Game.ts — в handleAttack добавь ветку создания нужного снаряда/ эффекта; в consumeActions смена оружия циклом по всем режимам.
  4. src/render/HudOverlay.ts — подпись/иконка режима.
  5. Если это новый вид снаряда — заведи сущность в src/core/entities/ и обновляй её в Game.step (по образцу Projectile/updateTears), а в ThreeRenderer добавь её отрисовку.

5. Добавить тип комнаты (например, shop)

  1. src/core/types.ts — добавь в RoomType.
  2. src/core/world/RoomMap.ts — правило назначения типа в generate().
  3. src/core/systems/spawner.ts — сколько врагов (часто 0).
  4. src/core/Game.ts — если врагов 0, комната уже авто-зачищается при входе (см. enterRoom); особая логика комнаты — здесь же.
  5. src/render/HudOverlay.ts — цвет на миникарте и подпись (drawMinimap/drawHud).

6. Поменять управление

src/input/KeyboardController.ts — метод poll() (маппинг клавиш на InputState) и набор PREVENT (клавиши, у которых гасим поведение браузера). Логику игры это не затрагивает — она читает только InputState.

Геймпад/тач: сделай новый контроллер с тем же poll(): InputState и подставь его в main.ts.


7. Добавить уровень (пресет правил) в меню

src/core/rules.ts — добавь объект в массив PRESETS. Он сразу появится кнопкой в стартовом меню (меню строится из PRESETS). Например:

{
  id: 'swarm',
  name: 'Орда',
  description: 'Очень много слабых быстрых врагов.',
  map: { minRooms: 10, extraRooms: 4, mapRadius: 4 },
  player: { maxHp: 6, speed: 3.2 },
  enemies: { densityMul: 2.5, fastChance: 0.9, hpMul: 0.5, speedMul: 1.2, bossHpMul: 1 },
},
  • seed (необязательный) фиксирует данжен — одинаковый забег каждый раз.
  • Нужен новый «рычаг» (например, шанс сокровищниц)? Добавь поле в LevelRules и читай его там, где раньше брал константу из config.ts (генерация — RoomMap, спавн — spawner). Тест на влияние правил — в tests/game.test.ts.

8. Кастомные ассеты (своя тема / текстуры)

Сейчас вид мира — это цвета в src/render/theme.ts (Theme + DEFAULT_THEME).

  • Своя палитра: сделай ещё один объект Theme и передай его в new ThreeRenderer(canvas, myTheme) в main.ts. Сейчас рендер создаётся один раз при старте с темой по умолчанию и переиспользуется между забегами, поэтому чтобы выбирать тему по правилам уровня, нужно либо перенести new ThreeRenderer(...) внутрь startGame(rules)dispose() предыдущий), либо добавить рендеру метод setTheme(theme).
  • Перейти на спрайты/текстуры: расширь Theme полями с путями к картинкам (см. комментарий-задел в theme.ts), загрузи их через THREE.TextureLoader в ThreeRenderer и положи текстуру в material.map соответствующих мешей вместо (или вместе с) color. Логика игры при этом НЕ меняется — это чисто рендер.

9. Поменять или нарастить рендер (вплоть до 3D)

Рендер изолирован за интерфейсом src/render/Renderer.ts (render(game, alpha)

  • dispose()). Варианты:
  • Доработать вид (спрайты, текстуры, частицы) — внутри ThreeRenderer.
  • Сделать 3D — поменяй OrthographicCamera на PerspectiveCamera, добавь свет и 3D-меши. Мир рисуется по тем же координатам сущностей из Game — логику менять не нужно.
  • Другой рендер целиком (например, Canvas2D для отладки) — реализуй Renderer и подставь в main.ts. Ядро не трогается вообще.

Помни про управление ресурсами (правило 6 в CLAUDE.md).


10. Отладка

  • В консоли браузера доступен game — текущий Game. Примеры:
    game.player.hp = 99            // бессмертие на тест
    game.curRoom.enemies.length    // сколько врагов в комнате
    game.reset()                   // новая карта
    
  • Детерминизм: чтобы воспроизвести конкретную карту, задай seed правилам — проще всего поставить seed: 42 нужному пресету в core/rules.ts (см. пресет daily). Тот же seed — та же генерация и спавн, в т.ч. после рестарта. В тестах можно передать свой ГПСЧ вторым аргументом: new Game(rules, new Rng(42)) (первый аргумент — правила, не ГПСЧ).
  • Тесты ядра гоняются мгновенно и без браузера: bun test. Логику отлаживай тестом, а не кликами.