# 50 волн, модели и бесконечный режим

Статус: обязательный будущий объём. Числа новых режимов ниже — проектные предложения. Подтверждённый источник — `reference_data/gemtd2-source/future-audit/waves-coop.json`:50 основных NPC,25 альтернативных, базовые HP/speed/armor/MR и ability IDs. Полная художественная раскладка всех 50 форм: [data/wave_visual_plan.json](data/wave_visual_plan.json).

## 1. Сначала определить версию правил

Создать `rulesets/gemtd2-2026-10-29` как неизменяемый импорт свидетельств и `rulesets/web-campaign-v1` как явно выбранную веб-реализацию. Пакет старых приложений, установленная 2.0 и нынешний прототип — три разных источника. Не объединять коэффициенты без `provenance` и причины изменения. Серверный матч фиксирует `rulesetVersion`, `contentHash`, `mapVersion`, `seed`, `difficultyProfile` до старта.

Переносить исходные **свойства**, не Dota-модели. Dota ModelScale/скорость Source2 не являются готовыми метрами/секундами веба. Масштаб маршрута 128Source units на клетку известен, но итоговые скорости и native-модификаторы требуют отдельного контрольного теста. Временные скорости должны иметь статус `provisional`, а не «точный оригинал».

## 2. Реестр данных и состояние

`EnemyDefinition`: id, sourceId, movementType, baseHp, armor, resistances, abilityIds, targetTags, collisionRadius, visualId, rewardRule, leakRule, provenance, implementationStatus.

`WaveDefinition`: index1..50, primaryEnemyId, alternativeRules, countRule, spawnSchedule, boss, briefingTags, rewardPolicy, abilityLevelPolicy. Варианты 20/30/40/50 и случайность по врагам 35/36/38 хранить раздельно: это разные точки броска. Календарную ветвь не читать с часов клиента; в рейтинге заменить версионированным seed-событием или фиксированным сезонным правилом.

`WaveState`: absoluteWave, campaignWave, endlessCycle, phase, scheduled/spawned/alive/killed/escaped, startTick, rosterSize, hpScale, countScale, nextSpawnTick, variantChoice, eliteRolls, enrageAtTick. Прочитанное число врагов фиксировать на начале волны; изменения будущей сложности не должны менять уже созданных существ.

`EnemyState`: entityId, definitionId, hp/maxHp, shield, routeProgress, statuses, sourceWave, spawnOrdinal, ownerMapId, rewardClaimed. Визуальный размер не меняет маршрут, радиусы AoE и hitbox автоматически.

Фазы: construction → allDraftsResolved → ready → spawning/combat → waveSettled → nextConstruction. Волна завершена только когда очередь spawn пуста и живых врагов нет. Смерть и выход одного врага в один tick должны дать ровно один исход. Волна 50 выставляет `campaignCleared`, отдельно от `matchEnded`.

## 3. Пять художественных глав

| Волны | Образ | Обычная высота, клетки | Боссы | Сложность формы |
| --- | --- | --- | --- | --- |
| 1–10 |Садовые животные, небольшие насекомые и лесные жители |0.35–0.62 |10:0.95 |Простой силуэт, минимум украшений |
| 11–20 |Лесные хранители и сильные звери |0.55–0.80 |20:1.25 |Панцирь, рога, шерсть, листья |
| 21–30 |Древние хищники, броненосцы, крылатые |0.70–0.98 |30:1.55 |Явные массы брони и отличимые части тела |
| 31–40 |Фантастические глубоководные/небесные существа |0.85–1.15 |40:1.85 |Более крупная пластика, сдержанные рунические акценты |
| 41–50 |Древние стражи и колоссальные финальные формы |1.0–1.30 |50:2.15 |Величественные силуэты, собственная финальная анимация |

Это предложенные высоты для камеры, не характеристики исходника. Сильный рост заметен между главами и внутри каждого класса; первая обычная волна новой главы не обязана быть выше предыдущего босса. У быстрых и летающих форм силуэт может быть вытянутым/широким вместо высокого. Обычный наземный footprint до 0.8 клетки; у босса видимая ширина до 1.4, вычислительная точка пути прежняя. Крылья/рога не должны закрывать больше соседних башен; проверить мобильный zoom1.5.

Таблица 50 силуэтов в JSON — **собственный предложенный арт**, не перевод китайских имён source ID. Например персонаж с «птицей» в имени исходника может идти по земле: gameplay fly брать из source-флага, а не из картинки. Для летучего меха обязателен визуальный полёт; для наземной птицы — бег/прыжки.

## 4. Полный производственный конвейер моделей

1. Привязать 50 `waveVisualId` к реестру. Для каждого: силуэт, движение, размер, палитра главы, читаемые признаки свойств, автор/лицензия/источник, статус temporary/final.
2. Сначала пакет 1–10 с одним полным циклом импорта; затем 11–20,21–30,31–40,41–50. Шесть имеющихся моделей допустимы как временные. Повторная перекраска одной модели не считается 50 уникальными финальными моделями; общий скелет и анимации разрешены.
3. Для каждого GLB: Idle/Move/Death; при необходимости Hit/Spawn. Корень в основании, согласованная ось вперёд, одна система единиц, запечённый transform, без камеры/света. Смерть не задерживает игровую награду; визуальный хвост удаляется отдельно.
4. Цели mobile: обычный LOD0 до 3–5 тыс. треугольников, дальний до 1–2 тыс., босс до 8–12 тыс.;1–2 материала, текстуры 512–1024, минимальное число костей. Это лимиты проекта, а не обещанная скорость на любом телефоне. Профилировать шкуру/прозрачность/тени всей волны.
5. Подгружать текущую и следующие 2 волны; освобождать неиспользуемые материалы/скелеты. Не грузить 50 GLB на первом входе. На сбое загрузки сохранить играбельную fallback-модель и вывести диагностический статус в QA.
6. Совместный скелет/атлас и переиспользование геометрии снижают бюджет; независимую анимацию обеспечить корректным skeleton clone либо измеренным animation instancing. Не покупать дорогую систему до профиля нагрузки.
7. Сохранить contact sheet, масштаб рядом с камнем, короткий preview движения и mobile-снимок каждой главы. Одобренные изображения камней не менять.

## 5. Кампания 1–50

Реализовать все исходные волны как данные. Боссы 10/20/30/40/50; летающие волны определяет JSON. Части модификаторов требуют собственного порта поведения Dota. Для каждого ability ID иметь один из статусов `verified / implemented-provisional / missing`; неизвестная способность не должна исчезнуть без отметки.

Оригинал использует динамический `wave_enemy_count` с нижним 5,50-пороговой серией успехов/неудач, случайной элитой 1/400 и особыми вариантами. Нельзя принять упрощённую «10 врагов всегда» за полный Classic. Для честного сравнения набор бросков — серверный seed, а не Math.random. Варианты элиты должны заранее показывать значок и объяснение, не только цвет.

После 50 показать результат кампании и предложить продолжение в endless. В solo разрешить завершить и сохранить clear. В co-op режим продолжения выбирают в лобби; после 50 можно командой закончить без потери достижения. В Last Survivor выбор не открывает паузу: если осталось несколько игроков, автоматически продолжается endless.

## 6. Настоящая бесконечность — web-endless-v1

Оригинальный 51–100 без стройки сохранить только как отдельный будущий `classic-survival-100`, если он понадобится. **Не называть его endless.** Наше предложение: разрешить новую пятёрку и улучшения после 50; это поддерживает продолжение развития лабиринта. Флаг `buildAfter50=true` фиксируется ruleset и не меняется в ходе матча.

Для абсолютной волны n>50: `cycle=floor((n-51)/50)+1`, `slot=((n-1)%50)+1`. Повторять 50 семейств, но брать отдельную возрастающую кривую угрозы; летающие/боссовые слоты сохранять. Предложение начальной кривой: целевая волновая суммаHP `B(n)=B50 * 1.025^(n-50)`, а не умножение исходногоHP первой волны на большой случайный коэффициент. ДолюHP распределять по типу/количеству существ и коэффициенту босса. Сохранять логарифмическую форму расчёта для аудита.

Эта 2.5%-кривая — старт балансировки, не обещание финальной длительности. Не добавлять одновременно неограниченные скорость, число врагов иHP. Предварительные caps: speed не выше 1.25×соответствующей кампанийной; обычный count не выше 30, живых одновременно не выше 60 на карту. Достигнут cap — увеличивать угрозу свойствами/HP, но не количеством геометрии.

Два модификатора из разрешённого набора на цикл: броня, сопротивление, регенерация, защита первой атаки; несовместимые комбинации запрещены. Комбинация не может сделать волну абсолютно неуязвимой для всех типов доступной обороны. Объявлять условия до следующей стройки. Случайность модификаторов отдельна от RNG камней.

При угрозе численной точности использовать версионированные ступени масштаба HP/урона или decimal/fixed-point представление; не получать Infinity/NaN. Выбранная нормализация обязана сохранять отношение урона кHP, отображаемые числа и replay hash. В рейтинге серверные ресурсные лимиты завершают матч статусом `technical_limit`, а не «игрок проиграл». Такой исход не публикуется как обычный рекорд без обозначения. Стресс-прогон хотя бы до 10000-й вычисляемой волны; фактический баланс проверять на длинных игровых сессиях отдельно.

## 7. Задания агенту и приёмка

| ID | Работа | Проверяемый результат |
| --- | --- | --- |
| W1 |Импорт 50+вариантов, provenance, версия | Все ссылки enemy/ability/visual валидны; неизвестные эффекты блокируют статус Classic |
| W2 |WaveDirector, отдельные RNG-потоки | Одинаковый seed даёт одинаковые варианты при любом FPS клиента |
| W3 |Полный урон замку, награды, enrage | Проверки обычного/босса, последнего spawn, одновременной смерти и выхода |
| W4 |Все особые эффекты | Невидимый не атакуется без раскрытия; иммунитет/броня/уклонение/регенерация корректны |
| W5 |Переход 50→51 и продолжение | Clear не стирается при отказе продолжить; co-op не зависает; wave101 существует |
| W6 |Числа и ресурсы endless | Нет NaN/Infinity/неограниченного spawn; после snapshot получаем тот же hash |
| W7 |Модели пакетами 10 |50 уникальных final visual IDs, клипы/лицензия/LOD/масштаб приняты |

Графика не должна менять исход боя. В headless50 волн проверять независимый отFPS результат; отдельные fixture-тесты не заменять только «сильной башней, убивающей всё». Проверить wave5/15/25/30/40/50, все невидимые/иммунные разновидности и минимум одну волну каждого специального эффекта.
