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>
This commit is contained in:
2026-06-18 14:07:06 +03:00
co-authored by Claude Opus 4.8
parent 46dc9f2fad
commit 0684368ac7
60 changed files with 2759 additions and 3364 deletions
+142
View File
@@ -0,0 +1,142 @@
/**
* config.ts — ВСЕ настройки игры в одном месте.
*
* Здесь сосредоточены: размеры мира, геометрия комнат/дверей и баланс
* (скорости, здоровье, перезарядки). Меняй числа тут, чтобы «крутить»
* игру — больше ничего трогать не нужно.
*
* ВАЖНО: единица времени — один «шаг» симуляции (= 1/60 секунды), а НЕ
* кадр браузера. Цикл игры (см. engine/GameLoop.ts) гарантирует ровно
* 60 шагов в секунду на любом мониторе, поэтому скорости заданы «пикселей
* за шаг», а перезарядки — «в шагах».
*/
// ─────────────────────────────────────────────────────────────
// Холст и сетка
// ─────────────────────────────────────────────────────────────
export const CW = 880; // ширина области отрисовки (px)
export const CH = 660; // высота области отрисовки (px)
export const TILE = 44; // размер одного тайла (px)
export const COLS = 15; // тайлов по горизонтали в комнате
export const ROWS = 11; // тайлов по вертикали в комнате
export const RW = COLS * TILE; // ширина комнаты в пикселях
export const RH = ROWS * TILE; // высота комнаты в пикселях
export const OX = (CW - RW) / 2; // отступ комнаты слева
export const OY = 80; // отступ комнаты сверху (место под HUD)
// Частота симуляции. Логика всегда обновляется с этим шагом.
export const FIXED_FPS = 60;
export const FIXED_DT = 1 / FIXED_FPS; // секунд на шаг
// ─────────────────────────────────────────────────────────────
// Типы тайлов
// ─────────────────────────────────────────────────────────────
export const T_WALL = 0;
export const T_FLOOR = 1;
export const T_DOOR = 2;
// ─────────────────────────────────────────────────────────────
// Режимы боя
// ─────────────────────────────────────────────────────────────
export const MODE_RANGED = 0;
export const MODE_MELEE = 1;
// ─────────────────────────────────────────────────────────────
// Направления
// ─────────────────────────────────────────────────────────────
import type { Dir } from './core/types';
/** Единичный вектор смещения для каждого направления (x вправо, y вниз). */
export const DIR: Record<Dir, readonly [number, number]> = {
up: [0, -1],
down: [0, 1],
left: [-1, 0],
right: [1, 0],
};
/** Противоположное направление. Используется при простановке дверей соседей. */
export const OPP: Record<Dir, Dir> = {
up: 'down',
down: 'up',
left: 'right',
right: 'left',
};
/**
* Геометрия дверей: проём шириной в 3 тайла по центру каждой стороны.
* cx/cy — координата тайла-центра двери (для спавна игрока и расчётов).
*/
export const DOOR = {
up: { cols: [6, 7, 8] as number[], row: 0, cx: 7, cy: 0 },
down: { cols: [6, 7, 8] as number[], row: ROWS - 1, cx: 7, cy: ROWS - 1 },
left: { col: 0, rows: [4, 5, 6] as number[], cx: 0, cy: 5 },
right: { col: COLS - 1, rows: [4, 5, 6] as number[], cx: COLS - 1, cy: 5 },
};
// ─────────────────────────────────────────────────────────────
// Генерация карты (случайное блуждание)
// ─────────────────────────────────────────────────────────────
export const MAP_RADIUS = 3; // карта вмещается в сетку (2*R+1)²
export const MIN_ROOMS = 8; // минимум комнат
export const EXTRA_ROOMS = 4; // + случайно до этого числа
// ─────────────────────────────────────────────────────────────
// Баланс: игрок
// ─────────────────────────────────────────────────────────────
export const PLAYER = {
size: 26,
speed: 3.2, // px за шаг
maxHp: 6,
invFrames: 60, // неуязвимость после удара, в шагах
rangedCooldown: 10, // перезарядка выстрела, в шагах
meleeCooldown: 22, // перезарядка удара ближнего боя, в шагах
transitionLock: 15, // блок повторного перехода между комнатами, в шагах
};
// ─────────────────────────────────────────────────────────────
// Баланс: снаряд (слеза) и ближний бой
// ─────────────────────────────────────────────────────────────
export const PROJECTILE = {
radius: 5,
speed: 7, // px за шаг
damage: 1,
life: 80, // время жизни в шагах
};
export const MELEE = {
reach: 22, // отступ хитбокса от центра игрока
size: 50, // сторона квадратного хитбокса
life: 10, // длительность взмаха в шагах
damage: 2,
knockback: 10,
};
// ─────────────────────────────────────────────────────────────
// Баланс: враги (таблица характеристик по типу)
// ─────────────────────────────────────────────────────────────
export const ENEMY_STATS = {
normal: { size: 32, hp: 3, speed: 1.15, damage: 1 },
fast: { size: 26, hp: 2, speed: 1.9, damage: 1 },
boss: { size: 46, hp: 10, speed: 0.9, damage: 2 },
} as const;
export const ENEMY = {
aggroRange: 500, // дистанция, с которой враг начинает преследование
attackCooldown: 30, // пауза между контактными ударами, в шагах
hitFlash: 8, // длительность «мигания» при попадании, в шагах
knockbackDecay: 0.85,
fastChance: 0.3, // доля быстрых врагов в обычной комнате
};
// ─────────────────────────────────────────────────────────────
// Спавн врагов
// ─────────────────────────────────────────────────────────────
export const SPAWN = {
normalMin: 2, // минимум врагов в обычной комнате
normalExtra: 2, // + случайно до этого числа
minDistFromDoor: 180, // не спавнить ближе к двери входа
minDistFromPlayer: 150,
minDistBetween: 60,
treasureChance: 0.12, // шанс комнаты-сокровищницы
bossChance: 0.2, // шанс назначить комнату боссом
};