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
+27
View File
@@ -0,0 +1,27 @@
import { T_WALL, T_FLOOR, T_DOOR, COLS, ROWS, DOOR } from '../../config';
import type { Doors } from '../types';
/**
* Строит свежую сетку тайлов комнаты: по краям стены, внутри пол.
* Если передан doorState — в стенах прорезаются дверные проёмы.
*/
export function buildTiles(doorState?: Doors): number[][] {
const tiles: number[][] = [];
for (let r = 0; r < ROWS; r++) {
tiles[r] = [];
for (let c = 0; c < COLS; c++) {
const isEdge = r === 0 || r === ROWS - 1 || c === 0 || c === COLS - 1;
tiles[r][c] = isEdge ? T_WALL : T_FLOOR;
}
}
if (doorState) placeDoors(tiles, doorState);
return tiles;
}
/** Помечает дверные тайлы на готовой сетке согласно набору открытых дверей. */
export function placeDoors(tiles: number[][], doors: Doors): void {
if (doors.up) for (const c of DOOR.up.cols) tiles[DOOR.up.row][c] = T_DOOR;
if (doors.down) for (const c of DOOR.down.cols) tiles[DOOR.down.row][c] = T_DOOR;
if (doors.left) for (const r of DOOR.left.rows) tiles[r][DOOR.left.col] = T_DOOR;
if (doors.right) for (const r of DOOR.right.rows) tiles[r][DOOR.right.col] = T_DOOR;
}