# Аудит GemTD 2.0:50 волн, продолжение и co-op

Источник: установленный Workshop **3817795446**, GemTD 2.0, publish29.10.2026. Пакет 243517262 байт; SHA256 `c2aed66ec055edf7fdd3bceb02a0d941d207c01b82326074366f0642c623166e`. Точный decoded Lua дополнительно сверён с зашифрованным Lua внутри установленного VPK. NPC definitions прочитаны выборочно из того же пакета. Игра и addon Lua не запускались. Site, игровое ядро и общие планы не менялись.

Основные артефакты: waves-coop.json (структурированные факты/неизвестное/предложения), evidence/index.json (смещения символов и байтов, исходная строка и SHA точной выдержки), evidence/npc-selected-original-lines.txt (исходные строки NPC).

## Главные выводы

1. В оригинале50 исходных типов волн, после них51–100 — усиленное повторение без строительства. **Настоящего бесконечного режима в этом коде нет.**
2. Оригинальный co-op — общая карта/замок/маршрут, до4 игроков, отдельные пятёрки и владельцы; при этом общее управление и общий пул сохранённых ингредиентов.
3. Сложность оригинала **нелинейна**: при4 игроках HP примерно11.319×Solo и speed2.058×Solo; число врагов не умножается на игроков.
4. Значения именно2.0 отличаются от старого приложения: HP[0.69,2.91,5.17,7.81],speed[0.69,1.01,1.19,1.42]. Например wave2 baseSpeed700, а не600. Источники разных версий нельзя незаметно смешивать.

## Все50 исходных волн

HP/speed ниже — **базовые KV**, до коэффициентов игроков, элиты, post50, способностей и движковых ограничений. Количество N: исходный счётчик wave_enemy_count стартует10 и динамически меняется; колонка показывает номинал при начальном счётчике10. Боссы обычно1. Raw flags здесь следуют именно проверкам имён в Lua, а не художественным названиям врагов. Полные armor/MR, intrinsic и добавляемые abilities — в JSON; это необходимо для реализации иммунитетов/аур/уклонения.

| № | Source ID | BaseHP | Base speed | Начальный номинал N | Flags | Intrinsic + added ability IDs |
| --- | --- | ---: | ---: | ---: | --- | --- |
| 1 | `gemtd_kuangbaoyezhu` | 5 | 525 | 10 | ground |  |
| 2 | `gemtd_kuaidiqingwatiaotiao` | 10 | 700 | 10 | ground |  |
| 3 | `gemtd_zhongchenggaoshanmaoniu` | 20 | 525 | 10 | ground |  |
| 4 | `gemtd_moluokedejixiezhushou` | 18 | 1000 | 10 | ground |  |
| 5 | `gemtd_wuweizhihuan_fly` | 22 | 500 | 10 | fly | enemy_fly |
| 6 | `gemtd_shudunziranzhizhu` | 80 | 450 | 10 | ground |  |
| 7 | `gemtd_chaomengjuxi` | 120 | 600 | 10 | ground |  |
| 8 | `gemtd_mengzhu` | 80 | 750 | 10 | ground | enemy_jili, riki_permanent_invisibility |
| 9 | `gemtd_dashiqi` | 180 | 600 | 10 | ground | guai_shanbi |
| 10 | `gemtd_buquzhanquan_boss` | 2100 | 750 | 1 | ground, boss |  |
| 11 | `gemtd_maorongrongdefeiyangyang` | 400 | 525 | 10 | ground |  |
| 12 | `gemtd_caonimalama` | 500 | 600 | 10 | ground | guai_jiaoxieguanghuan |
| 13 | `gemtd_fengtungongzhu` | 900 | 600 | 10 | ground |  |
| 14 | `gemtd_bugou` | 1100 | 600 | 10 | ground | enemy_zheguang |
| 15 | `gemtd_banzhuduizhang_fly` | 270 | 600 | 10 | fly | enemy_fly, enemy_jili |
| 16 | `gemtd_xunjiemotong` | 950 | 800 | 10 | ground | enemy_thief, enemy_momian |
| 17 | `gemtd_yonggandexiaoji` | 2200 | 600 | 10 | ground | enemy_bukeqinfan |
| 18 | `gemtd_xiaobajie` | 3300 | 675 | 10 | ground | riki_permanent_invisibility, guai_jiaoxieguanghuan |
| 19 | `gemtd_shentu` | 3200 | 1000 | 10 | ground | runrunrun |
| 20 | `gemtd_huxiaotao_boss` | 30000 | 900 | 1 | ground, boss |  |
| 21 | `gemtd_siwangsiliezhe` | 7500 | 675 | 10 | ground |  |
| 22 | `gemtd_yaorenxiangluoke` | 11000 | 600 | 10 | ground | enemy_high_armor |
| 23 | `gemtd_tiezuiyaorenxiang` | 10000 | 625 | 10 | ground | enemy_wumian, enemy_zheguang |
| 24 | `gemtd_jixieyaorenxiang` | 20000 | 675 | 10 | ground | mars_bulwark |
| 25 | `gemtd_fengbaozhizikesaier_fly` | 4500 | 400 | 10 | fly | enemy_fly, enemy_high_armor |
| 26 | `gemtd_niepanhuolieniao` | 11000 | 900 | 10 | ground | enemy_momian |
| 27 | `gemtd_lgddejinmengmeng_fly` | 3000 | 625 | 10 | fly | enemy_fly, enemy_jili, enemy_bukeqinfan |
| 28 | `gemtd_youniekesizhinu_fly` | 5000 | 750 | 10 | fly | enemy_fly, mars_bulwark |
| 29 | `gemtd_feihuxia_fly` | 6000 | 775 | 10 | fly | enemy_fly, guai_shanbi, enemy_zheguang |
| 30 | `gemtd_mofafeitanxiaoemo_boss_fly` | 80000 | 750 | 1 | fly, boss | enemy_fly |
| 31 | `gemtd_modianxiaolong` | 24000 | 900 | 10 | ground | enemy_momian, enemy_zheguang |
| 32 | `gemtd_xiaoshayu` | 95000 | 600 | 10 | ground | enemy_recharge |
| 33 | `gemtd_feijiangxiaobao` | 85000 | 700 | 10 | ground | enemy_wumian, enemy_thief, enemy_shanshuo |
| 34 | `gemtd_shangjinbaobao_fly` | 19000 | 725 | 10 | fly | enemy_fly, enemy_thief, guai_shanbi |
| 35 | `gemtd_jinyinhuling_fly` | 9500 | 700 | 10 | fly | enemy_fly, enemy_momian |
| 36 | `gemtd_cuihua` | 65000 | 1000 | 10 | ground | enemy_momian |
| 37 | `gemtd_xiaobaihu` | 160000 | 1200 | 10 | ground | enemy_shanshuo |
| 38 | `gemtd_xiaoxingyue` | 150000 | 1000 | 10 | ground | enemy_jili |
| 39 | `gemtd_liangqiyuhai_fly` | 55000 | 600 | 10 | fly | enemy_fly, enemy_bukeqinfan, tidehunter_blubber, guai_shanbi |
| 40 | `gemtd_guixiaoxieling_boss_fly` | 330000 | 800 | 1 | fly, boss | enemy_fly, tidehunter_blubber |
| 41 | `gemtd_weilanlong` | 170000 | 1300 | 10 | ground | enemy_high_armor |
| 42 | `gemtd_saodiekupu_fly` | 18000 | 700 | 10 | fly | enemy_fly, enemy_momian, runrunrun |
| 43 | `gemtd_maomaoyu` | 200000 | 800 | 10 | ground | enemy_bukeqinfan, guai_shanbi, enemy_recharge |
| 44 | `gemtd_xiaomogu` | 240000 | 500 | 10 | ground | enemy_momian, enemy_shanshuo |
| 45 | `gemtd_jiujiu_fly` | 120000 | 2000 | 10 | fly | enemy_fly, guai_xietong, guai_jiaoxieguanghuan |
| 46 | `gemtd_siwangxintu` | 550000 | 800 | 10 | ground | enemy_wumian |
| 47 | `gemtd_jilamofashi` | 550000 | 600 | 10 | ground | enemy_bukeqinfan, runrunrun |
| 48 | `gemtd_xiaofeixia_fly` | 50000 | 500 | 10 | fly | enemy_fly, enemy_thief, enemy_momian, guai_jiaoxieguanghuan, guai_shanbi |
| 49 | `gemtd_juniaoduoduo` | 650000 | 700 | 10 | ground | tidehunter_blubber, enemy_recharge |
| 50 | `gemtd_roushan_boss_fly` | 1000000 | 1000 | 1 | fly, boss | enemy_fly, tidehunter_blubber |

Количество: обычный spawn уменьшает счётчик на1; boss дополнительно на100, поэтому формально bossCount=ceil(capturedN/101), обычно1. Счётчик не умножается на число игроков. WinStreakAdjust при >=50 очках уменьшает очки на50 и N+=1; при <=−50 — очки+=50,N−=1,нижняя граница5. Явного верхнего cap нет; >=20 только выдаёт quest. Spawn один за глобальный OnThink раз в1сек, не отдельный поток для каждого игрока.

## Варианты волн

- На волнах с guai[wave+100] в start_shuaguai действует50% выбор альтернативы НА ВСЮ ВОЛНУ. Все ID и их KV-статы сохранены в variants JSON (25 уникальных альтернативных NPC). Выбор мутирует guai[wave], поэтому он сохраняется при повторе после50.
- 35,36,38: каждый отдельный spawn с шансом50% добавляет к ID суффикс1.
- 20: yuediyang_boss при day<daysInMonth AND(Random>90 OR day>20). Последний день месяца исключён; февраль жёстко28. До21числа шанс10%, затем до предпоследнего дня100%. Источник системной даты/часовой пояс для веба отдельно определить.
- 30: zard_boss_fly10%;40:gugubiao_boss_fly10%.
- 50: первый Random>80 даётjin20%; ELSEIF со вторым независимым Random>90 даётbojin8% безусловно (вычислено0.8×0.1); базовый72%. Это не два последовательных независимых if старой версии.
- Элита:1/400 на обычного не-boss/non-tester. HP multiplier=(51−min(wave,50))/50×6+2; после50 остаётся2.12. Учитывать дополнительные abilities/tickets/word/account effects отдельно.

## Масштабирование Solo/co-op

| Игроков | HP×base | Speed×base | HP относительно Solo | Speed относительно Solo | Enrage threshold,сек |
| ---: | ---: | ---: | ---: | ---: | ---: |
| 1 | 0.69 | 0.69 | 1.0000 | 1.0000 | 180 |
| 2 | 2.91 | 1.01 | 4.2174 | 1.4638 | 300 |
| 3 | 5.17 | 1.19 | 7.4928 | 1.7246 | 420 |
| 4 | 7.81 | 1.42 | 11.3188 | 2.0580 | 540 |

Формулы: HP=baseHP×gem_difficulty[gem_nandu]×elite; speed=baseSpeed×gem_difficulty_speed[gem_nandu]×post50Speed. Индекс gem_nandu поднимается до PlayerResource:GetPlayerCount() и **не уменьшается** при последующем уменьшении count в этом обработчике. Не обещать пропорциональную разгрузку после ухода игрока. Значения скорости относятся к SetBaseMoveSpeed; лимиты/округление и native modifiers Source2 здесь не воспроизведены.

Дополнительные зависимости от игроков:

- Добавляемые guai_ability получают level=gem_nandu; эффекты также могут усиливаться своими ability-таблицами.
- Enrage: floor(waveSeconds)>P×120+60; добавляет enemy_crazy к общему замку. Win-streak time window — половина этого порога:90/150/210/270сек.
- Каждый строитель ставит5 и сохраняет результат; обычный раунд даёт доP сохранённых результатов, а не один общий выбор.
- Начальные булыжники при1/2/3/4 участниках:32/16/8/0. Карта общая37×37, неP независимых полей.
- Для post50 word guwu пороги раздачи special ability10/8/7/6мобов.
- В non-race damage filter добавляется общий pet-power bonus от аккаунтов участников. Это отдельная мета-система, а не чистая функция P.

Свидетельства: scaling-tables.txt,coop-spawn-scaling.txt,enrage-window.txt,WinStreakAdjust.txt,StartGame.txt.

## Что происходит после50

После завершения50 показывается youwin, но матч не останавливается: через примерно11сек начинается51. start_build при level>50 немедленно переводит в бой и запускает spawn, не выдавая новую пятёрку. gemtd_build_stone отдельно запрещает level>50.

UpdateGuaiLevel использует **level−50**, не modulo. Поэтому51–100 повторяют1–50 (с уже мутированными wave variants). Добавляются2 случайные способности на каждого врага; при word есть дополнительная ветвь. До усиления flying HP /3; каждый выбранный enemy_momian делитHP на5, enemy_wumian на3. Повтор одного random ability допустим: divisor применяется ещё раз до проверки HasAbility. Затем еслиHP>10000,HP=999999999; иначеHP×100000. Speed×1.8. Это резкий survival-переход, а не плавная бесконечная экспонента.

При level>100 start_shuaguai явно выставляет game_status3,отправляет ranking и назначает BADGUYS winner через20сек. При этом немедленного return нет, а content index51 отсутствует — возможный дефект этой конечной ветви; точный runtime outcome не проверялся. Нельзя выдавать этот код за рабочую бесконечность. Разрушение замка при level>50 отдельно назначает GOODGUYS winner, поскольку рубеж50 уже пройден.

Свидетельства: start_build.txt,start_shuaguai.txt,UpdateGuaiLevel.txt,coop-spawn-scaling.txt,EndRoundIfNoEnemyAlive.txt,shared-castle-defeat.txt.

## Общая карта, жизнь и владение

Один общий gem_castle на path7; baseline HP=maxHP100 без умножения на P. SyncHP отражает тот же показатель у замка и строителей. HuichunAura может повышать текущие и максимальные жизни выше100. Обычный escape отнимает1…3/1…7/1…11/1…15/1…19 по диапазонам1–9/11–19/21–29/31–39/41–49; boss ceil(currentHP/maxHP×100). Это общий ущерб, не отдельные100жизней каждому.

У башни сохраняется владелец-builder и control ownerId. В gemtd_coop StartGame включает share masks1 и2 для каждой пары слотов0…3: принадлежность и доступность совместного управления — разные свойства. Saved recipe использует общий gemtd_pool, поэтому потенциально затрагивает башни разных владельцев. Draft build_curr[playerId] отдельный; его нельзя автоматически объединять с общим инвентарём.

Один общий team_gold. CostGold списывает один раз и синхронизирует число всем. Убийство начисляет общий gold один раз; XP получает каждый существующий строитель полностью, без деления на P. Post50 продолжает эту экономику, но строительство отключено.

## Готовность, подключение и уход

Co-op team cap4, addon metadata min1; отдельная Solo-карта ограничена1. В listen-host co-op expected lobby size по умолчанию4, host-команда -player N допускает1…4 до выдачи roster/hero selection. Required lobby count и фактический PlayerResource count — разные величины. Первое нужно для ожидания/дополнения dummy, второе используется в сложности.

is_build_ready[0…3] инициализированыtrue. start_build переводит существующих строителей вfalse. Их выбор очищает собственную пятёрку и ставитtrue. finish_build запускает общую волну, только когда все4флагаtrue; пустые слоты остаютсяtrue. StartGame перед началом ждёт precache реальных игроков.

Connect публикует roster/online count; Steam/session/profile связывают участника. Disconnect уменьшаетonline count и добавляет silence_self герою; Session:OnDisconnect очищает только pending auth. В этом обработчике нет auto-ready, grace timer, удаления башен или уменьшения gem_nandu. Незавершённая пятёрка может остаться блокером готовности — это риск для веба, runtime recovery не проверялся.

Reconnect снимает silence и вызывает Profile:Resume, но восстановление строительной возможности проверяет _G.game_state==1 вместо game_status; в inspected addon присваиваний game_state не найдено. Это сомнительная ветвь, а не готовый веб-контракт reconnect. Playable hotjoin после начала матча этими callbacks не доказан.

Свидетельства: finish_build.txt,StartGame.txt,GemTD_OnPlayerDisconnect.txt,session-disconnect.txt,GemTD_OnPlayerReconnected.txt,GetAllPlayerCount.txt,GemTD_OnPlayerConnectFull.txt,GemTD_OnPlayerStartTeamSelect.txt,GemTD_OnPlayerSay.txt.

## Неизвестное / не переносить догадкой

- Native Source2 damage/movement caps, встроенные способности и жизненный цикл projectiles.
- Точная семантика Steam PlayerResource count после disconnect/dummy и реальный hotjoin.
- Поведение дефектной ветви101 и typo game_state при reconnect.
- Полная работа account pet/meta, дневных word, tickets и случайных elite как финальные боевые статы.

## Предложения для будущего плана — не факты оригинала

1. Требование50+endless реализовать отдельным ruleset: campaign1–50,затем настоящий loop через modulo,версионированный и ограниченный почисленнойустойчивости ростHP/механик. Явно выбрать, остаётся ли строительство; оригинал после50его отключает.
2. Для строго пропорционального co-op можно использовать HP(P)=HP(1)×P,при неизменных speed/count. Не умножать одновременноHP,число искорость, получая скрытую степень сложности. Оригинальную нелинейную таблицу оставить отдельным профилем, если нужен перенос исходной сложности.
3. Фиксировать P для текущей волны; новую численность применять на границе волн. Объявить slot reservation/grace,готовность и управление после ухода. Это веб-политика,невыводизDota callbacks.
4. Общий серверный авторитет,атомарные клетки/крафт,идемпотентные actionId,запрет двойного расхода,снимок и возврат прежнего слота — уже требования docs/planning/03:257–285 и04:T08/T17,а не реализованная часть Lua.

## Связь с текущими планами

docs/planning/02:95 требует полный wave manifest/сложность/варианты;02:109–125 — общую карту и сверку владения.03:257–285 описывает будущую авторитетную сеть/reconnect/snapshot.04:T14(152–158) иT17(176–182) требуют переноса подтверждённых волн и co-op. Исходные документы не содержат доказательства настоящей оригинальной бесконечности или линейного масштабирования. Эти новые пожелания нужно записать в новый план явными продуктовымирешениями.
