# Battlefield Generation — History

Последовательность изменений по системе генерации боевого поля в стиле Heroes 3 (по мотивам VCMI).

---

## 1. Первичная архитектура

**Запрос:** сделать независимый алгоритм генерации (seed + terrain → config), компонент-разворачиватель конфигурации на сцене, редакторский плагин для случайной генерации, ScriptableObject для препятствий. Ориентир — репозиторий VCMI.

**Результат:**
- `Assets/Scripts/Battle/TerrainType.cs` — enum террейнов.
- `Assets/Scripts/Battle/BattlefieldConfig.cs` — чистая data-структура, без Unity-зависимостей.
- `Assets/Scripts/Battle/BattlefieldGenerator.cs` — `static Generate(int seed, TerrainType, ObstacleDatabase)`, `System.Random`, детерминированный.
- `Assets/Scripts/Battle/ObstacleDefinition.cs` — ScriptableObject препятствия (sprite, allowedTerrains, blockedHexes, spriteOffset, weight).
- `Assets/Scripts/Battle/ObstacleDatabase.cs` — ScriptableObject c obstacles + backgrounds per terrain + параметры генерации.
- `Assets/Scripts/Battle/BattlefieldView.cs` — MonoBehaviour-деплой конфигурации на сцену.
- `Assets/Scripts/Battle/Editor/BattlefieldEditorWindow.cs` — окно *Tools → Heroes 3 → Battlefield Generator* с Seed / Terrain / Generate / Clear.
- `asmdef`-файлы для разделения runtime и editor.

## 2. Материал BattleObstacle

**Запрос:** использовать материал `BattleObstacle.mat` для всех препятствий.

**Изменения:**
- На `BattlefieldView` добавлено поле `Material obstacleMaterial`, назначается каждому обстаклу через `sharedMaterial`.
- В `BattlefieldEditorWindow` добавлено поле *Obstacle Material*, авто-подтягивает ассет по GUID `436e6b82d1d434848a2ec2990da08781`, значение сохраняется в `EditorPrefs`.

## 3. Sorting Order

**Запрос:** фон — 0, гексы — 1, препятствия — 2.

**Изменения:** удалён per-obstacle Y-offset sorting. Сейчас:
- `backgroundSortingOrder = 0`
- `hexSortingOrder = 1`
- `obstacleSortingOrder = 2`

## 4. Отключение гексов под препятствиями

**Запрос:** гексы, пересекающиеся с препятствиями, отключать.

**Изменения:** перед `BuildHexGrid` рассчитывается `ComputeOccupancy(config)` по `blockedHexes` каждого препятствия. Гексы под занятыми клетками получают `SetActive(false)` — в иерархии остаются, но не рендерятся.

## 5. Авто-расчёт blocked hexes

**Запрос:** кнопка в инспекторе `ObstacleDefinition` для авто-расчёта `blockedHexes`. Пустое пространство — цвет `00FFFF`.

**Изменения:**
- `Assets/Scripts/Battle/Editor/ObstacleDefinitionEditor.cs` — CustomEditor с кнопкой **Auto-compute Blocked Hexes**.
- Алгоритм:
  1. `Graphics.Blit` → временный `RenderTexture` (без флага *Read/Write Enabled*).
  2. Пустым считается пиксель с `alpha == 0` или близкий к cyan (с допуском: `r ≤ 64, g ≥ 192, b ≥ 192` — устойчив к сжатию).
  3. Для каждого непустого пикселя мировые координаты от pivot + `spriteOffset` → ближайший гекс.
  4. Уникальный набор → отсортированный список `HexOffset`.
- Добавлены константы `BattlefieldConfig.HexHorizontalStep` / `HexVerticalStep` для единого источника геометрии.

## 6. Типы препятствий (Usual/Absolute) + сверка с VCMI

**Запрос:** два типа препятствий — абсолютное (одно или ноль) и обычное. Обстаклы не должны попадать в клетки юнитов. Сверить алгоритм с VCMI.

**Сверка с VCMI (`lib/battle/ObstaclePlacer.cpp`, `CObstacleInstance`):**
- Два класса: `CObstacleInstance::USUAL` и `CObstacleInstance::ABSOLUTE_OBSTACLE`.
- Абсолютные ставятся первыми, на фиксированном якоре из описания; ≤ 1 на поле.
- Хексы стартовых юнитов и клетки, занятые абсолютным препятствием, помечаются занятыми до цикла случайного размещения.
- Обычные: взвешенный рандом + N попыток рандомного якоря.

**Изменения:**
- `ObstacleDefinition`: добавлены `enum ObstacleType { Usual, Absolute }`, поля `type` и `absoluteAnchor` (Vector2Int).
- `ObstacleDatabase`: перегрузка `GetObstaclesForTerrain(terrain, type)`, поле `absoluteObstacleChance` (0..1).
- `BattlefieldGenerator` переписан в три шага:
  1. `ReserveUnitColumns` — колонки `UnitColumnLeft = 0` и `UnitColumnRight = 14` помечаются занятыми.
  2. `TryPlaceAbsoluteObstacle` — с шансом `absoluteObstacleChance` выбирает одного кандидата и ставит на `absoluteAnchor`, либо пропускает.
  3. `PlaceUsualObstacles` — обычный цикл, границы контролирует сама маска `occupied`.

## 7. VCMI-style pixel-раскладка + PPU 64

**Запрос:** генерация всегда в safe-зоне фона. Фон разместить как надо. PPU 64 — учесть при масштабировании.

**Сверка с VCMI (`client/battle/BattleFieldController.cpp`, `lib/battle/BattleHex.h`):**
- Бэк 800×556 px.
- `BattleHex::WIDTH = 44`, `HEIGHT = 42`, вертикальный шаг `32` (перекрытие для изометрии), сдвиг нечётных рядов `22`.
- Все координаты — от левого-верхнего угла бэка.

**Изменения:**
- `BattlefieldConfig`: константы `ReferencePixelsPerUnit = 64`, `HexHorizontalStepPx = 44`, `HexVerticalStepPx = 32`, `OddRowOffsetPx = 22`.
- `BattlefieldView` переведён на пиксельную раскладку: `pixelsPerUnit`, `hexHorizontalStepPx`, `hexVerticalStepPx`, `oddRowOffsetPx`, `gridOriginPx`. `HexToWorld` делит пиксели на PPU.
- Поле `backgroundOffsetPx`, флаг `normalizeBackgroundScale` — ремасштабирует бэк при несоответствии import PPU.
- Гизмо `drawGridGizmo` — рисует габариты сетки + маркеры углов.

## 8. Фикс наложения гексов / обстаклов в зоне юнитов

**Проблема:** с VCMI-дефолтами (44/32 px) гексы пользовательского префаба (больше по размеру) начали накладываться; обстакл, логически стоящий в col 1, визуально уезжал в col 0 (зона атакующего).

**Изменения:**
- `autoComputeSpacing` (дефолт ON) — шаги берутся из спрайта `hexPrefab`: `stepX = width_px`, `stepY = 0.75 * height_px`, `oddOffset = width_px / 2` (pointy-top тесселляция без наложений).
- Конверсия учитывает расхождение PPU: `scale = view.ppu / sprite.ppu`.
- Убран `gridOriginPx` — заменён на `gridOffsetPx` (оффсет от авто-центра сетки).
- Публичные геттеры `EffectiveHorizontalStepPx` / `EffectiveVerticalStepPx` / `EffectiveOddRowOffsetPx` / `EffectivePixelsPerUnit` — для внешних инструментов.
- `ObstacleDefinitionEditor` — авто-расчёт blocked hexes теперь берёт шаги из `BattlefieldView` в активной сцене (через `FindObjectOfType`), fallback — константы.
- Проверен алгоритм генератора: колонки отрядов резервируются до цикла, `CanPlace` не пускает blocked hex в col 0 / col 14. Логически всё корректно.

## 9. Авто-масштабирование фона под сетку

**Проблема:** сетка вылезала за пределы фона — нужно масштабировать бэк.

**Изменения в `BattlefieldView.ApplyBackground`:**
- Поле `autoFitBackgroundToGrid` (дефолт ON).
- Поле `backgroundSafeZone` (нормализованный `Rect`, 0..1) — зона фона, в которой сетка гарантированно помещается.
- Формула: `fitScale = max(1, gridPx.x / (bgPx.x * safeZone.w), gridPx.y / (bgPx.y * safeZone.h))` (только увеличение, не уменьшение).
- Позиционирование учитывает pivot спрайта: центр safe-зоны всегда совпадает с центром auto-центрированной сетки.
- Гизмо расширен — рисует также safe-зону (оранжевый прямоугольник).
- Публичный `GetGridSizePx()`.

## 10. Дефолт safe-зоны под H3

**Проблема:** даже после auto-fit верхний ряд сетки попадал на область неба/горизонта (safe-зона 90%×90% включала небо).

**Изменения:**
- Дефолт `backgroundSafeZone = Rect(0.03, 0.05, 0.94, 0.78)` — верхние 17% отдаются под небо, 5% снизу, 3% по бокам.
- Дефолт `gridOffsetPx = (0, 0)` — позиционирование полностью делегировано auto-fit.
- Добавлен `[ContextMenu("Apply H3 Defaults")]` на `BattlefieldView` — чтобы сбросить уже сериализованные значения в новые дефолты одним кликом.

---

## Финальная структура

```
Assets/Scripts/Battle/
├── Heroes.Battle.asmdef
├── TerrainType.cs
├── BattlefieldConfig.cs           # pure data + константы раскладки
├── BattlefieldGenerator.cs        # pure C# алгоритм VCMI-style
├── ObstacleDefinition.cs          # SO: ObstacleType { Usual, Absolute }
├── ObstacleDatabase.cs            # SO: pool + backgrounds + настройки
├── BattlefieldView.cs             # MonoBehaviour: deploy на сцену
└── Editor/
    ├── Heroes.Battle.Editor.asmdef
    ├── BattlefieldEditorWindow.cs # Tools → Heroes 3 → Battlefield Generator
    └── ObstacleDefinitionEditor.cs # авто-расчёт blocked hexes по спрайту
```

## Ключевые инварианты

- Генератор детерминирован: `(seed, terrain, database) → BattlefieldConfig`.
- Колонки `0` и `14` зарезервированы под юнитов, обстаклы туда не попадают.
- Абсолютное препятствие — не более одного, с фиксированным якорем.
- Обычные препятствия — взвешенный рандом с ретраями (как в VCMI).
- Раскладка: pointy-top тесселляция, шаги авто-детектятся из спрайта префаба.
- Фон: auto-fit гарантирует, что сетка помещается в `backgroundSafeZone`; масштаб только вверх.
- Sort order: фон `0`, гексы `1`, препятствия `2`; гексы под препятствиями отключаются.
