@vimp-games/tanks 0.22.10 → 0.22.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{vimp_tanks_core_bg-CUAFeVPQ.wasm → vimp_tanks_core_bg-GriiC3GE.wasm} +0 -0
- package/dist/client-ByYEfcjw.js +210 -0
- package/dist/client-ByYEfcjw.js.map +1 -0
- package/dist/core-node/vimp_tanks_core_bg.wasm +0 -0
- package/dist/{host-BjO9cIgI.js → host-DlCqKb7D.js} +1 -1
- package/dist/host-DlCqKb7D.js.map +1 -0
- package/dist/manifest.json +5 -5
- package/dist/maps/garden.json +1 -1
- package/dist/sounds/bomb-has-been-planted.webm +0 -0
- package/dist/sounds/bomb-has-been-planted2.webm +0 -0
- package/dist/sounds/defeat.webm +0 -0
- package/dist/sounds/empty-bullet.webm +0 -0
- package/dist/sounds/explosion.webm +0 -0
- package/dist/sounds/frag.webm +0 -0
- package/dist/sounds/game-over.webm +0 -0
- package/dist/sounds/hit.webm +0 -0
- package/dist/sounds/round-start.webm +0 -0
- package/dist/sounds/shot.webm +0 -0
- package/dist/sounds/tank-engine.webm +0 -0
- package/dist/sounds/tank-landing.webm +0 -0
- package/dist/sounds/tank-water.webm +0 -0
- package/dist/sounds/victory.webm +0 -0
- package/package.json +1 -1
- package/dist/client-DIcPvl9Z.js +0 -210
- package/dist/client-DIcPvl9Z.js.map +0 -1
- package/dist/host-BjO9cIgI.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-DlCqKb7D.js","sources":["../core/pkg-web/vimp_tanks_core.js","../src/data/maps/wallHeights.js","../src/data/maps/canopy.js","../src/data/maps/city/tiles.js","../src/data/maps/downtown.js","../src/data/maps/garden.js","../src/data/maps/overpass.js","../src/data/maps/pool_mini.js","../src/data/maps/terraces.js","../src/data/maps/index.js","../src/data/models.js","../src/data/weapons.js","../src/config/snapshot.js","../src/config/game.js","../src/config/sounds.js","../src/config/render.js","../src/config/client.js","../src/config/auth.js","../src/host/botCommand.js","../src/host/metaCommands.js","../src/host/systemMessages.js","../src/host/TanksBotManager.js","../src/host/createModules.js","../src/nodeCore.js","../src/host/index.js"],"sourcesContent":["/* @ts-self-types=\"./vimp_tanks_core.d.ts\" */\n\n/**\n * Клиентский режим ядра (срез 2.6): интерполяция снапшотов, предикт\n * своего танка, визуальный спавн снарядов и распаковка кадров v3.\n * Живёт в главном потоке вкладки клиента; горячие позиции читаются\n * zero-copy плоским Float32-буфером, событийные кадры — JSON-строкой.\n */\nexport class ClientCore {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n ClientCoreFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_clientcore_free(ptr, 0);\n }\n /**\n * Зеркало `abi_describe` игрового ядра (см. игровой макрос).\n * @returns {string}\n */\n abi_describe() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_abi_describe(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Ввод указателем: мировая точка + биты состояния — в историю\n * предикта.\n * @param {number} x\n * @param {number} y\n * @param {number} flags\n * @param {number} local_now\n */\n apply_aim(x, y, flags, local_now) {\n wasm.clientcore_apply_aim(this.__wbg_ptr, x, y, flags, local_now);\n }\n /**\n * Ввод игрока: action ('down'/'up') + имя клавиши — в историю\n * предикта.\n * @param {string} action\n * @param {string} key_name\n * @param {number} local_now\n */\n apply_input(action, key_name, local_now) {\n const ptr0 = passStringToWasm0(action, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passStringToWasm0(key_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n wasm.clientcore_apply_input(this.__wbg_ptr, ptr0, len0, ptr1, len1, local_now);\n }\n /**\n * Локальный цикл смены оружия (авторитетное подтверждение — панелью).\n * @param {boolean} back\n */\n cycle_weapon(back) {\n wasm.clientcore_cycle_weapon(this.__wbg_ptr, back);\n }\n /**\n * Дамп клиентского состояния для отладки: сетевой буфер\n * (глубина, окно seq, оффсет, последний кадр), свой gameId,\n * размеры hot-буфера и очереди событийных кадров.\n * @returns {string}\n */\n debug_json() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_debug_json(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Чистая распаковка кадра v3 → JSON {port, seq, serverTime,\n * camera, player, snapshot} (замена unpackFrame в тестах);\n * 'null' при несовпадении версии или повреждённом кадре.\n * @param {Uint8Array} data\n * @returns {string}\n */\n decode_frame(data) {\n let deferred2_0;\n let deferred2_1;\n try {\n const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_decode_frame(this.__wbg_ptr, ptr0, len0);\n deferred2_0 = ret[0];\n deferred2_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);\n }\n }\n /**\n * Зеркало `dispatch` игрового ядра (см. игровой макрос).\n * @param {string} op\n * @param {Uint8Array} payload\n * @returns {Uint8Array}\n */\n dispatch(op, payload) {\n const ptr0 = passStringToWasm0(op, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArray8ToWasm0(payload, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_dispatch(this.__wbg_ptr, ptr0, len0, ptr1, len1);\n var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v3;\n }\n /**\n * Уровень пола под мировой точкой для пули, летящей на уровне `level`:\n * сам `level`, если там его плита (у земли — всегда), иначе уровень\n * приземления — то же правило, что у сегментов луча\n * (`shot_levels::floor_under`). По нему клиент ищет грань стены или\n * насыпи под пулей с моста и кладёт осколки её попадания на пол под ними.\n * Карты нет — `level`.\n * @param {number} level\n * @param {number} x\n * @param {number} y\n * @returns {number}\n */\n floor_level(level, x, y) {\n const ret = wasm.clientcore_floor_level(this.__wbg_ptr, level, x, y);\n return ret;\n }\n /**\n * Указатель на hot-буфер (zero-copy чтение из JS:\n * new Float32Array(wasm.memory.buffer, ptr, len) — view\n * пересоздавать каждый тик, рост памяти WASM инвалидирует\n * buffer).\n * @returns {number}\n */\n hot_ptr() {\n const ret = wasm.clientcore_hot_ptr(this.__wbg_ptr);\n return ret >>> 0;\n }\n /**\n * Копия hot-буфера (nodejs-таргет; горячий путь браузера —\n * hot_ptr).\n * @returns {Float32Array}\n */\n hot_values() {\n const ret = wasm.clientcore_hot_values(this.__wbg_ptr);\n var v1 = getArrayF32FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);\n return v1;\n }\n /**\n * Локальная точка тела → мировая в РЕНДЕРНОМ фрейме: `[x, y]` либо\n * пустой массив (ключ неизвестен). Точку попадания считает симуляция,\n * а рисуется она там, где ящик виден.\n * @param {string} key\n * @param {number} local_x\n * @param {number} local_y\n * @returns {Float32Array}\n */\n map_dynamics_to_world(key, local_x, local_y) {\n const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_map_dynamics_to_world(this.__wbg_ptr, ptr0, len0, local_x, local_y);\n var v2 = getArrayF32FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);\n return v2;\n }\n /**\n * Поколение карты: дешёвый признак «геометрия сменилась». По нему\n * рендер держит разбор `ramp_runs` в кеше и не гоняет через\n * WASM-границу все прогоны карты на каждый слой.\n * @returns {number}\n */\n map_generation() {\n const ret = wasm.clientcore_map_generation(this.__wbg_ptr);\n return ret >>> 0;\n }\n /**\n * Свой gameId из последнего player-блока; -1, если ещё не\n * приходил.\n * @returns {number}\n */\n my_game_id() {\n const ret = wasm.clientcore_my_game_id(this.__wbg_ptr);\n return ret;\n }\n /**\n * Создаёт клиентское ядро из JSON-конфига `{engine: {...}, game: {...}}`\n * (собирается src/lib/clientCoreConfig.js из CONFIG_DATA + opcodes).\n * @param {string} config_json\n */\n constructor(config_json) {\n const ptr0 = passStringToWasm0(config_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_new(ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n this.__wbg_ptr = ret[0];\n ClientCoreFinalization.register(this, this.__wbg_ptr, this);\n return this;\n }\n /**\n * EMA-оценка (serverTime − localNow); NaN, если кадров ещё не\n * было. Это разница часов (`Date.now` хоста против\n * `performance.now` клиента), а **не** латентность: за оценку\n * RTT её принимать нельзя.\n * @returns {number}\n */\n offset() {\n const ret = wasm.clientcore_offset(this.__wbg_ptr);\n return ret;\n }\n /**\n * Бинарный кадр из транспорта: распаковка, вставка в буфер по\n * seq (+дедупликация/опоздавшие), reconciliation предикта по\n * player-блоку. false — кадр отброшен (чужой порт/версия/\n * повреждён).\n * @param {Uint8Array} data\n * @param {number} local_now\n * @returns {boolean}\n */\n push_frame(data, local_now) {\n const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_push_frame(this.__wbg_ptr, ptr0, len0, local_now);\n return ret !== 0;\n }\n /**\n * Прогоны рамп текущей карты для РЕНДЕРА клина: тот же\n * `MapLevels::runs`, по которому физика ставит стражей. Второй обход\n * грида на JS расходился бы с ядром молча — горка, которую видно и\n * нельзя проехать.\n * Формат: JSON-массив\n * `{ axis, sign, from, to, min, max, crossMin, crossMax, block,\n * railMin, railMax }`,\n * координаты — МИРОВЫЕ (уже масштабированные), как в `RampRun`.\n * Пустой массив — карты нет или она одноуровневая.\n * @returns {string}\n */\n ramp_runs() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_ramp_runs(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Полный сброс (порт CLEAR).\n */\n reset() {\n wasm.clientcore_reset(this.__wbg_ptr);\n }\n /**\n * Ресинк часов после долгой паузы вкладки: сброс сетевого\n * буфера и очереди кадров без обнуления предикта.\n */\n resync() {\n wasm.clientcore_resync(this.__wbg_ptr);\n }\n /**\n * Весь рендер-тик: выдача пересечённых кадров (фильтр дублей\n * своих эффектов → JSON-очередь), интерполяция, шаг предикта,\n * запись hot-буфера. Возвращает длину hot-буфера в\n * f32-элементах.\n * @param {number} local_now\n * @returns {number}\n */\n sample(local_now) {\n const ret = wasm.clientcore_sample(this.__wbg_ptr, local_now);\n return ret >>> 0;\n }\n /**\n * Смена режима игрок/спектатор (KEYSET_DATA).\n * @param {boolean} active\n */\n set_active(active) {\n wasm.clientcore_set_active(this.__wbg_ptr, active);\n }\n /**\n * Данные карты (MAP_DATA): мир raycast + сброс буфера и\n * предикта.\n * @param {string} map_json\n */\n set_map(map_json) {\n const ptr0 = passStringToWasm0(map_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.clientcore_set_map(this.__wbg_ptr, ptr0, len0);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Модель танка пользователя (известна при авторизации).\n * @param {string} model\n */\n set_model(model) {\n const ptr0 = passStringToWasm0(model, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n wasm.clientcore_set_model(this.__wbg_ptr, ptr0, len0);\n }\n /**\n * Сегменты луча выстрела по уровням — те же `shot_levels::ray_segments`,\n * по которым хост судит попадание: `(x, y)` — дуло, `(dx, dy)` —\n * единичное направление, `range` — длина луча, `level` — уровень\n * стрелка (пуля ровно на уровне стрелка — наклона ствола рендер не\n * знает). Плоско: `[t0, t1, fly, …]`, `t` — мировые единицы вдоль\n * луча, третий элемент — уровень ПРОЕКЦИИ сегмента. Трассер режется по\n * ним на куски, и каждый рисуется на своём уровне: луч с моста идёт над\n * плитой и дальше на её высоте. Карты нет —\n * один сегмент `[0, range, level]`; одноуровневая карта — один\n * сегмент уровня 0, как у хоста.\n * @param {number} x\n * @param {number} y\n * @param {number} dx\n * @param {number} dy\n * @param {number} range\n * @param {number} level\n * @returns {Float32Array}\n */\n shot_segments(x, y, dx, dy, range, level) {\n const ret = wasm.clientcore_shot_segments(this.__wbg_ptr, x, y, dx, dy, range, level);\n var v1 = getArrayF32FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);\n return v1;\n }\n /**\n * Индекс типа поверхности под мировой точкой уровня (порядок\n * `surface_types`); `-1` — карты нет, поверхностей она не объявила или\n * клетка нейтральна. Эффекты частей берут поверхность отсюда, а не из\n * копии таблицы на JS: пыль обязана подниматься ровно там, где физика\n * тормозит танк.\n * @param {number} x\n * @param {number} y\n * @param {number} level\n * @returns {number}\n */\n surface_at(x, y, level) {\n const ret = wasm.clientcore_surface_at(this.__wbg_ptr, x, y, level);\n return ret;\n }\n /**\n * Направление стрелки клетки под мировой точкой уровня: `0..3` —\n * север/юг/запад/восток (как у рамп); `-1` — нейтральная клетка или тип\n * без направления.\n * @param {number} x\n * @param {number} y\n * @param {number} level\n * @returns {number}\n */\n surface_dir_at(x, y, level) {\n const ret = wasm.clientcore_surface_dir_at(this.__wbg_ptr, x, y, level);\n return ret;\n }\n /**\n * Имена типов поверхностей JSON-массивом в порядке индексов\n * `surface_at`. Направлений не содержит — их даёт `surface_dir_at`.\n * @returns {string}\n */\n surface_types() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_surface_types(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Авторитетное состояние панели (PANEL_DATA): патроны/активное оружие.\n * @param {string} panel_json\n */\n sync_panel(panel_json) {\n const ptr0 = passStringToWasm0(panel_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n wasm.clientcore_sync_panel(this.__wbg_ptr, ptr0, len0);\n }\n /**\n * Записи расхождения предикта с авторитетным состоянием\n * (JSON {samples, violations, dropped, maxDelta, records});\n * очередь очищается. 'null' — детектор выключен (конфиг без\n * секции divergence, боевой путь).\n * @returns {string}\n */\n take_divergence() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_take_divergence(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Событийные кадры JSON-строкой [{game, camera}, ...] в\n * форме, готовой для applyShot; вызывать при флаге hasFrames\n * hot-буфера, очередь очищается.\n * @returns {string}\n */\n take_frames() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.clientcore_take_frames(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Локальный визуальный выстрел: гейты (кулдаун/патроны/pending-бомба/\n * жив/активен) внутри. JSON спавна для applyGameData либо None.\n * @param {number} local_now\n * @returns {string | undefined}\n */\n try_fire(local_now) {\n const ret = wasm.clientcore_try_fire(this.__wbg_ptr, local_now);\n let v1;\n if (ret[0] !== 0) {\n v1 = getStringFromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n }\n return v1;\n }\n}\nif (Symbol.dispose) ClientCore.prototype[Symbol.dispose] = ClientCore.prototype.free;\n\n/**\n * Публичный ABI ядра для JS-оболочки (Worker хоста / тестовый харнесс).\n */\nexport class GameCore {\n __destroy_into_raw() {\n const ptr = this.__wbg_ptr;\n this.__wbg_ptr = 0;\n GameCoreFinalization.unregister(this);\n return ptr;\n }\n free() {\n const ptr = this.__destroy_into_raw();\n wasm.__wbg_gamecore_free(ptr, 0);\n }\n /**\n * Самоописание ядра (JSON: версия формата, версия движкового\n * крейта, список опкодов dispatch). Движок читает его один раз\n * при загрузке — до первого вызова, а не посреди матча.\n * @returns {string}\n */\n abi_describe() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_abi_describe(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Живые игроки плоским массивом [id, teamId, x, y, ...]\n * (аналог Game.getAlivePlayers для меты).\n * @returns {Float32Array}\n */\n alive_players() {\n const ret = wasm.gamecore_alive_players(this.__wbg_ptr);\n var v1 = getArrayF32FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);\n return v1;\n }\n /**\n * Ввод указателем: seq + мировая точка + биты состояния\n * (wire-формат 'seq:aim:x:y:flags' разбирает JS-оболочка).\n * @param {number} game_id\n * @param {number} seq\n * @param {number} x\n * @param {number} y\n * @param {number} flags\n */\n apply_aim(game_id, seq, x, y, flags) {\n wasm.gamecore_apply_aim(this.__wbg_ptr, game_id, seq, x, y, flags);\n }\n /**\n * Ввод игрока: seq + action ('down'/'up') + имя клавиши\n * (wire-формат 'seq:action:name' разбирает JS-оболочка).\n * @param {number} game_id\n * @param {number} seq\n * @param {string} action\n * @param {string} key_name\n */\n apply_input(game_id, seq, action, key_name) {\n const ptr0 = passStringToWasm0(action, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passStringToWasm0(key_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len1 = WASM_VECTOR_LEN;\n wasm.gamecore_apply_input(this.__wbg_ptr, game_id, seq, ptr0, len0, ptr1, len1);\n }\n /**\n * Содержал ли последний `pack_body()` событийные блоки\n * (трассеры/бомбы/взрывы/удаления). JS-Worker вызывает после\n * `pack_body()` для выбора канала WebRTC: события → meta\n * (reliable), только позиции → state.\n * @returns {boolean}\n */\n body_has_events() {\n const ret = wasm.gamecore_body_has_events(this.__wbg_ptr);\n return ret !== 0;\n }\n /**\n * Полная очистка мира (смена карты).\n */\n clear() {\n wasm.gamecore_clear(this.__wbg_ptr);\n }\n /**\n * Курированный дамп мира для отладки (тела, коллайдеры,\n * карта, нав-граф, spatial-сетка, rng, аккумулятор) —\n * читаемая альтернатива serialize_state.\n * @returns {string}\n */\n debug_json() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_debug_json(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * @param {Uint8Array} data\n */\n deserialize_state(data) {\n const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_deserialize_state(this.__wbg_ptr, ptr0, len0);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Единая точка роста ABI: новая возможность приезжает опкодом,\n * а не новым символом (таблица экспортов заморожена). Пустой\n * возврат — «опкод не обработан», `[0x00]` — «обработан, ответа\n * нет».\n * @param {string} op\n * @param {Uint8Array} payload\n * @returns {Uint8Array}\n */\n dispatch(op, payload) {\n const ptr0 = passStringToWasm0(op, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ptr1 = passArray8ToWasm0(payload, wasm.__wbindgen_malloc);\n const len1 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_dispatch(this.__wbg_ptr, ptr0, len0, ptr1, len1);\n var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v3;\n }\n /**\n * Копия последнего кадра (nodejs-таргет не отдаёт память\n * наружу; горячий путь браузера использует frame_ptr + память\n * WASM).\n * @returns {Uint8Array}\n */\n frame_bytes() {\n const ret = wasm.gamecore_frame_bytes(this.__wbg_ptr);\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Указатель на буфер последнего кадра (zero-copy чтение из JS:\n * new Uint8Array(wasm.memory.buffer, ptr, len)).\n * @returns {number}\n */\n frame_ptr() {\n const ret = wasm.gamecore_frame_ptr(this.__wbg_ptr);\n return ret >>> 0;\n }\n /**\n * @param {number} game_id\n * @returns {boolean}\n */\n is_alive(game_id) {\n const ret = wasm.gamecore_is_alive(this.__wbg_ptr, game_id);\n return ret !== 0;\n }\n /**\n * @param {number} game_id\n * @returns {number}\n */\n last_input_seq(game_id) {\n const ret = wasm.gamecore_last_input_seq(this.__wbg_ptr, game_id);\n return ret >>> 0;\n }\n /**\n * Загружает карту из JSON (см. scripts/export-maps.js).\n * @param {string} map_json\n */\n load_map(map_json) {\n const ptr0 = passStringToWasm0(map_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_load_map(this.__wbg_ptr, ptr0, len0);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Информация о загруженной карте: setId, масштабированные\n * респауны, размеры мира (JSON).\n * @returns {string}\n */\n map_info() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_map_info(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Создаёт ядро из JSON-конфига `{engine: {...}, game: {...}}`\n * (собирается JS-оболочкой из game.js + models.js + weapons.js +\n * opcodes.js, см. PLAN.md §3.4).\n * @param {string} config_json\n */\n constructor(config_json) {\n const ptr0 = passStringToWasm0(config_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_new(ptr0, len0);\n if (ret[2]) {\n throw takeFromExternrefTable0(ret[1]);\n }\n this.__wbg_ptr = ret[0];\n GameCoreFinalization.register(this, this.__wbg_ptr, this);\n return this;\n }\n /**\n * Пакует broadcast-тело кадра, дренируя накопленные события\n * снапшота. Вызывать один раз на отправляемый кадр (throttle\n * частоты отправки — забота JS-оболочки).\n */\n pack_body() {\n const ret = wasm.gamecore_pack_body(this.__wbg_ptr);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Собирает per-user кадр v3 во внутренний буфер, возвращает\n * длину. Кадр читается zero-copy через frame_ptr() + память\n * WASM. player_id < 0 — кадр без player-блока (наблюдатель).\n * @param {number} server_time\n * @param {number} seq\n * @param {boolean} has_camera\n * @param {number} camera_x\n * @param {number} camera_y\n * @param {boolean} force_reset\n * @param {string | null | undefined} shake\n * @param {number} player_id\n * @returns {number}\n */\n pack_frame(server_time, seq, has_camera, camera_x, camera_y, force_reset, shake, player_id) {\n var ptr0 = isLikeNone(shake) ? 0 : passStringToWasm0(shake, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_pack_frame(this.__wbg_ptr, server_time, seq, has_camera, camera_x, camera_y, force_reset, ptr0, len0, player_id);\n return ret >>> 0;\n }\n /**\n * Полные данные всех игроков (Game.getPlayersData) одной\n * JSON-строкой для первого кадра (FIRST_SHOT_DATA). Не\n * дренирует накопители.\n * @returns {string}\n */\n players_data() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_players_data(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * Координаты танка [x, y] (скруглены до 2 знаков) или пустой\n * массив.\n * @param {number} game_id\n * @returns {Float32Array}\n */\n position_of(game_id) {\n const ret = wasm.gamecore_position_of(this.__wbg_ptr, game_id);\n var v1 = getArrayF32FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);\n return v1;\n }\n /**\n * @param {number} game_id\n */\n remove_actor(game_id) {\n wasm.gamecore_remove_actor(this.__wbg_ptr, game_id);\n }\n /**\n * Удаляет игроков и снаряды, возвращает JSON-массив имён для\n * очистки полотна клиентов (Game.removePlayersAndShots).\n * @returns {string}\n */\n remove_players_and_shots() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_remove_players_and_shots(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n /**\n * @param {number} game_id\n */\n remove_scripted_actor(game_id) {\n wasm.gamecore_remove_scripted_actor(this.__wbg_ptr, game_id);\n }\n /**\n * Респаун/смена команды (аналог Game.changePlayerData).\n * @param {number} game_id\n * @param {number} team_id\n * @param {number} x\n * @param {number} y\n * @param {number} angle_deg\n */\n reset_actor(game_id, team_id, x, y, angle_deg) {\n wasm.gamecore_reset_actor(this.__wbg_ptr, game_id, team_id, x, y, angle_deg);\n }\n /**\n * Сброс здоровья/боезапаса всех танков (аналог Panel.reset).\n */\n reset_all_vitals() {\n wasm.gamecore_reset_all_vitals(this.__wbg_ptr);\n }\n /**\n * Дамп состояния симуляции (Worker Handoff, Этап 5.2).\n * @returns {Uint8Array}\n */\n serialize_state() {\n const ret = wasm.gamecore_serialize_state(this.__wbg_ptr);\n if (ret[3]) {\n throw takeFromExternrefTable0(ret[2]);\n }\n var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();\n wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);\n return v1;\n }\n /**\n * Явный уровень участника на слоёной карте (`respawns[i][3]`).\n * Движок зовёт сразу после `spawn_actor`/`reset_actor`, когда\n * точка респауна назвала уровень; игра без слоёв метод\n * игнорирует (дефолт трейта — no-op).\n * @param {number} game_id\n * @param {number} level\n */\n set_actor_level(game_id, level) {\n wasm.gamecore_set_actor_level(this.__wbg_ptr, game_id, level);\n }\n /**\n * @param {number} game_id\n * @param {string} model\n * @param {number} team_id\n * @param {number} x\n * @param {number} y\n * @param {number} angle_deg\n */\n spawn_actor(game_id, model, team_id, x, y, angle_deg) {\n const ptr0 = passStringToWasm0(model, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_spawn_actor(this.__wbg_ptr, game_id, ptr0, len0, team_id, x, y, angle_deg);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * @param {number} game_id\n * @param {string} model\n * @param {number} team_id\n * @param {number} x\n * @param {number} y\n * @param {number} angle_deg\n */\n spawn_scripted_actor(game_id, model, team_id, x, y, angle_deg) {\n const ptr0 = passStringToWasm0(model, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n const len0 = WASM_VECTOR_LEN;\n const ret = wasm.gamecore_spawn_scripted_actor(this.__wbg_ptr, game_id, ptr0, len0, team_id, x, y, angle_deg);\n if (ret[1]) {\n throw takeFromExternrefTable0(ret[0]);\n }\n }\n /**\n * Шаг симуляции: фиксированные подшаги физики + ИИ ботов.\n * @param {number} dt\n */\n step(dt) {\n wasm.gamecore_step(this.__wbg_ptr, dt);\n }\n /**\n * События за тик (kill/health/ammo/weapon/shake) одной\n * JSON-строкой; буфер очищается.\n * @returns {string}\n */\n take_events() {\n let deferred1_0;\n let deferred1_1;\n try {\n const ret = wasm.gamecore_take_events(this.__wbg_ptr);\n deferred1_0 = ret[0];\n deferred1_1 = ret[1];\n return getStringFromWasm0(ret[0], ret[1]);\n } finally {\n wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);\n }\n }\n}\nif (Symbol.dispose) GameCore.prototype[Symbol.dispose] = GameCore.prototype.free;\nfunction __wbg_get_imports() {\n const import0 = {\n __proto__: null,\n __wbg_Error_92b29b0548f8b746: function(arg0, arg1) {\n const ret = Error(getStringFromWasm0(arg0, arg1));\n return ret;\n },\n __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {\n throw new Error(getStringFromWasm0(arg0, arg1));\n },\n __wbindgen_init_externref_table: function() {\n const table = wasm.__wbindgen_externrefs;\n const offset = table.grow(4);\n table.set(0, undefined);\n table.set(offset + 0, undefined);\n table.set(offset + 1, null);\n table.set(offset + 2, true);\n table.set(offset + 3, false);\n },\n };\n return {\n __proto__: null,\n \"./vimp_tanks_core_bg.js\": import0,\n };\n}\n\nconst ClientCoreFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_clientcore_free(ptr, 1));\nconst GameCoreFinalization = (typeof FinalizationRegistry === 'undefined')\n ? { register: () => {}, unregister: () => {} }\n : new FinalizationRegistry(ptr => wasm.__wbg_gamecore_free(ptr, 1));\n\nfunction getArrayF32FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);\n}\n\nfunction getArrayU8FromWasm0(ptr, len) {\n ptr = ptr >>> 0;\n return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);\n}\n\nlet cachedFloat32ArrayMemory0 = null;\nfunction getFloat32ArrayMemory0() {\n if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) {\n cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer);\n }\n return cachedFloat32ArrayMemory0;\n}\n\nfunction getStringFromWasm0(ptr, len) {\n return decodeText(ptr >>> 0, len);\n}\n\nlet cachedUint8ArrayMemory0 = null;\nfunction getUint8ArrayMemory0() {\n if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {\n cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachedUint8ArrayMemory0;\n}\n\nfunction isLikeNone(x) {\n return x === undefined || x === null;\n}\n\nfunction passArray8ToWasm0(arg, malloc) {\n const ptr = malloc(arg.length * 1, 1) >>> 0;\n getUint8ArrayMemory0().set(arg, ptr / 1);\n WASM_VECTOR_LEN = arg.length;\n return ptr;\n}\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length, 1) >>> 0;\n getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len, 1) >>> 0;\n\n const mem = getUint8ArrayMemory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7F) break;\n mem[ptr + offset] = code;\n }\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;\n const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);\n const ret = cachedTextEncoder.encodeInto(arg, view);\n\n offset += ret.written;\n ptr = realloc(ptr, len, offset, 1) >>> 0;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction takeFromExternrefTable0(idx) {\n const value = wasm.__wbindgen_externrefs.get(idx);\n wasm.__externref_table_dealloc(idx);\n return value;\n}\n\nlet cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\ncachedTextDecoder.decode();\nconst MAX_SAFARI_DECODE_BYTES = 2146435072;\nlet numBytesDecoded = 0;\nfunction decodeText(ptr, len) {\n numBytesDecoded += len;\n if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {\n cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\n cachedTextDecoder.decode();\n numBytesDecoded = len;\n }\n return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));\n}\n\nconst cachedTextEncoder = new TextEncoder();\n\nif (!('encodeInto' in cachedTextEncoder)) {\n cachedTextEncoder.encodeInto = function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length\n };\n };\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nlet wasmModule, wasmInstance, wasm;\nfunction __wbg_finalize_init(instance, module) {\n wasmInstance = instance;\n wasm = instance.exports;\n wasmModule = module;\n cachedFloat32ArrayMemory0 = null;\n cachedUint8ArrayMemory0 = null;\n wasm.__wbindgen_start();\n return wasm;\n}\n\nasync function __wbg_load(module, imports) {\n if (typeof Response === 'function' && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === 'function') {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n } catch (e) {\n const validResponse = module.ok && expectedResponseType(module.type);\n\n if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {\n console.warn(\"`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\", e);\n\n } else { throw e; }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n } else {\n return instance;\n }\n }\n\n function expectedResponseType(type) {\n switch (type) {\n case 'basic': case 'cors': case 'default': return true;\n }\n return false;\n }\n}\n\nfunction initSync(module) {\n if (wasm !== undefined) return wasm;\n\n\n if (module !== undefined) {\n if (Object.getPrototypeOf(module) === Object.prototype) {\n ({module} = module)\n } else {\n console.warn('using deprecated parameters for `initSync()`; pass a single object instead')\n }\n }\n\n const imports = __wbg_get_imports();\n if (!(module instanceof WebAssembly.Module)) {\n module = new WebAssembly.Module(module);\n }\n const instance = new WebAssembly.Instance(module, imports);\n return __wbg_finalize_init(instance, module);\n}\n\nasync function __wbg_init(module_or_path) {\n if (wasm !== undefined) return wasm;\n\n\n if (module_or_path !== undefined) {\n if (Object.getPrototypeOf(module_or_path) === Object.prototype) {\n ({module_or_path} = module_or_path)\n } else {\n console.warn('using deprecated parameters for the initialization function; pass a single object instead')\n }\n }\n\n if (module_or_path === undefined) {\n module_or_path = new URL('vimp_tanks_core_bg.wasm', import.meta.url);\n }\n const imports = __wbg_get_imports();\n\n if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {\n module_or_path = fetch(module_or_path);\n }\n\n const { instance, module } = await __wbg_load(await module_or_path, imports);\n\n return __wbg_finalize_init(instance, module);\n}\n\nexport { initSync, __wbg_init as default };\n","// Высоты стен карты для ядра: уровень → тайл → высота объёма в уровнях.\n// Выводятся из тех же рендер-слоёв (`layers`: слой → тайлы) и их объёмов\n// (`volumes`: слой → высота), по которым клиент рисует объёмы\n// (src/client/volumes.js), — у уровня 0 и у каждого `levels[N]`. Движок\n// `layers`/`volumes` хосту не передаёт (`GameMap` их не хранит), поэтому\n// высоты едут в игровом поле карты `game.wallHeights`\n// (core/src/map_game.rs). Тайл в нескольких слоях — самый высокий объём,\n// как у `volumes.js`\nexport function wallHeightsOf(map) {\n const out = {};\n const add = (level, layers = {}, volumes = {}) => {\n for (const [layer, height] of Object.entries(volumes)) {\n for (const tile of layers[layer] || []) {\n const heights = (out[level] ??= {});\n\n heights[tile] = Math.max(heights[tile] ?? 0, height);\n }\n }\n };\n\n add('0', map.layers, map.volumes);\n\n for (const [level, config] of Object.entries(map.levels || {})) {\n add(String(level), config.layers, config.volumes);\n }\n\n return out;\n}\n\n// Карта с `game.wallHeights`; прочие поля `game` сохраняются. Карта без\n// объёмов остаётся как есть: стена без высоты для пули бесконечно\n// высокая (`MapGame::wall_height`)\nexport function withWallHeights(map) {\n const wallHeights = wallHeightsOf(map);\n\n if (Object.keys(wallHeights).length === 0) {\n return map;\n }\n\n return { ...map, game: { ...map.game, wallHeights } };\n}\n","export default {\n setId: 'c1',\n\n scale: 0.2,\n\n spriteSheet: {\n img: 'tiles3.png',\n frames: [\n [0, 64, 32, 32], // 0: пол\n [0, 192, 32, 32], // 1: стена\n [96, 64, 32, 32], // 2: эффект блиндажа\n ],\n },\n\n layers: {\n 1: [0, 1], // слои под танком\n 4: [2], // слой над танком\n },\n\n step: 32,\n\n physicsStatic: [1],\n\n // динамические объекты\n physicsDynamic: (function () {\n const step = 32;\n const dynamicObjectSize = 96;\n const halfSize = dynamicObjectSize / 2;\n const generatedObjects = [];\n\n const objectSizeInTiles = dynamicObjectSize / step; // 3\n\n // параметры стены: 1 блок в ширину, 20 в высоту\n const wallWidthInBlocks = 1;\n const wallHeightInBlocks = 20;\n\n const startX = 104; // центр по горизонтали\n const startY = 45;\n\n // вертикальный ряд\n for (let y = 0; y < wallHeightInBlocks; y += 1) {\n for (let x = 0; x < wallWidthInBlocks; x += 1) {\n const posX = (startX + x * objectSizeInTiles) * step + halfSize;\n const posY = (startY + y * objectSizeInTiles) * step + halfSize;\n\n generatedObjects.push({\n density: 20,\n layer: 3,\n position: [posX, posY],\n angle: 0,\n width: dynamicObjectSize,\n height: dynamicObjectSize,\n img: 'b1.png',\n linearDamping: 8.0,\n angularDamping: 16.0,\n });\n }\n }\n\n return generatedObjects;\n })(),\n\n respawns: {\n team1: [\n // центральная зона\n [448, 1840, 0],\n [256, 2304, 0],\n [448, 2688, 0],\n [256, 3000, 0],\n // верхняя зона\n [256, 512, 0],\n [448, 832, 0],\n [256, 1152, 0],\n // нижняя зона\n [448, 3648, 0],\n [256, 3968, 0],\n [448, 4288, 0],\n ],\n team2: [\n // центральная зона\n [6272, 1840, 180],\n [6464, 2304, 180],\n [6272, 2688, 180],\n [6464, 3000, 180],\n // верхняя зона\n [6464, 512, 180],\n [6272, 832, 180],\n [6464, 1152, 180],\n // нижняя зона\n [6272, 3648, 180],\n [6464, 3968, 180],\n [6272, 4288, 180],\n ],\n },\n\n map: (function () {\n const width = 210;\n const height = 150;\n const tiles = { floor: 0, wall: 1, effect: 2 };\n\n const map = Array.from({ length: height }, () =>\n Array(width).fill(tiles.floor),\n );\n\n // внешние стены\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n if (y < 2 || y >= height - 2 || x < 2 || x >= width - 2) {\n map[y][x] = tiles.wall;\n }\n }\n }\n\n // статические стены\n const buildWall = (x1, y1, x2, y2) => {\n for (let y = y1; y <= y2; y++) {\n for (let x = x1; x <= x2; x++) {\n if (x >= 0 && x < width && y >= 0 && y < height) {\n map[y][x] = tiles.wall;\n }\n }\n }\n };\n\n // статичные стены\n buildWall(50, 0, 51, 30);\n buildWall(50, height - 31, 51, height - 1);\n buildWall(width - 52, 0, width - 51, 30);\n buildWall(width - 52, height - 31, width - 51, height - 1);\n buildWall(80, 30, 130, 31);\n buildWall(80, height - 32, 130, height - 31);\n\n // центральный блиндаж\n for (let y = 64; y < 86; y++) {\n for (let x = 2; x < width - 2; x++) {\n if ((x + y) % 2 === 0) {\n map[y][x] = tiles.effect;\n } else {\n map[y][x] = tiles.floor;\n }\n }\n }\n\n return map;\n })(),\n};\n","// Каталог кадров тайл-листа города assets/img/city.png (карта downtown).\n// Id тайла — индекс в `frames`: bakeTileLayer.js берёт текстуру\n// `frame${id}`. Кадр 0 — прозрачная пустышка: `0` в `levels[n].map` значит\n// «уровня здесь нет», поэтому тайл 0 не стоит ни на одном уровне.\n//\n// Лист — сетка 16 × 8 клеток по 32 px (512 × 256). Кадр id лежит в клетке\n// (id % 16, id / 16). Финальный арт заменяет city.png без правок кода, если\n// сохраняет эту раскладку. Заглушки рисует scripts/generate-placeholder-art.js.\n\nexport const CELL = 32;\nexport const SHEET_COLUMNS = 16;\nexport const SHEET_ROWS = 8;\n\nexport const T = {\n EMPTY: 0,\n ASPHALT: 1,\n SIDEWALK: 2,\n LANE: 3,\n SAND: 4,\n MUD: 5,\n WATER: 6,\n OIL: 7,\n CONVEYOR_E: 8,\n CONVEYOR_W: 9,\n BOOST_N: 10,\n BOOST_E: 11,\n WALL: 12,\n ROOF: 13,\n SLAB: 14,\n RAILING: 15,\n RAMP_N: 16,\n RAMP_S: 17,\n RAMP_E: 18,\n RAMP_W: 19,\n CANAL_WALL: 20,\n // дополнительные кадры анимаций (в сетках карты не стоят)\n WATER_2: 21,\n WATER_3: 22,\n WATER_4: 23,\n CONVEYOR_E_2: 24,\n CONVEYOR_E_3: 25,\n CONVEYOR_E_4: 26,\n CONVEYOR_W_2: 27,\n CONVEYOR_W_3: 28,\n CONVEYOR_W_4: 29,\n BOOST_N_2: 30,\n BOOST_E_2: 31,\n FAN: 32,\n};\n\nconst FRAME_COUNT = T.FAN + 1;\n\nexport const frames = Array.from({ length: FRAME_COUNT }, (_, id) => [\n (id % SHEET_COLUMNS) * CELL,\n Math.floor(id / SHEET_COLUMNS) * CELL,\n CELL,\n CELL,\n]);\n","import { T, frames } from './city/tiles.js';\n\n// Ночной город на два уровня — эталон `game`-полей карты: поверхности,\n// разрушаемые пропы, ночное освещение, анимированные тайлы, неон и декали.\n// Графика — заглушки scripts/generate-placeholder-art.js (city.png,\n// prop_*.png); финальный арт заменяет файлы без правок кода.\n//\n// Районы (клетки [col, row], сетка 96 × 72):\n// * запад и восток (1–14 и 81–94 × 20–52) — базы команд, по 8 респаунов;\n// * центр «Neon Strip» (37–58 × 28–43) — перекрёсток, масляное пятно,\n// фонари по углам, вывески на крышах;\n// * эстакада W–E (уровень 1, ряды 20–26) над проспектом, рампы на концах\n// и два разрыва перил;\n// * северо-запад — промзона: встречные конвейеры, ящики, группа бочек,\n// вентиляторы на крышах;\n// * северо-восток — стройка: песок, грязь, заборы, ящики;\n// * юг — канал с водой, два моста уровня 1 и брод с бустером;\n// * прыжок (запад центра) — бустер перед рампой на крышу-парковку.\n//\n// Пропы стоят только во дворах, на срезках и в тупиках: граф навигации\n// ботов строится по статике, и проп в единственном проезде запер бы бота\n// (tests/scenarios/bots_downtown.json).\n//\n// Она же — фикстура сценариев tests/scenarios/downtown_*.json.\n\nconst width = 96;\nconst height = 72;\nconst step = 32;\n\n// эстакада: перила в рядах 20 и 26, плита 21..25, рампы на концах\nconst overpass = { x0: 22, x1: 73, railNorth: 20, railSouth: 26 };\nconst overpassRampWest = { x0: 15, x1: 21 };\nconst overpassRampEast = { x0: 74, x1: 80 };\n// разрывы перил над проспектом — уступы\nconst overpassLedgeNorth = { x0: 38, x1: 40 };\nconst overpassLedgeSouth = { x0: 55, x1: 57 };\n\n// прыжок: бустер → рампа → крыша-парковка. От выхода рампы (x0) до\n// восточных перил 11 клеток плиты — площадка приземления с запасом\nconst parking = { x0: 24, x1: 36, y0: 30, y1: 40 };\nconst jumpBoost = { x0: 18, x1: 19, y0: 34, y1: 36 };\nconst jumpRamp = { x0: 20, x1: 23, y0: 34, y1: 36 };\nconst parkingLedge = { x0: 29, x1: 31 };\n\n// канал: стены в рядах 54 и 62, вода между ними\nconst canal = { wallNorth: 54, wallSouth: 62 };\n// мосты уровня 1 через канал: перила по колонкам x0/x1, плита между ними;\n// рампы по концам (северная — подъём на юг, южная — на север)\nconst bridges = [\n { x0: 27, x1: 31 },\n { x0: 64, x1: 68 },\n];\nconst bridgeDeck = { y0: 53, y1: 63 };\nconst bridgeRampNorth = { y0: 50, y1: 52 };\nconst bridgeRampSouth = { y0: 64, y1: 66 };\n// брод напротив проезда к центру и бустер на южном берегу\nconst ford = { x0: 45, x1: 50 };\nconst fordBoost = { x0: 47, x1: 48, y0: 64, y1: 65 };\n\n// здания: стены уровня 0, у `roof` — крыша уровня 1 (под вывески и\n// вентиляторы). Вокруг каждого — кольцо тротуара: край крыши обязан\n// выходить на проходимую землю\nconst buildings = [\n // промзона\n { x0: 4, x1: 10, y0: 3, y1: 7, roof: true },\n { x0: 25, x1: 31, y0: 3, y1: 7, roof: true },\n // север центра\n { x0: 37, x1: 44, y0: 3, y1: 15 },\n { x0: 51, x1: 58, y0: 3, y1: 15 },\n // стройка\n { x0: 85, x1: 91, y0: 3, y1: 7 },\n // Neon Strip\n { x0: 38, x1: 43, y0: 29, y1: 32, roof: true },\n { x0: 52, x1: 57, y0: 29, y1: 32, roof: true },\n { x0: 38, x1: 43, y0: 39, y1: 42, roof: true },\n { x0: 52, x1: 57, y0: 39, y1: 42, roof: true },\n // укрытие к востоку от центра (напротив парковки)\n { x0: 62, x1: 70, y0: 31, y1: 39 },\n // базы\n { x0: 3, x1: 8, y0: 45, y1: 50, roof: true },\n { x0: 87, x1: 92, y0: 45, y1: 50, roof: true },\n];\n\nconst grid = fill =>\n Array.from({ length: height }, () => Array(width).fill(fill));\n\nconst rect = (map, x0, y0, x1, y1, tile) => {\n for (let y = y0; y <= y1; y += 1) {\n for (let x = x0; x <= x1; x += 1) {\n map[y][x] = tile;\n }\n }\n};\n\nconst hline = (map, y, x0, x1, tile) => rect(map, x0, y, x1, y, tile);\nconst vline = (map, x, y0, y1, tile) => rect(map, x, y0, x, y1, tile);\n\nconst inRange = (value, from, to) => value >= from && value <= to;\n\n// пунктир разметки по чётным клеткам\nconst dashes = (map, y, x0, x1) => {\n for (let x = x0; x <= x1; x += 2) {\n if (map[y][x] === T.ASPHALT) {\n map[y][x] = T.LANE;\n }\n }\n};\n\nconst level0 = (function () {\n const map = grid(T.ASPHALT);\n\n // здания с кольцом тротуара\n for (const b of buildings) {\n rect(map, b.x0 - 1, b.y0 - 1, b.x1 + 1, b.y1 + 1, T.SIDEWALK);\n }\n\n for (const b of buildings) {\n rect(map, b.x0, b.y0, b.x1, b.y1, T.WALL);\n }\n\n // разметка: проспект под эстакадой, Neon Strip, южная дорога и берег\n dashes(map, 23, 2, 93);\n dashes(map, 35, 38, 44);\n dashes(map, 35, 51, 57);\n dashes(map, 47, 2, 93);\n dashes(map, 68, 2, 93);\n\n // промзона: встречные конвейеры по 12 клеток\n rect(map, 6, 11, 17, 12, T.CONVEYOR_E);\n rect(map, 16, 15, 27, 16, T.CONVEYOR_W);\n\n // стройка: пятна песка и грязи\n rect(map, 63, 9, 72, 15, T.SAND);\n rect(map, 80, 12, 90, 16, T.SAND);\n rect(map, 73, 3, 79, 8, T.MUD);\n rect(map, 75, 11, 79, 16, T.MUD);\n\n // масляное пятно 6 × 4 в центре перекрёстка\n rect(map, 45, 34, 50, 37, T.OIL);\n\n // канал: вода через всю ширину, стены с проёмами под мостами и у брода\n rect(map, 1, canal.wallNorth + 1, width - 2, canal.wallSouth - 1, T.WATER);\n\n for (const y of [canal.wallNorth, canal.wallSouth]) {\n for (let x = 1; x < width - 1; x += 1) {\n const open =\n inRange(x, ford.x0, ford.x1) ||\n bridges.some(bridge => inRange(x, bridge.x0, bridge.x1));\n\n if (!open) {\n map[y][x] = T.CANAL_WALL;\n }\n }\n }\n\n // бустеры: брод на север и разгон перед рампой парковки на восток\n rect(map, fordBoost.x0, fordBoost.y0, fordBoost.x1, fordBoost.y1, T.BOOST_N);\n rect(map, jumpBoost.x0, jumpBoost.y0, jumpBoost.x1, jumpBoost.y1, T.BOOST_E);\n\n // рампы лежат в гриде уровня 0\n rect(map, overpassRampWest.x0, overpass.railNorth + 1, overpassRampWest.x1, overpass.railSouth - 1, T.RAMP_E);\n rect(map, overpassRampEast.x0, overpass.railNorth + 1, overpassRampEast.x1, overpass.railSouth - 1, T.RAMP_W);\n rect(map, jumpRamp.x0, jumpRamp.y0, jumpRamp.x1, jumpRamp.y1, T.RAMP_E);\n\n for (const bridge of bridges) {\n rect(map, bridge.x0 + 1, bridgeRampNorth.y0, bridge.x1 - 1, bridgeRampNorth.y1, T.RAMP_S);\n rect(map, bridge.x0 + 1, bridgeRampSouth.y0, bridge.x1 - 1, bridgeRampSouth.y1, T.RAMP_N);\n }\n\n // периметр\n hline(map, 0, 0, width - 1, T.WALL);\n hline(map, height - 1, 0, width - 1, T.WALL);\n vline(map, 0, 0, height - 1, T.WALL);\n vline(map, width - 1, 0, height - 1, T.WALL);\n\n return map;\n})();\n\nconst level1 = (function () {\n const map = grid(T.EMPTY); // 0 — пустота: уровня здесь нет, виден нижний\n\n // крыши зданий (визуальные, но в `floor`: карта освещённости уровня\n // затемняет только его плиту)\n for (const b of buildings) {\n if (b.roof) {\n rect(map, b.x0, b.y0, b.x1, b.y1, T.ROOF);\n }\n }\n\n // эстакада: торцы — выходы рамп во всю ширину плиты, перила по длинным\n // сторонам кроме разрывов\n rect(map, overpass.x0, overpass.railNorth + 1, overpass.x1, overpass.railSouth - 1, T.SLAB);\n\n for (let x = overpass.x0; x <= overpass.x1; x += 1) {\n map[overpass.railNorth][x] = inRange(x, overpassLedgeNorth.x0, overpassLedgeNorth.x1)\n ? T.SLAB\n : T.RAILING;\n map[overpass.railSouth][x] = inRange(x, overpassLedgeSouth.x0, overpassLedgeSouth.x1)\n ? T.SLAB\n : T.RAILING;\n }\n\n // масляное пятно на эстакаде\n rect(map, 60, 22, 62, 24, T.OIL);\n\n // крыша-парковка: периметр — перила, кроме выхода рампы (запад) и\n // разрыва на южной стороне\n rect(map, parking.x0, parking.y0, parking.x1, parking.y1, T.SLAB);\n hline(map, parking.y0, parking.x0, parking.x1, T.RAILING);\n hline(map, parking.y1, parking.x0, parking.x1, T.RAILING);\n vline(map, parking.x0, parking.y0, parking.y1, T.RAILING);\n vline(map, parking.x1, parking.y0, parking.y1, T.RAILING);\n vline(map, parking.x0, jumpRamp.y0, jumpRamp.y1, T.SLAB);\n hline(map, parking.y1, parkingLedge.x0, parkingLedge.x1, T.SLAB);\n\n // мосты через канал: торцы — выходы рамп\n for (const bridge of bridges) {\n rect(map, bridge.x0 + 1, bridgeDeck.y0, bridge.x1 - 1, bridgeDeck.y1, T.SLAB);\n vline(map, bridge.x0, bridgeDeck.y0, bridgeDeck.y1, T.RAILING);\n vline(map, bridge.x1, bridgeDeck.y0, bridgeDeck.y1, T.RAILING);\n }\n\n return map;\n})();\n\n// центр тайла в НЕмасштабированных мировых единицах (scale накладывает\n// движок при раздаче MAP_DATA)\nconst at = (x, y) => [x * step + step / 2, y * step + step / 2];\n\n// у динамического объекта `position` — ЛЕВЫЙ ВЕРХНИЙ угол тела: тело\n// центрируется на клетке (x, y); забор в 2 клетки начинается с клетки x\nconst body = (x, y, w, h, extra) => ({\n layer: 3,\n position: [x * step + (step - w) / 2, y * step + (step - h) / 2],\n angle: 0,\n width: w,\n height: h,\n linearDamping: 6.0,\n angularDamping: 14.0,\n level: 0,\n ...extra,\n});\n\nconst fence = (x, y) => ({\n ...body(x, y, 64, 12, {}),\n position: [x * step, y * step + (step - 12) / 2],\n density: 30,\n img: 'prop_fence.png',\n game: { prop: 'fence', imgDestroyed: 'prop_fence_broken.png' },\n});\n\nconst crate = (x, y, level = 0) =>\n body(x, y, 32, 32, {\n density: 40,\n img: 'prop_crate.png',\n level,\n game: {\n prop: 'crate',\n imgDamaged: 'prop_crate_damaged.png',\n imgDestroyed: 'prop_crate_broken.png',\n },\n });\n\nconst barrel = (x, y) =>\n body(x, y, 24, 24, {\n density: 30,\n img: 'prop_barrel.png',\n game: { prop: 'barrel' },\n });\n\n// все респауны наземные, уровень 0 объявлен явно\nconst spawn = (x, y, angle) => [...at(x, y), angle, 0];\n\nconst lamp = (x, y, level = 0, extra = {}) => ({\n cell: [x, y],\n level,\n radius: 110,\n color: 0xffc070,\n intensity: 0.9,\n head: true,\n flicker: 0,\n ...extra,\n});\n\nconst every = (from, to, by, make) => {\n const out = [];\n\n for (let v = from; v <= to; v += by) {\n out.push(make(v));\n }\n\n return out;\n};\n\nconst belt = 60; // = coreParams.surfaces.types.conveyor.belt\n\nexport default {\n setId: 'c1',\n\n scale: 0.4,\n\n spriteSheet: {\n img: 'city.png',\n frames,\n },\n\n // рендер-слои уровня 0: стены канала — отдельный низкий объём, в общем\n // слое с 1.0 они вытянулись бы на полный этаж, как здания\n layers: {\n 1: [\n T.ASPHALT,\n T.SIDEWALK,\n T.LANE,\n T.SAND,\n T.MUD,\n T.WATER,\n T.OIL,\n T.CONVEYOR_E,\n T.CONVEYOR_W,\n T.BOOST_N,\n T.BOOST_E,\n T.RAMP_N,\n T.RAMP_S,\n T.RAMP_E,\n T.RAMP_W,\n ],\n 2: [T.WALL],\n 4: [T.CANAL_WALL],\n },\n\n volumes: {\n 2: 1.0,\n 4: 0.25,\n },\n\n step,\n\n physicsStatic: [T.WALL, T.CANAL_WALL],\n\n levels: {\n 1: {\n map: level1,\n floor: [T.SLAB, T.RAILING, T.ROOF, T.OIL],\n walls: [T.RAILING],\n // крыши — свой слой 2 (`game.roofs`): они непрозрачны, пока не\n // закрывают танк; вывески и вентиляторы стоят на нём, а не на перилах\n layers: {\n 1: [T.SLAB, T.OIL],\n 2: [T.ROOF],\n 4: [T.RAILING],\n },\n volumes: {\n 4: 0.35,\n },\n },\n },\n\n ramps: [\n { tile: T.RAMP_E, dir: 'east', from: 0, to: 1 },\n { tile: T.RAMP_W, dir: 'west', from: 0, to: 1 },\n { tile: T.RAMP_S, dir: 'south', from: 0, to: 1 },\n { tile: T.RAMP_N, dir: 'north', from: 0, to: 1 },\n ],\n\n physicsDynamic: [\n // промзона: ящики во дворе, бочки группой у западной ленты (цепная\n // реакция)\n crate(3, 11),\n crate(3, 13),\n crate(20, 10),\n crate(21, 10),\n crate(31, 15),\n crate(32, 15),\n barrel(28, 13),\n barrel(29, 13),\n barrel(28, 14),\n barrel(29, 14),\n // стройка: две линии заборов с объездом по краям, ящики\n fence(62, 8),\n fence(64, 8),\n fence(66, 8),\n fence(68, 8),\n fence(70, 8),\n fence(82, 11),\n fence(84, 11),\n fence(86, 11),\n fence(88, 11),\n fence(90, 11),\n crate(66, 4),\n crate(68, 4),\n crate(80, 4),\n crate(82, 4),\n // бочки у стены канала\n barrel(10, 63),\n barrel(85, 63),\n // ящики на крыше-парковке, в стороне от полосы приземления\n crate(34, 31, 1),\n crate(35, 31, 1),\n ],\n\n // первые точки команд — стартовые позиции сценариев\n // tests/scenarios/downtown_*.json (одна клавиша до цели)\n respawns: {\n team1: [\n // носом на восток: прямо на западную рампу эстакады\n spawn(9, 24, 0),\n // вдоль северного края рампы: под эстакадой\n spawn(4, 20, 0),\n // бустер → рампа → крыша-парковка\n spawn(9, 35, 0),\n // под парковкой к масляному пятну перекрёстка\n spawn(4, 37, 0),\n spawn(4, 29, 0),\n spawn(9, 29, 0),\n spawn(4, 42, 0),\n spawn(9, 42, 0),\n ],\n team2: [\n spawn(86, 24, 180),\n spawn(91, 24, 180),\n spawn(86, 35, 180),\n spawn(91, 37, 180),\n spawn(91, 29, 180),\n spawn(86, 29, 180),\n spawn(91, 42, 180),\n spawn(86, 42, 180),\n ],\n },\n\n game: {\n surfaces: {\n 0: {\n [T.SAND]: 'sand',\n [T.MUD]: 'mud',\n [T.WATER]: 'water',\n [T.OIL]: 'oil',\n [T.CONVEYOR_E]: { type: 'conveyor', dir: 'east' },\n [T.CONVEYOR_W]: { type: 'conveyor', dir: 'west' },\n [T.BOOST_N]: { type: 'boost', dir: 'north' },\n [T.BOOST_E]: { type: 'boost', dir: 'east' },\n },\n 1: {\n [T.OIL]: 'oil',\n },\n },\n\n lighting: {\n night: true,\n ambient: 0x3a4260,\n lamps: [\n // проспект: по обе стороны эстакады, не под плитой\n ...every(12, 84, 8, x => lamp(x, 19)),\n ...every(16, 80, 8, x => lamp(x, 27)),\n // перекрёсток Neon Strip\n lamp(44, 33, 0, { color: 0xffd9a0 }),\n lamp(51, 33, 0, { color: 0xffd9a0 }),\n lamp(44, 38, 0, { color: 0xffd9a0 }),\n lamp(51, 38, 0, { color: 0xffd9a0 }),\n // южная дорога и берег канала\n ...every(12, 84, 8, x => lamp(x, 45)),\n ...every(16, 80, 16, x => lamp(x, 68, 0, { flicker: 0.2 })),\n // базы и районы\n lamp(12, 32, 0, { color: 0xa0c8ff }),\n lamp(83, 32, 0, { color: 0xa0c8ff }),\n lamp(15, 9, 0, { flicker: 0.35 }),\n lamp(80, 10, 0, { flicker: 0.35 }),\n // свои фонари уровня 1: эстакада, парковка, мосты\n ...every(26, 70, 8, x => lamp(x, 23, 1, { radius: 90, color: 0xcfe0ff })),\n lamp(30, 35, 1, { radius: 100 }),\n lamp(29, 58, 1, { radius: 90 }),\n lamp(66, 58, 1, { radius: 90 }),\n ],\n },\n\n animatedTiles: {\n [T.WATER]: {\n kind: 'frames',\n frames: [T.WATER, T.WATER_2, T.WATER_3, T.WATER_4],\n fps: 4,\n },\n [T.CONVEYOR_E]: {\n kind: 'frames',\n frames: [T.CONVEYOR_E, T.CONVEYOR_E_2, T.CONVEYOR_E_3, T.CONVEYOR_E_4],\n speed: belt,\n },\n [T.CONVEYOR_W]: {\n kind: 'frames',\n frames: [T.CONVEYOR_W, T.CONVEYOR_W_2, T.CONVEYOR_W_3, T.CONVEYOR_W_4],\n speed: belt,\n },\n [T.BOOST_N]: { kind: 'frames', frames: [T.BOOST_N, T.BOOST_N_2], fps: 2 },\n [T.BOOST_E]: { kind: 'frames', frames: [T.BOOST_E, T.BOOST_E_2], fps: 2 },\n },\n\n // крыши уровня 1: отдельный рендер-слой, см. `levels[1].layers`\n roofs: { 1: [T.ROOF] },\n\n signs: [\n {\n cell: [40, 30],\n level: 1,\n layer: 2,\n text: 'HOTEL',\n color: 0xff3ad0,\n size: 18,\n flicker: { pulse: 0.15, dropouts: 0.1 },\n light: { radius: 80, intensity: 0.7 },\n },\n {\n cell: [55, 30],\n level: 1,\n layer: 2,\n text: 'BAR',\n color: 0x3ae0ff,\n size: 20,\n flicker: { pulse: 0.3, dropouts: 0.3 },\n light: { radius: 70, intensity: 0.6 },\n },\n {\n cell: [40, 41],\n level: 1,\n layer: 2,\n text: 'CLUB',\n color: 0x9a5aff,\n size: 18,\n angle: -8,\n flicker: { pulse: 0.05, dropouts: 0 },\n light: { radius: 80, intensity: 0.7 },\n },\n {\n cell: [55, 41],\n level: 1,\n layer: 2,\n text: 'MOTEL',\n color: 0xff5a3a,\n size: 16,\n flicker: { pulse: 0.2, dropouts: 0.5 },\n light: { radius: 70, intensity: 0.6 },\n },\n {\n cell: [5, 47],\n level: 1,\n layer: 2,\n text: 'GUNS',\n color: 0x5aff7a,\n size: 16,\n flicker: { pulse: 0.1, dropouts: 0.05 },\n light: { radius: 60, intensity: 0.5 },\n },\n {\n cell: [90, 47],\n level: 1,\n layer: 2,\n text: 'GUNS',\n color: 0x5aff7a,\n size: 16,\n flicker: { pulse: 0.1, dropouts: 0.2 },\n light: { radius: 60, intensity: 0.5 },\n },\n ],\n\n // вентиляторы на крышах промзоны\n decals: [\n { cell: [6, 5], level: 1, layer: 2, frame: T.FAN, kind: 'rotate', rps: 1.5 },\n { cell: [8, 5], level: 1, layer: 2, frame: T.FAN, kind: 'rotate', rps: 1.1 },\n { cell: [27, 5], level: 1, layer: 2, frame: T.FAN, kind: 'rotate', rps: 1.8 },\n { cell: [29, 5], level: 1, layer: 2, frame: T.FAN, kind: 'rotate', rps: 0.9 },\n ],\n },\n\n map: level0,\n};\n","const dynamicBaseDefenses = [];\nconst step = 32;\n\n// создает линию из динамических блоков (ящиков)\nconst createDefenseLine = (startX, startY, count, isVertical) => {\n for (let i = 0; i < count; i += 1) {\n const position = isVertical\n ? [startX * step, (startY + i) * step]\n : [(startX + i) * step, startY * step];\n\n dynamicBaseDefenses.push({\n density: 100,\n layer: 3,\n position,\n angle: 0,\n img: 'b1.png',\n width: 32,\n height: 32,\n linearDamping: 5.0,\n angularDamping: 14.0,\n });\n }\n};\n\n// L-образная стена с разрывом\n// база 1 (верхний левый угол)\n// верхний левый сегмент (10 блоков)\ncreateDefenseLine(30, 2, 10, true); // вертикальная\ncreateDefenseLine(2, 30, 10, false); // горизонтальная\n// нижний правый сегмент (9 блоков)\ncreateDefenseLine(30, 22, 9, true); // вертикальная\ncreateDefenseLine(22, 30, 9, false); // горизонтальная\n\nconst end = 120;\nconst wallPos = 30;\n// база 2 (нижний правый угол)\n// верхний левый сегмент (10 блоков)\ncreateDefenseLine(end - wallPos - 1, end - 12, 10, true); // вертикальная\ncreateDefenseLine(end - 12, end - wallPos - 1, 10, false); // горизонтальная\n// нижний правый сегмент (9 блоков)\ncreateDefenseLine(end - wallPos - 1, end - 31, 9, true); // вертикальная\ncreateDefenseLine(end - 31, end - wallPos - 1, 9, false); // горизонтальная\n\nexport default {\n setId: 'c1',\n\n spriteSheet: {\n img: 'tiles3.png',\n frames: [\n [96, 0, 32, 32], // бетон\n [32, 32, 32, 32], // трава\n [64, 32, 32, 32], // ограждения\n [0, 96, 32, 32], // вода\n ],\n },\n\n layers: {\n 1: [0, 1, 2], // слои под танком\n 4: [3], // слой над танком (вода в оазисе)\n },\n\n step: 32,\n\n physicsStatic: [2], // препятствия\n\n physicsDynamic: [...dynamicBaseDefenses],\n\n respawns: {\n team1: [\n [320, 320, 45],\n [320, 480, 45],\n [320, 640, 45],\n [480, 320, 45],\n [480, 480, 45],\n [480, 640, 45],\n [640, 320, 45],\n [640, 480, 45],\n [640, 640, 45],\n [800, 400, 45],\n ],\n team2: [\n [3520, 3520, 225],\n [3520, 3360, 225],\n [3520, 3200, 225],\n [3360, 3520, 225],\n [3360, 3360, 225],\n [3360, 3200, 225],\n [3200, 3520, 225],\n [3200, 3360, 225],\n [3200, 3200, 225],\n [3040, 3440, 225],\n ],\n },\n\n map: (function () {\n const width = 120;\n const height = 120;\n const tiles = { stone: 0, grass: 1, wall: 2, water: 3 };\n\n const map = Array.from({ length: height }, () =>\n Array(width).fill(tiles.grass),\n );\n\n const drawBorder = () => {\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {\n map[y][x] = tiles.wall;\n }\n }\n }\n };\n\n const placeRocks = (cx, cy, radius, density = 0.5) => {\n for (let y = cy - radius; y <= cy + radius; y += 1) {\n for (let x = cx - radius; x <= cx + radius; x += 1) {\n if (\n x > 0 &&\n x < width - 1 &&\n y > 0 &&\n y < height - 1 &&\n Math.sqrt((x - cx) ** 2 + (y - cy) ** 2) <= radius\n ) {\n if (Math.random() < density) {\n map[y][x] = tiles.wall;\n }\n }\n }\n }\n };\n\n drawBorder();\n\n const oasisCx = Math.floor(width / 2);\n const oasisCy = Math.floor(height / 2);\n const oasisRadius = 22;\n\n for (let y = oasisCy - oasisRadius; y <= oasisCy + oasisRadius; y += 1) {\n for (let x = oasisCx - oasisRadius; x <= oasisCx + oasisRadius; x += 1) {\n if (Math.sqrt((x - oasisCx) ** 2 + (y - oasisCy) ** 2) <= oasisRadius) {\n map[y][x] = tiles.stone;\n }\n }\n }\n for (let y = oasisCy - 10; y <= oasisCy + 10; y += 1) {\n for (let x = oasisCx - 10; x <= oasisCx + 10; x += 1) {\n if (Math.sqrt((x - oasisCx) ** 2 + (y - oasisCy) ** 2) <= 10) {\n map[y][x] = tiles.water;\n }\n }\n }\n\n // деревья на карте для укрытий\n placeRocks(35, 85, 12, 0.7);\n placeRocks(85, 35, 12, 0.7);\n placeRocks(20, 60, 8, 0.6);\n placeRocks(60, 20, 8, 0.6);\n placeRocks(width - 20, height - 60, 8, 0.6);\n placeRocks(width - 60, height - 20, 8, 0.6);\n\n return map;\n })(),\n};\n","// Демо-карта многоуровневых карт (2.5D): земля (уровень 0) и сквозная\n// эстакада (уровень 1) шириной в 5 тайлов проезжей плиты, с двумя\n// рампами, перилами и двумя разрывами перил.\n// Она же — фикстура отладочных сценариев tests/scenarios/bridge.json,\n// fall.json, crosslevel.json, bots_bridge.json.\n\nconst width = 80;\nconst height = 60;\nconst step = 32;\n\n// тайлы (индексы в spriteSheet.frames)\nconst tiles = {\n ground: 0, // асфальт, уровень 0\n wall: 1, // стена здания, уровень 0 (physicsStatic)\n slab: 2, // плита моста, уровень 1 (floor)\n railing: 3, // перила моста, уровень 1 (floor + walls)\n rampNorth: 4, // рампа, подъём на север, уровень 0\n rampSouth: 5, // рампа, подъём на юг, уровень 0\n};\n\n// геометрия эстакады: 5 тайлов проезжей плиты (26..30) плюс перила\n// по обеим длинным сторонам (25 и 31). Пять, а не три: замеренный горизонт\n// прыжка с рампы на этой карте — 3.5 тайла (потолок при maxLaunchVz — около\n// шести, см. docs/ru/extending.md), а на трёхтайловой плите отрыв с ряда 31\n// клал танк ровно в дальние перила\nconst bridge = {\n railNorth: 25,\n slabTop: 26,\n slabBottom: 30,\n railSouth: 31,\n left: 2, // внутренняя кромка периметральной стены\n right: width - 3,\n};\n\n// рампы: длина и ширина по 3 тайла. Северная стоит ЮЖНЕЕ моста (едешь на\n// север — поднимаешься), южная СЕВЕРНЕЕ моста (едешь на юг — поднимаешься).\n// В строке перил напротив каждой рампы — проём, иначе въезд заперт.\nconst rampNorth = { x0: 3, x1: 5, y0: 32, y1: 34 };\nconst rampSouth = { x0: 74, x1: 76, y0: 22, y1: 24 };\n\n// разрывы перил — обрывы, с которых падают и через которые простреливают\n// снизу: один с северной стороны, один с южной\nconst ledgeNorth = { x0: 30, x1: 33 };\nconst ledgeSouth = { x0: 48, x1: 51 };\n\nconst grid = fill =>\n Array.from({ length: height }, () => Array(width).fill(fill));\n\nconst inRange = (value, from, to) => value >= from && value <= to;\n\nconst level0 = (function () {\n const map = grid(tiles.ground);\n\n // периметр\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n if (y < 2 || y >= height - 2 || x < 2 || x >= width - 2) {\n map[y][x] = tiles.wall;\n }\n }\n }\n\n // рампы лежат в гриде уровня 0 — по ним едут, оставаясь на земле,\n // пока z не перевалит за половину (core/src/level.rs)\n for (let y = rampNorth.y0; y <= rampNorth.y1; y += 1) {\n for (let x = rampNorth.x0; x <= rampNorth.x1; x += 1) {\n map[y][x] = tiles.rampNorth;\n }\n }\n\n for (let y = rampSouth.y0; y <= rampSouth.y1; y += 1) {\n for (let x = rampSouth.x0; x <= rampSouth.x1; x += 1) {\n map[y][x] = tiles.rampSouth;\n }\n }\n\n return map;\n})();\n\nconst level1 = (function () {\n const map = grid(0); // 0 — пустота: уровня здесь нет, виден нижний\n\n for (let x = bridge.left; x <= bridge.right; x += 1) {\n for (let y = bridge.slabTop; y <= bridge.slabBottom; y += 1) {\n map[y][x] = tiles.slab;\n }\n\n // перила по обеим длинным сторонам, кроме проёмов под рампы и\n // разрывов-обрывов\n const openNorth =\n inRange(x, rampSouth.x0, rampSouth.x1) ||\n inRange(x, ledgeNorth.x0, ledgeNorth.x1);\n const openSouth =\n inRange(x, rampNorth.x0, rampNorth.x1) ||\n inRange(x, ledgeSouth.x0, ledgeSouth.x1);\n\n map[bridge.railNorth][x] = openNorth ? tiles.slab : tiles.railing;\n map[bridge.railSouth][x] = openSouth ? tiles.slab : tiles.railing;\n\n // торцы: без них с конца моста уезжают в пустоту мимо периметра —\n // стены уровня 0 танку на плите не преграда\n if (x === bridge.left || x === bridge.right) {\n for (let y = bridge.slabTop; y <= bridge.slabBottom; y += 1) {\n map[y][x] = tiles.railing;\n }\n }\n }\n\n return map;\n})();\n\n// центр тайла в НЕмасштабированных мировых единицах (scale накладывает\n// движок при раздаче MAP_DATA)\nconst at = (x, y) => [x * step + step / 2, y * step + step / 2];\n\n// у динамического объекта `position` — ЛЕВЫЙ ВЕРХНИЙ угол тела, а не центр\n// (как в canopy.js), поэтому ящик 64 × 64 ставим так, чтобы он целиком лёг\n// на тайл (x, y) и не задел перила соседних строк\nconst box = (x, y, level, size = 64) => ({\n density: 40,\n layer: 3,\n position: [x * step, y * step + (step - size) / 2],\n angle: 0,\n width: size,\n height: size,\n img: 'b1.png',\n linearDamping: 6.0,\n angularDamping: 14.0,\n level,\n});\n\n// [x, y, angle] — уровень выведется из геометрии (0, если точка не под\n// плитой); [x, y, angle, level] — уровень задан явно\nconst spawn = (x, y, angle) => [...at(x, y), angle];\nconst spawnOn = (x, y, angle, level) => [...at(x, y), angle, level];\n\nexport default {\n setId: 'c1',\n\n scale: 0.4,\n\n // кадры взяты из общего листа assets/img/tiles.png (544 × 288,\n // сетка 32 × 32 ⇒ 17 колонок × 9 строк). Собственный тайл-лист города —\n // отдельная задача; менять надо только эти шесть координат\n spriteSheet: {\n img: 'tiles.png',\n frames: [\n [480, 64, 32, 32], // 0: асфальт\n [128, 224, 32, 32], // 1: стена здания\n [288, 64, 32, 32], // 2: плита моста\n [352, 96, 32, 32], // 3: перила моста\n [352, 64, 32, 32], // 4: рампа, подъём на север\n [384, 64, 32, 32], // 5: рампа, подъём на юг\n ],\n },\n\n // рендер-слои уровня 0\n layers: {\n 1: [tiles.ground, tiles.rampNorth, tiles.rampSouth], // под танком\n 2: [tiles.wall],\n },\n\n // высоты объёмов: здания уровня 0 — в целый уровень\n volumes: {\n 2: 1.0,\n },\n\n step,\n\n physicsStatic: [tiles.wall],\n\n // надземные уровни; ключ — номер уровня строкой\n levels: {\n 1: {\n map: level1,\n floor: [tiles.slab, tiles.railing],\n walls: [tiles.railing],\n // zIndex внутри уровня; движок сдвигает его на LEVEL_Z_STRIDE\n layers: {\n 1: [tiles.slab], // плита под танком (zIndex 101)\n 4: [tiles.railing], // перила над танком (zIndex 104)\n },\n // перила — низкий объём: экструзия в четверть уровня\n volumes: {\n 4: 0.35,\n },\n },\n },\n\n ramps: [\n { tile: tiles.rampNorth, dir: 'north', from: 0, to: 1 },\n { tile: tiles.rampSouth, dir: 'south', from: 0, to: 1 },\n ],\n\n // 2 ящика на земле под мостом и 2 на плите — проверка `level` динамики.\n // Мостовые ящики — по одному тайлу, у самых разрывов перил: у динамики\n // карты есть правила уровня, поэтому вытолкнутый в разрыв ящик падает на\n // землю и меняет уровень, а не висит над пустотой.\n // Ряд 29 плиты свободен — по нему идут отладочные сценарии\n physicsDynamic: [\n box(20, 28, 0),\n box(60, 28, 0),\n box(31, bridge.slabTop, 1, 32),\n box(49, bridge.slabBottom, 1, 32),\n ],\n\n // по 10 точек: 8 наземных (уровень выводится) и 2 мостовых (явный 1).\n // Ни одна наземная точка не стоит под плитой — иначе выведенный уровень\n // оказался бы 1\n respawns: {\n team1: [\n // первая точка — у подножия западной рампы, носом на север: с неё\n // въезд на мост — одна клавиша (tests/scenarios/bridge.json)\n spawn(3, 38, 270),\n spawn(9, 38, 0),\n spawn(6, 6, 0),\n spawn(12, 6, 0),\n spawn(6, 14, 0),\n spawn(12, 14, 0),\n spawn(6, 22, 0),\n spawn(12, 22, 0),\n spawnOn(10, 29, 0, 1),\n spawnOn(16, 29, 0, 1),\n ],\n team2: [\n // первая точка — под южным разрывом перил, носом на север: отсюда\n // видно кромку моста снизу (tests/scenarios/crosslevel.json)\n spawn(49, 36, 270),\n spawn(70, 38, 180),\n spawn(73, 6, 180),\n spawn(67, 6, 180),\n spawn(73, 14, 180),\n spawn(67, 14, 180),\n spawn(73, 22, 180),\n spawn(67, 22, 180),\n spawnOn(69, 29, 180, 1),\n spawnOn(63, 29, 180, 1),\n ],\n },\n\n map: level0,\n};\n","export default {\n setId: 'c1',\n\n scale: 0.6,\n\n spriteSheet: {\n img: 'tiles.png',\n frames: [\n [480, 64, 32, 32],\n [128, 224, 32, 32],\n [160, 0, 32, 32],\n [0, 64, 32, 32],\n [0, 64, 32, 32],\n ],\n },\n\n layers: {\n 1: [0, 2, 4],\n 2: [1],\n 4: [3],\n },\n\n physicsStatic: [1],\n\n physicsDynamic: [],\n\n step: 32,\n\n respawns: {\n team1: [\n [130, 520, 0],\n [130, 620, 0],\n [130, 720, 0],\n [130, 820, 0],\n [230, 520, 0],\n [230, 620, 0],\n [230, 720, 0],\n [230, 820, 0],\n [330, 620, 0],\n [330, 720, 0],\n ],\n team2: [\n [1600, 520, 180],\n [1600, 620, 180],\n [1600, 720, 180],\n [1600, 820, 180],\n [1500, 520, 180],\n [1500, 620, 180],\n [1500, 720, 180],\n [1500, 820, 180],\n [1400, 620, 180],\n [1400, 720, 180],\n ],\n },\n\n map: (function () {\n const originalMap = [\n [\n 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n 1, 1, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 4, 4, 4, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 3, 3, 3, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 3, 3, 3, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 4, 3, 3, 3, 4, 2, 2, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 3, 3, 3, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 4, 4, 4, 4, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n 0, 0, 1,\n ],\n [\n 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n 1, 1, 1,\n ],\n ];\n\n const newMap = [];\n\n for (const row of originalMap) {\n const newRow1 = [];\n const newRow2 = [];\n for (const tile of row) {\n newRow1.push(tile, tile);\n newRow2.push(tile, tile);\n }\n newMap.push(newRow1, newRow2); // дублируем строки для высоты\n }\n\n return newMap;\n })(),\n};\n","// Демо-карта многоуровневых карт (2.5D) на ТРИ уровня: земля (0), нижняя\n// терраса (1) и верхняя площадка (2). Здесь видно каждую возможность второй\n// итерации:\n//\n// * рампа 0 → 2 одним прогоном (`rampSteep`) — над её клетками плиты\n// уровня 1 нет, иначе подъём прошивал бы чужую плиту;\n// * ступенчатый путь 0 → 1 → 2 двумя рампами (`rampLong` + `rampStep`);\n// * `rampSide` — рампа, верхний торец которой упирается в проезд уровня 0\n// под плитой: снизу к нему подъезжают, но подняться нельзя (гейт входа\n// пускает на прогон только с торца, отвечающего уровню танка);\n// * ящики уровней 1 и 2 стоят у разрывов перил и падают — на нижнюю плиту\n// или на землю, смотря что под обрывом;\n// * `volumes` — высоты рендер-слоёв: здания уровня 0 и перила уровней 1—2;\n// * разная крутизна: короткий крутой прогон и длинный пологий.\n//\n// Она же — фикстура отладочных сценариев tests/scenarios/terraces_climb.json,\n// terraces_backside.json, terraces_crate.json.\n\nconst width = 72;\nconst height = 48;\nconst step = 32;\n\n// тайлы (индексы в spriteSheet.frames)\nconst tiles = {\n ground: 0, // асфальт, уровень 0\n wall: 1, // стена здания, уровень 0 (physicsStatic)\n slab1: 2, // плита нижней террасы, уровень 1 (floor)\n railing1: 3, // перила нижней террасы, уровень 1 (floor + walls)\n slab2: 4, // плита верхней площадки, уровень 2 (floor)\n railing2: 5, // перила верхней площадки, уровень 2 (floor + walls)\n rampLong: 6, // длинный пологий прогон 0 → 1, подъём на восток\n rampSide: 7, // прогон 0 → 1, подъём на север, торцом в проезд под плитой\n rampSteep: 8, // короткий крутой прогон 0 → 2, подъём на запад\n rampStep: 9, // прогон 1 → 2, подъём на восток (лежит в гриде уровня 1)\n};\n\n// нижняя терраса и верхняя площадка. Площадка перекрывает террасу с запада\n// (x 34..46) и вылезает за неё на восток (x 47..52): за восточной кромкой\n// плиты уровня 1 нет — только там и могла лечь рампа 0 → 2\nconst terrace = { x0: 14, x1: 46, y0: 12, y1: 30 };\nconst upper = { x0: 34, x1: 52, y0: 16, y1: 26 };\n\n// прогоны. Крутизна — перепад уровней на длину прогона: 1 уровень на 9\n// клеток у `rampLong` против 2 уровней на 4 клетки у `rampSteep`\nconst rampLong = { x0: 5, x1: 13, y: 21 };\nconst rampSide = { x: 24, y0: 31, y1: 34 };\nconst rampSteep = { x0: 53, x1: 56, y: 21 };\nconst rampStep = { x0: 31, x1: 33, y: 21 };\n\n// разрывы перил — обрывы, с которых падают: северный разрыв террасы отдаёт\n// на землю, южный разрыв площадки — на плиту террасы этажом ниже\nconst ledgeTerrace = { x0: 20, x1: 22 };\nconst ledgeUpper = { x0: 40, x1: 42 };\n\n// здания уровня 0: им и отданы `volumes` — объём виден по смещению стен\n// относительно камеры. Ни одно не стоит под обрывом: приземлиться нужно на\n// проходимую землю\nconst buildings = [\n { x0: 8, x1: 11, y0: 14, y1: 17 },\n { x0: 60, x1: 63, y0: 30, y1: 33 },\n];\n\nconst grid = fill =>\n Array.from({ length: height }, () => Array(width).fill(fill));\n\nconst inRange = (value, from, to) => value >= from && value <= to;\n\nconst level0 = (function () {\n const map = grid(tiles.ground);\n\n // периметр\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n if (y < 2 || y >= height - 2 || x < 2 || x >= width - 2) {\n map[y][x] = tiles.wall;\n }\n }\n }\n\n for (const building of buildings) {\n for (let y = building.y0; y <= building.y1; y += 1) {\n for (let x = building.x0; x <= building.x1; x += 1) {\n map[y][x] = tiles.wall;\n }\n }\n }\n\n // прогоны лежат в гриде того уровня, С которого идут: три из четырёх — в\n // гриде земли\n for (let x = rampLong.x0; x <= rampLong.x1; x += 1) {\n map[rampLong.y][x] = tiles.rampLong;\n }\n\n for (let y = rampSide.y0; y <= rampSide.y1; y += 1) {\n map[y][rampSide.x] = tiles.rampSide;\n }\n\n for (let x = rampSteep.x0; x <= rampSteep.x1; x += 1) {\n map[rampSteep.y][x] = tiles.rampSteep;\n }\n\n return map;\n})();\n\n// плита прямоугольником: периметр — перила, кроме проёмов (выходы рамп) и\n// разрывов (обрывы). Перила обязаны входить и в `floor`, иначе они не\n// экранируют луч снизу\nconst slabRect = (map, rect, floorTile, wallTile, isOpen) => {\n for (let y = rect.y0; y <= rect.y1; y += 1) {\n for (let x = rect.x0; x <= rect.x1; x += 1) {\n const edge =\n x === rect.x0 || x === rect.x1 || y === rect.y0 || y === rect.y1;\n\n map[y][x] = edge && !isOpen(x, y) ? wallTile : floorTile;\n }\n }\n};\n\nconst level1 = (function () {\n const map = grid(0); // 0 — пустота: уровня здесь нет, виден нижний\n\n slabRect(\n map,\n terrace,\n tiles.slab1,\n tiles.railing1,\n (x, y) =>\n // выход длинной рампы на западном торце\n (x === terrace.x0 && y === rampLong.y) ||\n // выход рампы из-под плиты на южном торце\n (y === terrace.y1 && x === rampSide.x) ||\n // разрыв перил на северном торце\n (y === terrace.y0 && inRange(x, ledgeTerrace.x0, ledgeTerrace.x1)),\n );\n\n // прогон 1 → 2 идёт по плите террасы: его клетки — часть `floor`\n for (let x = rampStep.x0; x <= rampStep.x1; x += 1) {\n map[rampStep.y][x] = tiles.rampStep;\n }\n\n return map;\n})();\n\nconst level2 = (function () {\n const map = grid(0);\n\n slabRect(\n map,\n upper,\n tiles.slab2,\n tiles.railing2,\n (x, y) =>\n // выход ступенчатой рампы на западном торце\n (x === upper.x0 && y === rampStep.y) ||\n // выход крутой рампы на восточном торце\n (x === upper.x1 && y === rampSteep.y) ||\n // разрыв перил на южном торце: под ним плита террасы\n (y === upper.y1 && inRange(x, ledgeUpper.x0, ledgeUpper.x1)),\n );\n\n return map;\n})();\n\n// центр тайла в НЕмасштабированных мировых единицах (scale накладывает\n// движок при раздаче MAP_DATA)\nconst at = (x, y) => [x * step + step / 2, y * step + step / 2];\n\n// у динамического объекта `position` — ЛЕВЫЙ ВЕРХНИЙ угол тела, а не центр\n// (как в canopy.js), поэтому ящик ставим так, чтобы он целиком лёг на тайл\nconst box = (x, y, level, size = 64) => ({\n density: 40,\n layer: 3,\n position: [x * step, y * step + (step - size) / 2],\n angle: 0,\n width: size,\n height: size,\n img: 'b1.png',\n linearDamping: 6.0,\n angularDamping: 14.0,\n level,\n});\n\n// [x, y, angle] — уровень выведется из геометрии (точка под плитой отдаст\n// танк НАВЕРХ); [x, y, angle, level] — уровень задан явно\nconst spawn = (x, y, angle) => [...at(x, y), angle];\nconst spawnOn = (x, y, angle, level) => [...at(x, y), angle, level];\n\nexport default {\n setId: 'c1',\n\n scale: 0.4,\n\n // кадры взяты из общего листа assets/img/tiles.png (544 × 288,\n // сетка 32 × 32 ⇒ 17 колонок × 9 строк)\n spriteSheet: {\n img: 'tiles.png',\n frames: [\n [480, 64, 32, 32], // 0: асфальт\n [128, 224, 32, 32], // 1: стена здания\n [288, 64, 32, 32], // 2: плита террасы\n [352, 96, 32, 32], // 3: перила террасы\n [96, 0, 32, 32], // 4: плита верхней площадки\n [64, 32, 32, 32], // 5: перила верхней площадки\n [352, 64, 32, 32], // 6: длинная рампа 0 → 1\n [384, 64, 32, 32], // 7: рампа 0 → 1 из-под плиты\n [416, 64, 32, 32], // 8: крутая рампа 0 → 2\n [320, 64, 32, 32], // 9: рампа 1 → 2\n ],\n },\n\n // рендер-слои уровня 0\n layers: {\n 1: [tiles.ground, tiles.rampLong, tiles.rampSide, tiles.rampSteep],\n 2: [tiles.wall],\n },\n\n // высоты объёмов уровня 0: здания в целый уровень\n volumes: {\n 2: 1.0,\n },\n\n step,\n\n physicsStatic: [tiles.wall],\n\n // надземные уровни; ключ — номер уровня строкой\n levels: {\n 1: {\n map: level1,\n floor: [tiles.slab1, tiles.railing1, tiles.rampStep],\n walls: [tiles.railing1],\n // zIndex внутри уровня; движок сдвигает его на LEVEL_Z_STRIDE\n layers: {\n 1: [tiles.slab1, tiles.rampStep], // плита под танком\n 4: [tiles.railing1], // перила над танком\n },\n volumes: {\n 4: 0.35,\n },\n },\n 2: {\n map: level2,\n floor: [tiles.slab2, tiles.railing2],\n walls: [tiles.railing2],\n layers: {\n 1: [tiles.slab2],\n 4: [tiles.railing2],\n },\n volumes: {\n 4: 0.35,\n },\n },\n },\n\n ramps: [\n { tile: tiles.rampLong, dir: 'east', from: 0, to: 1 },\n { tile: tiles.rampSide, dir: 'north', from: 0, to: 1 },\n { tile: tiles.rampSteep, dir: 'west', from: 0, to: 2 },\n { tile: tiles.rampStep, dir: 'east', from: 1, to: 2 },\n ],\n\n // ящики у разрывов перил (падают: с площадки — на плиту террасы, с\n // террасы — на землю) и два на земле\n physicsDynamic: [\n box(41, 25, 2, 32),\n box(21, 13, 1, 32),\n box(8, 24, 0),\n box(62, 38, 0),\n ],\n\n // по 8 точек на команду. Уровень 1 и 2 объявлены явно; наземные точки\n // стоят вне плит — под плитой выведенный из геометрии уровень был бы 1,\n // поэтому единственная точка под террасой объявляет свой 0 явно\n respawns: {\n team1: [\n // подножие крутой рампы, носом на запад: отсюда 0 → 2 одним прогоном\n // (tests/scenarios/terraces_climb.json)\n spawn(58, 21, 180),\n // на верхней площадке севернее ящика, носом на юг: ящик сталкивается\n // в разрыв перил (tests/scenarios/terraces_crate.json)\n spawnOn(41, 22, 90, 2),\n spawnOn(20, 20, 0, 1),\n spawnOn(45, 18, 180, 2),\n spawn(5, 8, 0),\n spawn(9, 8, 0),\n spawn(5, 40, 0),\n spawn(9, 40, 0),\n ],\n team2: [\n // под плитой террасы, носом на юг: впереди верхний торец rampSide —\n // подъехать можно, подняться нельзя\n // (tests/scenarios/terraces_backside.json)\n spawnOn(24, 28, 90, 0),\n // западнее того же прогона, носом на восток: заезд сбоку тоже не\n // поднимает (tests/scenarios/terraces_backside.json)\n spawn(20, 33, 0),\n spawnOn(21, 15, 270, 1),\n spawnOn(50, 21, 180, 2),\n spawnOn(40, 28, 180, 1),\n spawn(60, 8, 180),\n spawn(64, 8, 180),\n spawn(60, 40, 180),\n ],\n },\n\n map: level0,\n};\n","import { withWallHeights } from './wallHeights.js';\nimport canopy from './canopy.js';\nimport downtown from './downtown.js';\nimport garden from './garden.js';\nimport overpass from './overpass.js';\nimport poolMini from './pool_mini.js';\nimport terraces from './terraces.js';\n\n// `game.wallHeights` — высоты стен для пули (./wallHeights.js)\nexport default Object.fromEntries(\n Object.entries({\n canopy,\n downtown,\n garden,\n overpass,\n 'pool mini': poolMini,\n terraces,\n }).map(([name, map]) => [name, withWallHeights(map)]),\n);\n","export default {\n // tank\n m1: {\n constructor: 'Tank',\n currentWeapon: 'w1',\n // соотношение сторон танка 4:3, то есть widht: size*4, height: size*3\n size: 3,\n // высота ствола над полом, мировые единицы: пуля hitscan летит на ней и\n // упирается в насыпь рампы, если та выше (core/src/shot_height.rs).\n // = tankModel.barrelHeight 8 px · size 3 / 10 (src/config/render.js)\n barrelHeight: 2.4,\n // высота танка над полом (верх башни), мировые единицы: пуля поражает\n // танк, только если он дорастает до неё (core/src/shot_height.rs,\n // `tank_reaches`). = tankModel.turretTop 10 px · size 3 / 10\n // (src/config/render.js)\n turretTop: 3.0,\n\n // коэффициент ускорения\n // (чем выше, тем быстрее танк достигает максимальной скорости)\n accelerationFactor: 1000,\n // коэффициент торможения\n // (чем выше, тем резче танк останавливается)\n brakingFactor: 0.3,\n\n // целевая максимальная скорость вперед (юнитов/с)\n maxForwardSpeed: 260,\n // целевая максимальная скорость назад (юнитов/с)\n // (значение должно быть отрицательным)\n maxReverseSpeed: -130,\n\n // коэффициент желаемой интенсивности поворота (зависит от инерции)\n baseTurnTorqueFactor: 215,\n // сопротивление движению\n // (чем выше значение, тем больше сопротивление)\n damping: {\n // сопротивление при движении\n linear: 3,\n // сопротивление вращению (при повороте)\n angular: 100.0,\n },\n // физические свойства\n fixture: {\n density: 200, // плотность (0+)\n friction: 0.5, // трение (0 - 1)\n restitution: 0.1, // отскок при столкновении (0 - 1)\n },\n // сила бокового сцепления (сопротивление заносу)\n // чем выше значение, тем меньше занос/скольжение\n lateralGrip: 20,\n\n // манера вождения (используется сервером и клиентской репликой движения)\n // порог скорости для фактора поворота\n turnSpeedThreshold: 10,\n // фактор поворота при низкой скорости\n baseTurnFactorRatio: 0.8,\n // множитель эффективности поворота при движении назад (меньше 1)\n reverseTurnMultiplier: 0.7,\n // как быстро «нажимается педаль газа» (единиц в секунду)\n throttleIncreaseRate: 2.0,\n // как быстро «отпускается педаль газа»\n throttleDecreaseRate: 2.5,\n // коэффициент, усиливающий нагрузку двигателя при сопротивлении (звук)\n strainFactor: 1.5,\n // максимальный угол поворота башни\n maxGunAngle: 1.4,\n // скорость поворота башни\n gunRotationSpeed: 3.0,\n // скорость поворота башни в центр\n gunCenterSpeed: 10.0,\n },\n};\n","export default {\n // bullet\n w1: {\n type: 'hitscan',\n // сила импульса (кг*м/с); больше не масштабируется дальностью выстрела\n // в ядре (TanksSim::process_hitscan) — значение выбрано равным старому\n // эффективному импульсу (impulseMagnitude·range = 5000·1500)\n impulseMagnitude: 7500000,\n damage: 40, // урон от одного попадания\n range: 1500, // максимальная дальность выстрела (в игровых юнитах)\n fireRate: 0.01, // кулдаун между выстрелами (0 - отсутствует)\n spread: 0, // разброс в радианах (0 для идеальной точности)\n consumption: 1, // расход патронов за один выстрел\n cameraShake: {\n intensity: 20, // сила тряски (в пикселях)\n duration: 200, // продолжительность (в миллисекундах)\n },\n },\n\n // bomb\n w2: {\n type: 'explosive',\n constructor: 'Bomb',\n time: 300,\n shotOutcomeId: 'w2e', // id конструктора для детонации бомбы\n size: 8, // соотношение сторон 1:1\n fireRate: 0.1, // кулдаун (0 - отсутствует)\n damage: 70, // урон в эпицентре\n radius: 50, // радиус взрыва\n impulseMagnitude: 2000000, // сила импульса\n cameraShake: {\n intensity: 30, // сила тряски (в пикселях)\n duration: 400, // продолжительность (в миллисекундах)\n },\n },\n};\n","// Снапшот-схема игры (HostPlugin.gameConfig.snapshot): реестр ключей —\n// строковый ключ → числовой id + форма блока (kind — ширина count/id,\n// наличие null-маркера) + класс (class: 'hot' — интерполируется клиентом\n// между кадрами, 'event' — одноразовый, кадром как есть) + схема полей\n// строки (fields: порядок байтов в раскладке). Движок читает её из\n// gameConfig (lib/coreConfig.js) и шлёт клиенту в CONFIG_DATA\n// (lib/buildClientConfig.js) — сам движок раскладку не знает.\n// Новое оружие/карта обязаны быть зарегистрированы здесь.\n// optionalFrom: индекс первого поля опционального хвоста — поля с него\n// пишутся в кадр только у движущихся тел, наличие задаёт флаг-байт перед\n// строкой (покоящийся ящик не платит за скорости). Распаковка всё равно\n// отдаёт строку полной ширины: отсутствующий хвост читается нулями.\n// ВНИМАНИЕ: порядок и interp полей позиционно привязаны к Row-структурам\n// packages/engine/core/src/snapshot.rs — interpolator.rs читает interp по\n// индексу поля (schema.fields[i]), не по имени. Переставлять поля местами\n// или менять interp без синхронной правки Rust-структур нельзя: validate()\n// проверяет только количество и тип полей, не interp и не порядок по смыслу.\n// Единственное f32-поле m1, которое НЕ интерполируется, — vz: оно работает\n// детектором касания (см. src/client/landing.js), а сглаженная выборка\n// точного нуля не даёт. У u8-полей (condition, size, team, level) interp не\n// объявлен вовсе: движковый дефолт и так 'discrete'.\n// Тип поля при этом обычный f32 — правок в Rust не нужно.\n// Хвост m1 (angvel, z, level, vz, pitch, roll) обязан совпадать по ширине и\n// порядку в трёх местах сразу: `TankRow::fields` и `players_json` хостового\n// ядра (core/src/tanks.rs) и `render_overlay` клиентского\n// (core/src/client/mod.rs).\nexport default {\n m1: {\n id: 1,\n kind: 'indexed8',\n class: 'hot',\n fields: [\n { name: 'x', ty: 'f32', interp: 'lerp' },\n { name: 'y', ty: 'f32', interp: 'lerp' },\n { name: 'angle', ty: 'f32', interp: 'lerpAngle' },\n { name: 'gunRotation', ty: 'f32', interp: 'lerpAngle' },\n { name: 'vx', ty: 'f32', interp: 'lerp' },\n { name: 'vy', ty: 'f32', interp: 'lerp' },\n { name: 'engineLoad', ty: 'f32', interp: 'lerp' },\n { name: 'condition', ty: 'u8' },\n { name: 'size', ty: 'u8' },\n { name: 'team', ty: 'u8' },\n // угловая скорость корпуса: клиент доворачивает чужой танк за задержку\n // интерполяции (RemoteTanks); interp — 'lerp', это скорость, не угол\n { name: 'angvel', ty: 'f32', interp: 'lerp' },\n // 2.5D: визуальная высота 0..1 (рампа/падение) и дискретный уровень.\n // z интерполируется — подъём по рампе и падение обязаны быть плавными\n // у зрителя; level дискретен — он переключает zIndex и набор\n // коллизий, промежуточных значений у него нет\n { name: 'z', ty: 'f32', interp: 'lerp' },\n { name: 'level', ty: 'u8' },\n // вертикальная скорость (уровней/с, 0 на земле) и наклон корпуса в\n // радианах: наклон авторитетен, клиент его не восстанавливает из\n // разницы высот между кадрами — у стоящего танка та нулевая.\n // `vz` НЕ интерполируется: это детектор касания, а не плавная\n // величина. `lerp` размазывал щелчок `vz → 0` по буферу\n // интерполяции, и приземление ЧУЖОГО танка (просадка, пыль, звук)\n // не детектировалось вовсе: условие `vz === 0` требует точного нуля,\n // а сглаженная выборка его почти никогда не даёт\n // (см. src/client/landing.js)\n { name: 'vz', ty: 'f32', interp: 'discrete' },\n { name: 'pitch', ty: 'f32', interp: 'lerp' },\n { name: 'roll', ty: 'f32', interp: 'lerp' },\n ],\n },\n w1: {\n id: 2,\n kind: 'list16',\n class: 'event',\n fields: [\n { name: 'startX', ty: 'f32' },\n { name: 'startY', ty: 'f32' },\n { name: 'endX', ty: 'f32' },\n { name: 'endY', ty: 'f32' },\n { name: 'bodyX', ty: 'f32' },\n { name: 'bodyY', ty: 'f32' },\n // код попадания: `W1_HIT_*` (src/client/snapshotFields.js)\n { name: 'wasHit', ty: 'u8' },\n { name: 'shooterId', ty: 'u8' },\n // 2.5D: уровень начала и конца луча. Клиент мог бы вывести оба сам\n // из своей копии слоёв, но тогда картинка трассера зависела бы от\n // ещё одного повторённого алгоритма — два байта дешевле\n { name: 'startLevel', ty: 'u8' },\n { name: 'endLevel', ty: 'u8' },\n ],\n },\n w2: {\n id: 3,\n kind: 'indexed32',\n class: 'event',\n fields: [\n { name: 'x', ty: 'f32' },\n { name: 'y', ty: 'f32' },\n { name: 'angle', ty: 'f32' },\n { name: 'size', ty: 'u8' },\n { name: 'time', ty: 'u16' },\n { name: 'ownerId', ty: 'u8' },\n // 2.5D: уровень, на котором лежит бомба (взрыв поражает только его)\n { name: 'level', ty: 'u8' },\n ],\n },\n w2e: {\n id: 4,\n kind: 'list16',\n class: 'event',\n fields: [\n { name: 'x', ty: 'f32' },\n { name: 'y', ty: 'f32' },\n { name: 'radius', ty: 'f32' },\n // 2.5D: уровень взрыва — плита моста экранирует его и вверх, и вниз\n { name: 'level', ty: 'u8' },\n ],\n },\n c1: {\n id: 5,\n kind: 'indexedNoNull8',\n class: 'hot',\n optionalFrom: 6,\n fields: [\n { name: 'x', ty: 'f32', interp: 'lerp' },\n { name: 'y', ty: 'f32', interp: 'lerp' },\n { name: 'angle', ty: 'f32', interp: 'lerpAngle' },\n // 2.5D: высота и уровень тела карты. Обязательная голова, а не хвост:\n // покоящееся тело хвост не шлёт, и уровень читался бы нулём\n { name: 'z', ty: 'f32', interp: 'lerp', role: 'z' },\n { name: 'level', ty: 'u8', role: 'level' },\n // состояние тела карты (0 — цело): смысл значений задаёт игра,\n // движок лишь пишет байт на позицию роли. Голова по той же причине\n { name: 'state', ty: 'u8', role: 'state' },\n { name: 'vx', ty: 'f32', interp: 'lerp' },\n { name: 'vy', ty: 'f32', interp: 'lerp' },\n { name: 'angvel', ty: 'f32', interp: 'lerp' },\n ],\n },\n c2: {\n id: 6,\n kind: 'indexedNoNull8',\n class: 'hot',\n optionalFrom: 6,\n fields: [\n { name: 'x', ty: 'f32', interp: 'lerp' },\n { name: 'y', ty: 'f32', interp: 'lerp' },\n { name: 'angle', ty: 'f32', interp: 'lerpAngle' },\n // 2.5D: высота и уровень тела карты. Обязательная голова, а не хвост:\n // покоящееся тело хвост не шлёт, и уровень читался бы нулём\n { name: 'z', ty: 'f32', interp: 'lerp', role: 'z' },\n { name: 'level', ty: 'u8', role: 'level' },\n // состояние тела карты (0 — цело): смысл значений задаёт игра,\n // движок лишь пишет байт на позицию роли. Голова по той же причине\n { name: 'state', ty: 'u8', role: 'state' },\n { name: 'vx', ty: 'f32', interp: 'lerp' },\n { name: 'vy', ty: 'f32', interp: 'lerp' },\n { name: 'angvel', ty: 'f32', interp: 'lerp' },\n ],\n },\n};\n","import maps from '../data/maps/index.js';\nimport models from '../data/models.js';\nimport weapons from '../data/weapons.js';\nimport snapshot from './snapshot.js';\n\n// Игровая половина бывшего src/config/game.js: команды, панель, статистика,\n// клавиши игрока, карты и баланс. Движковые дефолты хоста —\n// src/config/hostDefaults.js; merge выполняет host.worker.js.\n// Этот объект — HostPlugin.gameConfig.\nexport default {\n parts: {\n models,\n weapons,\n mapConstructor: 'Map', // название конструктора карт\n hitscanService: 'HitscanService', // сервис вычисления стрельбы hitscan\n friendlyFire: false, // огонь по своей команде\n },\n\n // снапшот-схема (ключи/раскладка бинарного протокола) — игровая:\n // движок передаёт её ядру и клиенту, не зная содержимого\n snapshot,\n\n // маппинг движковых событий на имена звуков игры (SocketManager.sendSoundCue)\n soundCues: {\n roundStart: 'roundStart',\n victory: 'victory',\n defeat: 'defeat',\n frag: 'frag',\n death: 'gameOver',\n },\n\n // голосование, отправляемое игроку после первого кадра (выбор команды)\n initialVote: 'teamChange',\n\n maps, // карты игры\n mapScale: 0.3, // масштаб карт\n currentMap: 'pool mini', // название карты по умолчанию\n mapsInVote: 4, // количество карт в голосовании\n mapSetId: 'c1', // дефолтный id конструкторов создания карт\n\n // собственные параметры Rust-ядра игры: движок довозит объект целиком в\n // половину `game` init-JSON, не читая его (lib/coreConfig.js)\n coreParams: {\n // правила 2.5D-уровней; одноуровневые карты их не касаются\n levels: {\n fallTime: 0.35, // с на ОДИН уровень высоты\n fallDamage: 30, // урон за уровень СВЕРХ мёртвой зоны (0 — падение бесплатно)\n // высота дуги, которая ничего не стоит: прыжок с рампы возвращает\n // танк на ту же плиту. При freeHeight 0.5 и fallDamage 30 падение\n // ровно с одного уровня стоит прежние 15 HP\n fallDamageFreeHeight: 0.5,\n maxFallDamage: 100, // потолок урона падения\n // уклон безразмерный (`rise * levelHeight / span`): на демо-картах\n // 0.11 (rampLong) … 0.5 (rampSteep)\n climbGravity: 500, // мировых единиц/с² на единицу продольного уклона\n climbMaxSpeedFactor: 0.5, // множитель макс. скорости при уклоне 1.0\n // сколько кадров подряд кадр обязан держать уровень ВЫШЕ реплики,\n // чтобы она приняла подъём: понижение принимается сразу, а подъём\n // отличается от запоздавшего кадра только стойкостью несогласия\n levelAdoptFrames: 8,\n // потолок скачка высоты при заезде на горку сбоку или наискось\n // (в уровнях); граница исключающая\n maxSideEntryRise: 0.5,\n // множитель вертикальной скорости на вылете с верхнего торца\n // рампы: 0 — прыжка нет вовсе, 1 — вся вертикальная составляющая\n // скорости на уклоне уходит в полёт. 1.0 давал на крутом прогоне\n // `terraces` дугу в 2.6 уровня (замер), поэтому доля\n rampLaunchFactor: 0.35,\n // порог вылета (уровней/с): ниже него прыжок не начинается, иначе\n // съезд по рампе шагом рождал бы микропрыжки на каждой клетке\n minLaunchVz: 0.35,\n // потолок вылета (уровней/с). При g = 2/fallTime² = 16.33 он задаёт\n // максимальную дугу: vz²/(2g) = 0.375 уровня. Обязан быть НИЖЕ\n // jumpClearance, иначе штатный прыжок перелетает стены и периметр\n maxLaunchVz: 3.5,\n // насколько выше уровня взлёта (в уровнях) танк перестаёт видеть\n // стены — то есть перепрыгивает препятствия. Выше максимальной дуги\n // (см. maxLaunchVz): перелёт остаётся механикой ядра, но штатным\n // прыжком недостижим — карте, которой он нужен, достаточно поднять\n // rampLaunchFactor/maxLaunchVz\n jumpClearance: 0.45,\n // во сколько раз безразмерный уклон превращается в угол наклона\n // корпуса (1 — наклон равен арктангенсу уклона)\n tiltGain: 2.0,\n // наклон носа в полёте: радиан на единицу вертикальной скорости\n tiltAirGain: 0.12,\n // скорость возврата корпуса к целевому наклону, 1/с\n tiltResponse: 12.0,\n // потолок наклона по модулю, рад\n tiltMax: 0.6,\n // тряска камеры на приземлении (у оружия она задаётся так же —\n // `cameraShake` в src/data/weapons.js); отсутствие блока = тряски\n // нет. Пороги minImpact/fullImpact численно повторяют `landing` из\n // src/config/render.js (просадка, пыль и звук на клиенте) — общего\n // источника у рендера и WASM нет, менять только парой\n landingShake: {\n // при полном ударе, единицы как у оружия. 6 читалось как удар по\n // экрану на каждом прыжке; падение с уровня даёт k ≈ 0.94, то\n // есть почти всю интенсивность, поэтому число задаёт силу именно\n // ПАДЕНИЯ, а не прыжка\n intensity: 3,\n duration: 220, // мс\n minImpact: 1.5, // ниже — мягкое касание, тряски нет\n fullImpact: 6, // уровней/с, дающие полную интенсивность\n },\n },\n // поверхности клеток: карта размечает тайлы полем `game.surfaces`, смысл\n // типов задаётся здесь. Значения стартовые. Множители: accel — тяга,\n // maxSpeed — потолок скорости, grip — боковое сцепление, brake —\n // торможение без газа, turn — поворот (нейтрально 1). Добавки: drag —\n // линейное сопротивление, angularDrag — угловое, 1/с (нейтрально 0;\n // отрицательное ослабляет демпфирование, не ниже −damping.angular модели).\n // slickTime — остаток, с: после съезда гусеницы ещё столько скользят,\n // эффект спадает линейно. Только у масла: песок и грязь с гусениц не\n // тянутся, без поля остатка нет\n surfaces: {\n trackYawGain: 0.004, // Δω на единицу разницы тяги гусениц (1/ед. длины)\n trackSampleX: 0.6, // точки вдоль корпуса, доля полудлины\n trackSampleY: 0.75, // линия гусеницы, доля полуширины\n bodyBeltCoupling: 4.0, // связь ящика/бочки с лентой конвейера, 1/с\n types: {\n sand: { accel: 0.6, maxSpeed: 0.55, drag: 1.2, grip: 1.0, brake: 1.0, turn: 0.8 },\n mud: { accel: 0.45, maxSpeed: 0.4, drag: 2.0, grip: 0.9, brake: 1.0, turn: 0.7 },\n water: { accel: 0.7, maxSpeed: 0.6, drag: 1.5, grip: 0.8, brake: 0.8, turn: 0.85 },\n oil: {\n accel: 0.35,\n maxSpeed: 1.0,\n drag: 0.0,\n grip: 0.08,\n brake: 0.1,\n turn: 1.6,\n angularDrag: -0.5,\n slickTime: 1.5,\n },\n conveyor: { belt: 60 }, // ед./с по стрелке\n // разовый импульс по стрелке при въезде; boostMaxSpeed и minEntrySpeed\n // при boostDv обязательны. Удержание (необязательно): boostTime секунд\n // после импульса потолок скорости танка умножен на boostSpeedFactor,\n // а линейное демпфирование скомпенсировано. Итоговые числа\n // подбираются вручную в `npm run dev`\n boost: {\n boostDv: 220,\n boostMaxSpeed: 480,\n minEntrySpeed: 20,\n boostTime: 1.2,\n boostSpeedFactor: 1.8,\n },\n },\n },\n // разрушаемые тела карты: карта назначает тип полем\n // `physicsDynamic[i].game.prop`. Значения стартовые, мир — в единицах\n // после mapScale (максимальная скорость танка 260). damagedAt — доля HP,\n // ниже которой тело «повреждено» (0 — стадии нет); bulletFactor/\n // blastFactor — множители урона выстрела и взрыва; ramThreshold —\n // скорость удара вдоль нормали, ниже которой таран урона не наносит;\n // ramDamagePerSpeed — урон за единицу превышения; chainDelay — задержка\n // детонации от чужого взрыва, с (обязательна при blast)\n props: {\n fence: {\n hp: 30,\n damagedAt: 0,\n bulletFactor: 1.0,\n blastFactor: 1.0,\n ramThreshold: 60,\n ramDamagePerSpeed: 0.5,\n },\n crate: {\n hp: 120,\n damagedAt: 0.5,\n bulletFactor: 0.5,\n blastFactor: 1.5,\n ramThreshold: 140,\n ramDamagePerSpeed: 0.6,\n },\n barrel: {\n hp: 40,\n damagedAt: 0,\n bulletFactor: 1.0,\n blastFactor: 1.0,\n ramThreshold: 150,\n ramDamagePerSpeed: 1.0,\n chainDelay: 0.15,\n blast: { radius: 70, damage: 80, impulse: 2500000, cameraShake: { intensity: 30, duration: 400 } },\n },\n },\n },\n\n // рамки настроек комнаты в лобби (GameManifest.roomDefaults)\n roomDefaults: {\n maxPlayers: 8, // целевой размер комнаты (рамка P2P-плана)\n },\n\n // схема формы создания сервера (GameManifest.roomForm, движок v3): имена =\n // ключи roomDefaults; default НЕ указываем — движок засеивает его из\n // roomDefaults (mergeRoomDefaults). regExp для maxPlayers/roundTime/mapTime\n // здесь не указан — его накладывает build-game-manifest.js точным\n // диапазонным паттерном (rangeToPattern) из тех же чисел, что клампует\n // applyRoomOverrides.js (roomDefaults.maxPlayers; hostDefaults.timers\n // roomTimeMin/roomTimeMax) — единый источник, не независимая копия.\n // unit:'s' — движок делит мс на 1000 для показа; numeric:true — текстовое\n // поле хранит и валидирует число. build-game-manifest.js кладёт рядом с\n // regExp ещё и min/max из тех же чисел: движок показывает их подсказкой\n // «(min–max)» в подписи поля и проверяет сам, строкой в #lobby-error\n // (нативных браузерных попапов больше нет). Всё это UX-подсказка, не\n // авторитетная граница: значения всё равно клампятся в\n // applyRoomOverrides.js движка\n roomForm: [\n { name: 'maxPlayers', control: 'text', label: 'Max players', numeric: true },\n { name: 'roundTime', control: 'text', label: 'Round time', unit: 's', numeric: true },\n { name: 'mapTime', control: 'text', label: 'Map time', unit: 's', numeric: true },\n { name: 'friendlyFire', control: 'checkbox', label: 'Friendly fire' },\n { name: 'map', control: 'select', label: 'Map', source: 'maps' },\n ],\n\n stat: {\n name: {\n key: 0,\n bodyMethod: '=',\n headSync: true,\n headMethod: '#',\n },\n status: {\n key: 1,\n bodyMethod: '=',\n bodyValue: '',\n headValue: '',\n },\n score: {\n key: 2,\n bodyMethod: '+',\n bodyValue: 0,\n headMethod: '+',\n headValue: 0,\n },\n deaths: {\n key: 3,\n bodyMethod: '+',\n bodyValue: 0,\n headMethod: '+',\n headValue: 0,\n },\n latency: {\n key: 4,\n bodyMethod: '=',\n },\n },\n\n // rank/state игрока (Этап B4): rank — числовой рейтинг per (user, game),\n // state — непрозрачный для движка JSON (\"скиллы\"); defaultState — то, с\n // чем стартует игрок без сохранённой записи на auth-сервисе\n playerState: {\n defaultState: {},\n },\n\n panel: {\n // схема полей панели (стартовые значения ресурсов; уходит и в ядро)\n fields: {\n health: {\n key: 'h',\n value: 100,\n },\n w1: {\n key: 'w1',\n value: 200,\n },\n w2: {\n key: 'w2',\n value: 100,\n },\n },\n // ключ активного оружия в кадрах панели\n activeKey: 'wa',\n },\n\n // параметры scripted-участников (ботов): префикс имени Bot<id>,\n // модель танка по умолчанию\n scripted: {\n namePrefix: 'Bot',\n defaultModel: 'm1',\n },\n\n spectatorTeam: 'spectators', // название команды наблюдателя\n\n teams: {\n team1: 1,\n team2: 2,\n spectators: 3,\n },\n\n // конфигурация клавиш активного игрока\n // type - тип отработки нажатия на клавишу (по умолчанию 0):\n // 0 : многократное нажатие (начинается на keyDown, завершается на keyUp)\n // 1 : выполняется один раз на keyDown\n playerKeys: {\n // forward (w)\n forward: {\n key: 1 << 0,\n },\n // back (s)\n back: {\n key: 1 << 1,\n },\n // left (a)\n left: {\n key: 1 << 2,\n },\n // right (d)\n right: {\n key: 1 << 3,\n },\n // gun center (u)\n gunCenter: {\n key: 1 << 4,\n type: 1,\n },\n // gun left (k)\n gunLeft: {\n key: 1 << 5,\n },\n // gun right (l)\n gunRight: {\n key: 1 << 6,\n },\n // fire (j)\n fire: {\n key: 1 << 7,\n type: 1,\n },\n // next weapon (n)\n nextWeapon: {\n key: 1 << 8,\n type: 1,\n },\n // prev weapon (p)\n prevWeapon: {\n key: 1 << 9,\n type: 1,\n },\n },\n};\n","// Громкости пересчитаны под нормализованный webm: до правки\n// scripts/process-audio.js фильтры (`-af`) действовали только на mp3, то\n// есть на ветку Safari, а webm — кодек Chrome, Firefox и Edge — шёл сырым и\n// был разбросан на 11 LU. Каждое значение домножено на 10^(-Δ/20), где Δ —\n// замеренная разница интегральной громкости webm до и после нормализации\n// (двигатель −20.6 → −16.0 LUFS, взрыв −9.6 → −15.0), поэтому на слух\n// громкость каждого звука осталась той же, что была. Уровни проверяются\n// `npm run audio:check`.\n//\n// Прежний баланс этим сохранён целиком: взрыв по-прежнему на ~9 дБ громче\n// холостого хода. Исключение — двигатель: он терялся на слух, и его\n// громкость поднята отдельно (см. ниже). Остальные значения правятся на\n// слух в `npm run dev` (Howler.volume = 0.7 в движковом SoundManager).\nconst sounds = {\n // https://freesound.org/people/joepayne/sounds/413201/\n roundStart: { file: 'round-start', priority: 200, volume: 0.35 },\n // https://pixabay.com/sound-effects/silly-trumpet-2-187807/\n victory: { file: 'victory', priority: 200, volume: 0.38 },\n // https://pixabay.com/sound-effects/silly-trumpet-11-187806/\n defeat: { file: 'defeat', priority: 200, volume: 0.5 },\n\n // https://pixabay.com/sound-effects/metal-hit-sound-effect-241374/\n frag: { file: 'frag', priority: 150, volume: 0.26 },\n // https://freesound.org/people/SamsterBirdies/sounds/581598/\n hit: { file: 'hit', priority: 150, volume: 0.36 },\n // https://freesound.org/people/obstgegenrechz/sounds/267980/\n gameOver: { file: 'game-over', priority: 150, volume: 0.31 },\n\n // https://freesound.org/people/GaryQ/sounds/127845/\n shot: { file: 'shot', priority: 100, volume: 0.51 },\n // https://freesound.org/people/studiomandragore/sounds/401628/\n explosion: { file: 'explosion', priority: 100, volume: 0.74 },\n // гибель танка (`WreckFire`): тот же файл, что у взрыва бомбы, тише —\n // рядом с остовом часто рвётся и сама бомба\n tankExplosion: { file: 'explosion', priority: 100, volume: 0.6 },\n // https://pixabay.com/sound-effects/start-stop-stopwatch-364924/\n // https://freesound.org/people/vibe_crc/sounds/47988/\n bombHasBeenPlanted: {\n file: 'bomb-has-been-planted',\n priority: 90,\n volume: 0.48,\n },\n\n // разрушение пропа (забор, ящик): тот же файл, что у попадания, тише и\n // с меньшим приоритетом — выстрел, сломавший забор, слышен поверх треска.\n // Взрыв бочки звучит своим `explosion` через ExplosionEffect\n propBreak: { file: 'hit', priority: 80, volume: 0.28 },\n\n // https://freesound.org/people/7of9Designs/sounds/640204/\n tankLanding: { file: 'tank-landing', priority: 60, volume: 0.7 },\n\n // https://freesound.org/people/monosfera/sounds/572294/\n // громче остальных и с запасом на холостые: множитель холостого хода в\n // Tank.js — 0.6, поэтому эффективная громкость стоящего танка 0.5 × 0.6 =\n // 0.3 (было 0.29 × 0.9 = 0.26), а на ходу двигатель звучит в полный голос\n tankEngine: { file: 'tank-engine', priority: 50, loop: true, volume: 0.5 },\n\n // https://freesound.org/people/paulprit/sounds/563555/ (CC0, он же\n // https://pixabay.com/sound-effects/nature-river-wading-fast-50619/):\n // ровный отрезок 197.5–201.5 с, стык склеен кроссфейдом 100 мс\n // плеск под гусеницами в воде; громкость и скорость воспроизведения\n // ведёт Tank.js по `surfaceFx.water.sound` (src/config/render.js)\n tankWater: { file: 'tank-water', priority: 55, loop: true, volume: 0.3 },\n};\n\nexport default {\n codecList: ['webm', 'mp3'],\n path: '/sounds/',\n\n // Геометрия пространственного звука. Числа в МИРОВЫХ единицах: у танков\n // mapScale 0.3 и baseScale 5, то есть мировая единица впятеро меньше\n // экранного пикселя, и движковые дефолты (180/40, рассчитанные на 1:1)\n // здесь дали бы стереобазу в пять раз шире экрана.\n spatial: {\n mode: 'topDown',\n\n // половина видимой высоты экрана: 1080 / 2 / 5 = 108 мировых единиц.\n // На краю экрана (192 ед. вбок) это угол ~60°, у корпуса — единицы\n // градусов\n virtualElevation: 108,\n\n // полудиагональ корпуса m1: 12 x 9 мировых единиц (motion.rs: size*4 x\n // size*3 при size: 3) -> hypot(12, 9) / 2 = 7.5. Взрыв внутри габарита\n // распределяется поровну в оба уха\n innerRadius: 7.5,\n },\n\n sounds,\n};\n","// Настройки рендера 2.5D, общие для конфига и для самих партов.\n//\n// Партам движок конфиг не отдаёт: фабрика зовёт их как\n// `new Part(data, assets, dependencies, context)` (движок,\n// components/model/Game.js), а `dependencies` — только сервисы. Поэтому\n// единственный источник этих чисел — модуль: `src/config/client.js` кладёт\n// его в `parts.seeThrough` / `parts.parallax` / `parts.volume` (документация\n// клиентского конфига), а `src/client/levelView.js`,\n// `src/client/parallax.js` и парты импортируют его напрямую. Правка здесь\n// меняет обе стороны сразу.\n\n// прозрачность уровней НАД игроком (задачи 2 и 3 мастер-плана)\nexport const seeThrough = {\n // 'hole' — радиальная дыра вокруг игрока (по умолчанию, GTA 2)\n // 'layer' — прежнее поведение: гаснет весь слой целиком\n mode: 'hole',\n\n // мировые единицы: радиус дыры вокруг игрока (режим 'hole')\n radius: 260,\n\n // доля радиуса, уходящая на затухание края дыры\n softness: 0.45,\n\n // прозрачность в центре дыры\n minAlpha: 0.12,\n\n // режим 'layer': прозрачность всей плиты (прежний UNDER_BRIDGE_ALPHA)\n layerAlpha: 0.4,\n\n // скорость перехода (доля в секунду): мгновенный скачок читается как\n // мигание при каждом въезде под край моста\n fadeRate: 6,\n\n // мировые единицы: запас, с которым крыша (`game.roofs`) считается\n // закрывающей танк. 0 — как у стен: корпус 8×6, и уже запас 10 у стены\n // дотягивался до клетки крыши — крыша открывалась, а вывеска тускнела,\n // хотя танк она не закрывала\n roofMargin: 0,\n\n // затемнение уровней НИЖЕ игрока: «темнее — значит ниже»\n lowerTint: 0xb0b0c0,\n};\n\n// проекция 2.5D: одно число на всех потребителей высоты. Объект на высоте\n// h (в УРОВНЯХ) рисуется смещённым ОТ центра камеры на `h * shear` долей\n// расстояния до него — так читается высота в GTA 2. Потребители: слой\n// уровня N и его объём (`Map`), клин рампы, корпус танка, следы\n// (`src/client/parallax.js` — единственное место, где живёт формула).\n//\n// Число одно и для слоя, и для танка НЕ случайно: танк на плите уровня 1\n// обязан смещаться ровно так же, как сама плита, иначе он с неё съедет.\n//\n// Масштаб — часть ТОЙ ЖЕ проекции: объект на высоте z рисуется смещённым на\n// `z * shear` И увеличенным в `(1 + z * shear)` раз, ровно как плита, на\n// которой он стоит (`applyParallax` делает и то, и другое одним\n// трансформом). Отдельного коэффициента масштаба высоты нет — иначе танк,\n// дым и бомба разъезжались бы с плитой и друг с другом.\nexport const parallax = {\n // сдвиг на один уровень высоты, доля расстояния до центра камеры\n shear: 0.22,\n\n // шаг zIndex между уровнями (`src/client/levelZ.js`). Обязан быть больше\n // любого базового zIndex парта, иначе слой моста провалится под наземный\n // навес\n levelZStride: 100,\n};\n\n// объёмные элементы карты (задача 7): экструзия рендер-слоя\nexport const volume = {\n // путь отхода: false выключает экструзию слоёв и клин рампы\n enabled: true,\n\n // объём рисуется монолитно: боковые грани мешем плюс один верх слоя.\n // false — прежняя стопка из `slices` копий слоя (на краю экрана и при\n // отдалённой камере между ними видны ступени)\n faces: true,\n\n // копий картинки тайла на уровень высоты боковой грани. Текстура привязана\n // к стене, как в GTA 2: кирпич тянется и сжимается вместе с проекцией\n // 2.5D — у центра экрана грань короткая и кирпич сплющен, к краю вытянут,\n // — а число его рядов не меняется. Пропорции тайла кирпич сохраняет на\n // расстоянии `faceTilesPerLevel · клетка / shear` от центра камеры (при 3\n // на downtown ≈ 175 мировых единиц, ~0.9 полуэкрана)\n faceTilesPerLevel: 3,\n\n // рядов сетки грани на уровень высоты. Сдвиг параллакса по грани\n // билинеен, и квад из двух треугольников ломал швы кирпича по диагонали\n // (у стены в уровень до ~0.7 мировой единицы); 4 ряда — меньше пикселя\n faceSegments: 4,\n\n // нахлёст граней под верх объёма в ЭКРАННЫХ пикселях: грань заходит\n // чуть выше верха, и между мешем грани и спрайтом верха не остаётся\n // щели на дробном масштабе сцены. Считается от масштаба сцены каждый\n // кадр — движок отдаляет камеру от скорости, и запас в мировых единицах\n // переставал закрывать щель на отдалённой камере. 0 — стык снова мерцает\n faceBleedPx: 2,\n\n // число срезов экструзии при `faces: false` — число копий слоя, то есть\n // K вызовов отрисовки на слой; при `faces: true` не используется, но\n // `0` выключает экструзию. Только для ОБЪЁМОВ слоёв (здания, перила)\n slices: 4,\n\n // сегментов клина рампы на клетку прогона. Клин — наклонная плоскость\n // (один меш на прогон), и это плотность его вершин: сдвиг параллакса\n // вдоль прогона растёт квадратично, поэтому ломаная из 4 сегментов на\n // клетку от него уже неотличима\n rampSegments: 4,\n\n // затемнение боковых граней\n sideTint: 0x9aa0aa,\n};\n\n// признаки высоты у танка (`src/client/parts/Tank.js`): масштаб корпуса и\n// его тень. Не «настройка вкуса», а числа 2.5D — живут рядом с параллаксом.\n//\n// Тень рисуется ТОЛЬКО в полёте: она показывает отрыв от опоры, а у\n// стоящего и едущего танка отрыва нет\nexport const shadow = {\n // тень растёт и бледнеет с ВЫСОТОЙ ОТРЫВА от опоры\n scaleGain: 0.1,\n baseAlpha: 0.4,\n alphaFalloff: 0.12,\n\n // доля от ДЛИНЫ корпуса: текстура тени уже в его пропорции, поэтому в\n // начале прыжка тень лежит ровно под корпусом и из-под него не\n // выглядывает\n sizeFactor: 1.05,\n\n // сдвиг тени ОТ света (`tilt.lightDir`), мировые единицы. Со сдвигом тень\n // лежит и под стоящим танком: она выглядывает с одной стороны, как у\n // зданий, и отрывает корпус от земли. Без сдвига она легла бы ровно под\n // корпусом и читалась серым ореолом, поэтому 0 возвращает тень только в\n // полёте\n groundOffset: 3,\n\n // прозрачность тени стоящего и едущего танка\n groundAlpha: 0.3,\n};\n\n// наклон корпуса на рампе и в полёте (`src/client/tilt.js`,\n// `src/client/parts/Tank.js`). Углы `pitch`/`roll` считает ядро и везёт в\n// кадре — здесь только то, как они читаются на экране\nexport const tilt = {\n // включает деформацию корпуса целиком; false — прежний плоский спрайт\n enabled: true,\n\n // экранный подъём поднявшегося края (в долях его высоты)\n lift: 0.35,\n\n // плотность сетки PerspectiveMesh по каждой оси\n vertices: 6,\n\n // Экструзии корпуса здесь нет намеренно — почему, в\n // docs/ru/configuration.md, раздел «Рендер 2.5D»\n //\n // светотень наклона: насколько темнеет корпус, полностью отвёрнутый от\n // света. Эффект ОДНОСТОРОННИЙ — только затемнение: тинт уровня для\n // своего и верхних ярусов белый (`src/client/seeThrough.js`), и\n // подсветка поверх него упирается в потолок канала. 0 — света нет\n shading: 0.28,\n\n // направление света в СИСТЕМЕ ЭКРАНА (не корпуса): [x, y], нормируется\n // при чтении. По умолчанию свет с северо-запада — так же освещены\n // боковые грани зданий\n lightDir: [-0.7, -0.7],\n};\n\n// свет корпуса по карте нормалей (`src/client/tankLight.js`): фаски корпуса\n// и башни к свету светлее, от света темнее. Направление света — то же\n// `tilt.lightDir`. Ровный плоский верх не меняется (множитель ровно 1).\n// Меш со своим шейдером не батчится: +3 вызова отрисовки на танк\nexport const tankLight = {\n // false — прежний батченый меш, объём — только светотень наклона\n // (`tilt.shading`)\n enabled: true,\n\n // рассеянная и направленная доли света: чем меньше `ambient`, тем\n // темнее отвёрнутые от света грани\n ambient: 0.55,\n diffuse: 0.6,\n\n // высота света над плоскостью карты относительно длины `lightDir`:\n // меньше — свет ниже, грани контрастнее\n lightZ: 1,\n};\n\n// визуальная отдача после выстрела hitscan (`src/client/recoil.js`). Только\n// рендер: танк физически не сдвигается. Длины — мировые единицы, качание —\n// радианы\nexport const recoil = {\n // false — выстрел танк не трогает\n enabled: true,\n\n // полная длительность, мс, и доля на быстрый рост\n duration: 150,\n attack: 0.15,\n\n // откат башни со стволом и корпуса назад вдоль ствола\n gunKick: 1,\n bodyKick: 0,\n\n // качание: сторона ствола приподнимается на столько радиан\n rock: 0.1,\n};\n\n// низкополигональная 3D-модель танка (`src/client/tank3d/`, plan/tank-3d/).\n// Форма в плане — из рисунка корпуса (`tankTexture.js`, пиксели при базовом\n// size 10), высоты — здесь, в тех же пикселях; в мир всё масштабируется на\n// size / 10. Модель рисуется почти сверху: наклон от центра экрана один на\n// весь танк и ограничен `maxLean`, иначе проекция карты (`shear` на\n// уровень) вытянула бы бока на краю экрана длиннее корпуса\nexport const tankModel = {\n // false — прежний плоский корпус (`PerspectiveMesh` с картами нормалей)\n enabled: true,\n\n // высоты частей, пиксели рисунка\n trackHeight: 5,\n hullBase: 2,\n hullTop: 6,\n turretBase: 6,\n turretTop: 10,\n barrelHeight: 8,\n barrelRadius: 1.5,\n brakeRadius: 2,\n\n // мировых единиц на уровень для наклона от центра экрана (клетка\n // downtown: 32 · 0.4), доля наклона карты и плавный предел смещения\n // верхней точки модели, мировые единицы. Наклон слабый намеренно: камера на\n // скорости забегает вперёд, танк уходит от центра экрана, и при наклоне\n // «как у зданий» башня заметно съезжала при каждом разгоне\n levelHeight: 12.8,\n leanGain: 0.3,\n maxLean: 0.6,\n\n // тень по силуэту модели (`src/client/tank3d/shadow.js`): мягкость края,\n // сила размытия (0 — без размытия). Прозрачность — `shadow.groundAlpha` на\n // земле и `shadow.baseAlpha`/`alphaFalloff` в полёте\n shadowBlur: 1,\n};\n\n// визуальная реакция танка на взрыв бомбы или бочки (`src/client/blastJolt.js`).\n// Только рендер: физически танк толкает ядро. Задевает танки того же уровня\n// в радиусе взрыва, сила — `1 − d / radius`, как у урона\nexport const blastJolt = {\n // false — взрыв танк визуально не трогает\n enabled: true,\n\n // крен: сторона к взрыву подлетает на `rock` рад (при силе 1), затем\n // корпус качается с частотой `wobbleHz` и затухает за ~`decay` мс\n rock: 0.22,\n wobbleHz: 5,\n decay: 180,\n duration: 700,\n\n // взрыв под корпусом (ближе `underShare` длины корпуса к центру): корпус\n // подбрасывает на `hop` уровней за `hopDuration` мс и кренит в случайную\n // сторону, на конце — просадка приземления\n hop: 0.3,\n hopDuration: 420,\n underShare: 0.5,\n\n // встряска: сдвиг корпуса до `shake` мировых единиц, гаснет за\n // `shakeDuration` мс\n shake: 1.2,\n shakeDuration: 220,\n};\n\n// Гибель танка (`src/client/parts/WreckFire.js`): взрыв, пожар и дым над\n// остовом. Только рендер — ядро о них не знает. Время — мс, длины и\n// скорости — мировые единицы (корпус m1 — 12 × 9), высота подъёма — уровни.\n// Числа подобраны под танк размера `referenceSize`; у другого размера\n// длины, скорости и радиусы масштабируются пропорционально\nexport const wreckFx = {\n // false — гибель без эффекта (остов просто появляется)\n enabled: true,\n\n // size m1 (src/data/models.js): под него подобраны числа ниже\n referenceSize: 3,\n\n // потолки живых частиц на один остов: канал пламени (шар, искры, языки)\n // и канал дыма. При упоре частица просто не рождается\n maxFire: 64,\n maxSmoke: 80,\n\n // общий снос дыма и пламени (мировых единиц в секунду): все столбы\n // дыма на карте клонятся в одну сторону\n wind: { x: 5, y: -3 },\n\n // визуальный толчок через шину `blasts`: остов подпрыгивает (взрыв под\n // корпусом), соседние танки в радиусе качает. 0 — без толчка\n joltRadius: 18,\n\n // звук взрыва (src/config/sounds.js); null — без звука\n sound: 'tankExplosion',\n\n // вспышка: один аддитивный спрайт, растёт и гаснет\n flash: {\n duration: 180,\n startSize: 12,\n endSize: 40,\n alpha: 1,\n color: 0xfff1c8,\n },\n\n // огненный шар: горящие клубы разлетаются из корпуса и тормозят\n fireball: {\n count: 14,\n speed: { min: 15, max: 50 },\n drag: 4, // 1/с\n size: { min: 4, max: 7 }, // стартовый диаметр\n grow: 2.2, // во сколько раз вырастает к концу жизни\n lifetime: { min: 450, max: 850 },\n rise: 0.5, // подъём за жизнь, уровни\n alpha: 0.95,\n },\n\n // искры: быстрые короткие штрихи, вытянутые по скорости\n sparks: {\n count: 12,\n speed: { min: 70, max: 160 },\n drag: 3,\n length: 2.4,\n width: 0.5,\n lifetime: { min: 300, max: 700 },\n colors: [0xffe6a8, 0xff6a1a], // цвет в начале и в конце жизни\n },\n\n // пламя: непрерывные языки из точек корпуса\n fire: {\n duration: 9000, // полная сила\n fadeOut: 5000, // затухание до нуля\n rate: 36, // частиц/с на полной силе\n // точки пожара в долях корпуса [вдоль курса, поперёк]: моторное\n // отделение (корма) и погон башни; spread — разброс в тех же долях\n points: [\n [-0.3, 0],\n [0.05, 0],\n ],\n spread: 0.4,\n size: { min: 2.5, max: 4.5 },\n grow: 1.6,\n lifetime: { min: 300, max: 650 },\n jitter: 4, // случайный боковой снос языка\n rise: 0.35,\n alpha: 0.85,\n // цвет по доле жизни частицы: [доля, цвет] — от почти белого ядра\n // через оранжевый к тёмно-красному (общий для пламени и огненного шара)\n ramp: [\n [0, 0xfff4c8],\n [0.2, 0xffc24a],\n [0.5, 0xf2661a],\n [0.8, 0x8c240a],\n [1, 0x240804],\n ],\n },\n\n // отсвет пламени: один аддитивный спрайт над остовом, дышит с пламенем\n glow: { size: 28, alpha: 0.3, color: 0xff8a30, flicker: 0.35 },\n\n // дым: пока горит — чёрный и густой, после пожара — светлее и реже\n smoke: {\n rate: 12, // частиц/с на полной силе пожара\n tailRate: 7, // частиц/с сразу после пожара, дальше спадает до 0\n tail: 15000, // сколько ещё дымит после пожара\n burst: { count: 12, speed: { min: 12, max: 30 }, alpha: 0.55 },\n size: { min: 3, max: 5 },\n grow: 3.8,\n lifetime: { min: 2600, max: 4200 },\n speed: 6, // начальный разброс скорости\n drag: 1.5, // 1/с: скорость частицы сходится к ветру\n rise: 1.4,\n alpha: 0.42, // пик прозрачности при пожаре\n tailAlpha: 0.2, // пик после пожара\n burning: [0x141210, 0x2a2622], // диапазон цвета при пожаре\n cooling: [0x4a4744, 0x625e5a], // диапазон цвета после\n },\n\n // копоть на земле в точке гибели; живёт до респауна\n scorch: { enabled: true, size: 26, alpha: 0.55, fadeIn: 400 },\n};\n\n// трассер выстрела hitscan (`src/client/parts/effects/shot/TracerEffect.js`).\n// Длины и ширины — мировые единицы, время — мс\nexport const tracer = {\n // скорость «головы» и границы длительности пролёта\n speed: 19000,\n minDuration: 45,\n maxDuration: 80,\n\n // длина видимого хвоста: не меньше `trailLength` и не меньше доли\n // `trailShare` всего луча; к концу пути хвост стягивается к цели. Доля\n // нужна дальнему выстрелу: пролёт упирается в `maxDuration` (≈5 кадров),\n // голова проходит сотни единиц за кадр, и короткий хвост висел бы далеко\n // от ствола, чаще всего за краем экрана\n trailLength: 55,\n trailShare: 0.4,\n\n // цельная линия, «как в кино», а не мультяшный луч: тонкое раскалённое\n // почти белое ядро и узкое тусклое свечение тёплого оттенка — цвет\n // горящего фосфора реального трассера\n color: 0xff9a4a,\n coreColor: 0xfff4e0,\n coreWidth: 0.3,\n glowWidth: 1.2,\n glowAlpha: 0.2,\n\n // прозрачность головы в начале и в конце пути\n alphaStart: 1,\n alphaEnd: 0.6,\n\n // высота полёта пули над полом, уровни: высота ствола модели\n // (`tankModel.barrelHeight` 8 px · size 3 / 10 = 2.4 мировой единицы при\n // `tankModel.levelHeight` 12.8). На ней выстрел упирается в ГРАНЬ стены,\n // а не в её подножие на полу\n height: 0.19,\n\n // подотрезков хвоста: хвост гаснет к дулу по квадрату — яркость\n // собрана у головы, как у смазанной в движении точки\n fadeSteps: 8,\n};\n\n// вспышка у дула при выстреле hitscan\n// (`src/client/parts/effects/shot/MuzzleFlashEffect.js`). Видна и днём;\n// ночная световая вспышка — отдельно, `lighting.flash.shot`. «Как в кино»:\n// один-два кадра, неровные языки пламени, мягкий край слоями\nexport const muzzleFlash = {\n duration: 35,\n\n // языки пламени вперёд: сколько, длина, ширина основания и разброс\n // направления (рад) вокруг оси выстрела. Длина и угол каждого языка\n // случайны — рисунок у каждого выстрела свой\n spikes: 5,\n length: 6,\n width: 1.6,\n spread: 0.35,\n\n // разброс длины языка (доля): длина от (1 − jitter) до 1 от `length`\n jitter: 0.5,\n\n // боковые выбросы дульного тормоза\n sideLength: 2.2,\n sideWidth: 0.9,\n\n // мягкий край: те же языки, нарисованные слоями — шире и тусклее снаружи,\n // уже и ярче внутри. `core: true` — слой цвета ядра\n layers: [\n { scale: 1.35, alpha: 0.18 },\n { scale: 1, alpha: 0.45 },\n { scale: 0.55, alpha: 0.95, core: true },\n ],\n\n // к концу вспышка скорее гаснет, чем сжимается: размер падает только на\n // эту долю\n shrink: 0.4,\n\n // те же цвета, что у трассера: горящий фосфор и раскалённое ядро\n color: 0xff9a4a,\n coreColor: 0xfff4e0,\n};\n\n// эффекты поверхностей под танком (`src/client/parts/Dust.js`,\n// `src/client/parts/tracks/Tracks.js`). Поверхность клетки берётся из ядра\n// (сервис `surfaces`); здесь только то, как она выглядит. Карта без\n// поверхностей эти числа не читает\nexport const surfaceFx = {\n // песок: частая светлая пыль, пропорционально скорости\n sand: {\n color: 0xd8c38a,\n // частиц в секунду на гусеницу на скорости `fullSpeed`\n rate: 24,\n // скорость (мировых единиц в секунду), дающая полную частоту\n fullSpeed: 60,\n // ниже этой скорости песок не пылит\n minSpeed: 4,\n lifetime: { min: 350, max: 750 },\n sizeFactor: 1,\n alpha: 0.35,\n },\n\n // грязь: тёмные тяжёлые комья — живут короче, крупнее\n mud: {\n color: 0x3b2e1e,\n rate: 14,\n fullSpeed: 60,\n minSpeed: 4,\n lifetime: { min: 180, max: 380 },\n sizeFactor: 1.8,\n alpha: 0.6,\n },\n\n // вода: голубые брызги по сторонам гусениц и разовый всплеск при въезде\n water: {\n color: 0x9fd4ff,\n rate: 20,\n fullSpeed: 60,\n minSpeed: 4,\n lifetime: { min: 250, max: 550 },\n sizeFactor: 1.1,\n alpha: 0.5,\n // боковой разлёт брызг, мировых единиц в секунду\n sideSpeed: { min: 25, max: 60 },\n // всплеск при въезде: частиц на гусеницу и порог скорости въезда\n entryBurst: 10,\n entryMinSpeed: 20,\n // петля `tankWater` (src/config/sounds.js): громкость от скорости —\n // стоящий в воде танк еле слышно плещет (доля `minVolume`), на\n // `fullSpeed` мировых ед./с звучит в полный голос; скорость\n // воспроизведения растёт с ходом от `rate.min` до `rate.max`\n sound: { minVolume: 0.25, fullSpeed: 120, rate: { min: 0.9, max: 1.1 } },\n },\n\n // бустер: разовая вспышка-хвост при срабатывании\n boost: {\n color: 0xffd060,\n burst: 16,\n lifetime: { min: 200, max: 420 },\n sizeFactor: 1.4,\n alpha: 0.8,\n // скорость выброса хвоста против стрелки, мировых единиц в секунду\n tailSpeed: { min: 60, max: 140 },\n // визуальная копия `coreParams.surfaces.types.boost.minEntrySpeed`\n // (src/config/game.js): общего источника у рендера и WASM нет, и\n // расхождение даёт лишь лишнюю или пропущенную вспышку. Совпадение\n // держит tests/config/surfaces.test.js\n boostMinSpeed: 20,\n // скачок позиции за кадр (мировых единиц), после которого часть не\n // сравнивает клетки, а только запоминает текущую: телепорт и респаун\n // своего и чужого танка. ~2 клетки карты (step 32 · scale 0.6 = 19.2)\n resetDistance: 40,\n },\n\n // следы гусениц по поверхности: множитель начальной альфы и времени\n // жизни отметки и её тинт. Вода следов не оставляет вовсе. trail — сколько\n // секунд после съезда танк ещё оставляет такие следы на обычной клетке,\n // альфа спадает линейно (остаток масла `slickTime` в ядре — 1.5 с;\n // совпадение держит tests/config/surfaces.test.js)\n tracks: {\n oil: { alpha: 2, lifetime: 3, tint: 0x202028, trail: 1.5 },\n mud: { alpha: 1.5, lifetime: 1, tint: 0x5a4a3a },\n noMarks: ['water'],\n },\n};\n\n// просадка корпуса на приземлении (`src/client/parts/Tank.js`): чем\n// быстрее касание, тем сильнее сжатие.\n// minImpact/fullImpact численно повторяют `levels.landingShake` из\n// src/config/game.js (тряска камеры, считается в ядре): общего источника\n// у клиентского рендера и WASM нет, менять только парой — иначе будет\n// камера без пыли или пыль без камеры\nexport const landing = {\n // вертикальная скорость касания (уровней/с), дающая полную просадку\n fullImpact: 6,\n\n // максимальное сжатие корпуса по вертикали (доля). 0.22 на каждом\n // прыжке читалось как «танк складывается»; просадка обязана быть\n // заметной, но не менять силуэт\n squash: 0.14,\n\n // длительность просадки и возврата, мс\n duration: 260,\n\n // порог |vz| касания, ниже которого приземление считается мягким:\n // ни просадки, ни пыли, ни звука\n minImpact: 1.5,\n};\n\n// Анимированные элементы карты (`src/client/parts/map/animatedTiles.js`,\n// `decals.js`, `NeonSign.js`): вода и конвейеры, вентиляторы, мерцание\n// неона. Только клиент — хост о них не знает\nexport const animations = {\n // false — анимированные тайлы и декали стоят на кадре 0, неон не мерцает\n enabled: true,\n\n // потолок частоты смены кадров (и шагов вращения/мерцания)\n maxFps: 30,\n};\n\n// Ночь и освещение (`src/client/lighting/`): только атмосфера — враги\n// различимы, радар не меняется. Включается картой (`game.lighting.night`),\n// `enabled: false` выключает систему целиком на любой карте.\n// Карта освещённости уровня рисуется в пониженном разрешении и ложится на\n// сцену умножением (multiply), источники в ней складываются аддитивно\nexport const lighting = {\n enabled: true,\n\n // разрешение карты освещённости относительно экрана: свет мягкий, и\n // половины пикселей хватает с запасом\n resolution: 0.5,\n\n // потолок видимых источников на кадр (после отсечения по экрану)\n maxLights: 96,\n\n // фары живого танка: два конуса по курсу корпуса.\n // length — длина луча (мировые единицы); spread — полуширина конуса на\n // конце в долях длины; offset — смещение фары от оси корпуса в долях\n // полуширины корпуса. Блика на самой фаре нет: он засвечивал полкорпуса\n headlights: {\n length: 90,\n spread: 0.55,\n intensity: 0.9,\n color: 0xfff1c4,\n offset: 0.5,\n\n // стены гасят свет: конус обрезается по линии видимости из фары —\n // веером из `rays` лучей на ±90° от оси до первой клетки объёма уровня\n // (стены зданий, канала, перила) или горки. `enabled: false` — прежний\n // конус сквозь стены\n occlusion: {\n enabled: true,\n rays: 64,\n },\n\n // отсвет от стены: пятно на полу перед точкой, где ось фары упёрлась в\n // стену не дальше `maxDistance`. Сила — доля `intensity` фары,\n // спадающая к `length`; `radius` — мировые единицы. `intensity: 0` —\n // без отсвета. Каждое пятно — ещё один источник в счёт `maxLights`\n bounce: {\n intensity: 0.35,\n radius: 30,\n maxDistance: 80,\n },\n\n // засветка стены: часть луча, упёршаяся в стену, «заворачивается» на её\n // видимую грань — от подножия вверх на `height` уровней (не выше самой\n // стены); яркость — от точки упора до конца луча, то есть гаснет вверх.\n // intensity — множитель, 0 — без засветки\n wash: { height: 0.6, intensity: 1 },\n },\n\n // свет верхнего уровня на рампах, ведущих к нему: фары танка на плите и\n // фонари у верха рампы освещают клин (только в его пределах, земля под\n // мостом остаётся тёмной). Множитель силы 0..1: 1 — как на плите, 0 —\n // рампы свет сверху не ловят\n rampSpill: 1,\n\n // слабый свет под каждым танком — видимость врагов в темноте\n tankGlow: { radius: 26, intensity: 0.35, color: 0xbfd0ff },\n\n // вспышки: радиус (мировые единицы), сила, длительность (мс), цвет\n flash: {\n explosion: { radius: 140, intensity: 1.0, duration: 260, color: 0xffb060 },\n shot: { radius: 40, intensity: 0.6, duration: 70, color: 0xfff0a0 },\n // гибель танка: ярче и дольше выстрела, теплее взрыва бомбы\n wreck: { radius: 150, intensity: 1.1, duration: 320, color: 0xffa050 },\n },\n\n // пожар остова (`WreckFire`): мерцающий тёплый свет, сила следует за\n // силой пламени и гаснет вместе с ним\n wreckFire: { radius: 70, intensity: 0.75, color: 0xff9040, flicker: 0.45 },\n\n // засвет: яркий аддитивный блик на стороне танка или пропа, обращённой к\n // сильнейшему источнику (фонарь, чужая фара, вспышка). Форма — градиент\n // `glintTexture` по силуэту предмета. intensity — множитель силы\n // источника в точке, size — размер блика в долях размера предмета.\n // Число бликов на кадр не больше `maxLights`\n glints: { enabled: true, intensity: 0.8, size: 1.2 },\n\n // лучи в воздухе вокруг головы фонаря (`lamps[].head: true`): запечённые\n // лучи `lightShaftTexture`, аддитивно в карте освещённости уровня фонаря.\n // rays — лучей в текстуре; length — радиус лучей в долях радиуса фонаря;\n // intensity — яркость; shadows — просветы-тени от танков и пропов;\n // maxShadowCasters — теней на фонарь (ближайшие). Число фонарей с лучами\n // на кадр не больше `maxLights`\n shafts: {\n enabled: true,\n rays: 14,\n length: 1.3,\n intensity: 0.35,\n shadows: true,\n maxShadowCasters: 4,\n },\n};\n","import sounds from './sounds.js';\nimport {\n seeThrough,\n parallax,\n volume,\n shadow,\n tilt,\n landing,\n lighting,\n animations,\n} from './render.js';\n\n// Игровая половина клиентского CONFIG_DATA: сущности рендера, канвасы,\n// keyset игрока, схемы panel/stat, тексты chat/vote/gameInform. Движковые\n// дефолты — src/config/clientDefaults.js; merge выполняет buildClientConfig\n// (в этапе 6 этот объект соберёт HostPlugin.buildClientGameConfig).\nexport default {\n // ***** parts ***** //\n parts: {\n // распределение данных в заданные классы\n gameSets: {\n c1: ['Map', 'MapRadar'],\n c2: ['Map'],\n m1: ['Tank', 'TankRadar', 'Smoke', 'Tracks', 'Dust', 'WreckFire'],\n w1: ['ShotEffect'],\n w2: ['Bomb'],\n w2e: ['ExplosionEffect'],\n },\n // отображение классов на полотнах\n entitiesOnCanvas: {\n Map: 'vimp',\n MapRadar: 'radar',\n TankRadar: 'radar',\n Tank: 'vimp',\n ShotEffect: 'vimp',\n Bomb: 'vimp',\n ExplosionEffect: 'vimp',\n Smoke: 'vimp',\n Tracks: 'vimp',\n Dust: 'vimp',\n WreckFire: 'vimp',\n },\n\n // ассеты, которые должны быть \"запечены\" (созданы один раз) при старте игры\n bakedAssets: {\n vimp: [\n {\n // id доступа к текстуре и название функции \"запекания\"\n name: 'explosionTexture',\n component: 'ExplosionEffect', // компонент, которому назначен ассет\n params: {\n radius: 50, // радиус круга\n blur: 2, // сила размытия\n quality: 40, // проходов размытия\n color: 0xffffff, // цвет (белый для удобного tinting)\n },\n },\n {\n name: 'impactParticleTexture',\n component: 'ShotEffect',\n params: {\n radius: 4,\n blur: 1,\n quality: 10,\n color: 0xffffff,\n },\n },\n {\n name: 'funnelTexture',\n component: 'ExplosionEffect',\n params: {\n baseRadius: 25, // базовый радиус пятна\n irregularity: 4, // неровность краёв\n // размытие: сильнее ~1/4 радиуса пятно размазывается\n // на весь холст, а бортик перестаёт читаться\n blur: 3,\n numPoints: 20, // точек по контуру (мало - силуэт выходит угловатым)\n colorFill: 0x1c1c1c, // тёмная выемка (видна на светлых картах)\n colorRim: 0xb0b0b0, // светлый бортик выброса (виден на тёмных)\n rimWidth: 4, // толщина бортика\n variants: 5, // сколько силуэтов запечь (воронка берёт случайный)\n },\n },\n {\n name: 'smokeTexture',\n component: 'Smoke',\n params: {\n radius: 3, // базовый радиус частицы дыма\n blur: 1, // размытие для мягкости\n quality: 40, // проходов размытия\n color: 0xffffff, // цвет для последующего tint'а\n },\n },\n {\n name: 'tankTexture',\n component: 'Tank',\n params: {\n colors: {\n teamId1: 0x552222,\n teamId2: 0x225522,\n },\n },\n },\n {\n // атлас граней 3D-модели танка (src/client/tank3d/): те же цвета\n // команд, что у плоской текстуры\n name: 'tankModelTexture',\n component: 'Tank',\n params: {\n colors: {\n teamId1: 0x552222,\n teamId2: 0x225522,\n },\n },\n },\n {\n name: 'bombTexture',\n component: 'Bomb',\n params: {\n colorOuter: 0xffffff,\n colorInner: 0x0f0f0f,\n },\n },\n {\n name: 'tankShadowTexture',\n component: 'Tank',\n params: {\n // силуэт корпуса: та же пропорция 4:3, что у самого танка\n // (`tankTexture`). Размытие маленькое — оно даёт мягкий край, а\n // не второй силуэт вокруг фигуры; нормировка по contentSize\n // оставляет видимый размер независимым от запаса под размытие\n width: 40,\n height: 30,\n radius: 6,\n blur: 2,\n quality: 20,\n color: 0x000000,\n },\n },\n {\n // пыль из-под гусениц и всплеск приземления: тот же размытый круг,\n // что у дыма, белым — цвет даёт tint частицы\n name: 'dustTexture',\n component: 'Dust',\n params: {\n radius: 4,\n blur: 1,\n quality: 20,\n color: 0xffffff,\n },\n },\n {\n name: 'trackMarkTexture',\n component: 'Tracks',\n params: {\n width: 4,\n length: 5,\n color: 0x1a1a12,\n },\n },\n {\n // копоть разрушенного пропа без своей картинки imgDestroyed\n // (бочка): тёмное размытое пятно, вариантов несколько\n name: 'scorchTexture',\n component: 'Map',\n params: {\n baseRadius: 20, // базовый радиус пятна\n irregularity: 5, // неровность краёв\n blur: 4, // мягкий край (не сильнее ~1/4 радиуса)\n numPoints: 16, // точек по контуру\n color: 0x141210, // цвет копоти\n coreColor: 0x050505, // тёмная середина\n coreRatio: 0.55, // доля радиуса тёмной середины\n variants: 4, // сколько силуэтов запечь\n },\n },\n {\n // щепки разлёта при разрушении пропа: серые, цвет даёт tint\n name: 'debrisTexture',\n component: 'Map',\n params: {\n length: 8, // длина щепки (сторона холста)\n width: 3, // наибольшая толщина щепки\n color: 0xffffff,\n variants: 4, // сколько силуэтов запечь\n },\n },\n {\n // ночь: радиальное пятно света (фонари, свет под танком,\n // вспышки). Регистрирует в сервисе освещения `Map`\n name: 'lightRadialTexture',\n component: 'Map',\n params: {\n radius: 64, // радиус пятна\n rings: 32, // колец плавного спада\n blur: 2, // против ступеней спада\n },\n },\n {\n // ночь: голова фонаря — яркий диск с ореолом (светильник в асфальте)\n name: 'lampHeadTexture',\n component: 'Map',\n params: {\n core: 3, // радиус яркого диска\n halo: 10, // радиус ореола\n rings: 12,\n blur: 1,\n },\n },\n {\n // ночь: засвет — градиент «край → центр», режется силуэтом танка\n // или пропа. Регистрирует в сервисе освещения `Map`, `Tank`\n // берёт его из сервиса\n name: 'glintTexture',\n component: 'Map',\n params: {\n radius: 32, // полуразмер квадрата\n strips: 16, // полос градиента\n blur: 2,\n },\n },\n {\n // ночь: лучи в воздухе вокруг головы фонаря\n name: 'lightShaftTexture',\n component: 'Map',\n params: {\n radius: 128, // наибольшая длина луча\n rays: lighting.shafts.rays,\n width: 0.05, // полуширина луча на конце, рад\n seed: 7, // рисунок лучей\n blur: 3,\n },\n },\n {\n // ночь: конус фары (вершина на фаре, спад по длине)\n name: 'headlightConeTexture',\n component: 'Tank',\n params: {\n length: 128, // длина клина\n halfWidth: 64, // полуширина на конце\n strips: 24, // полос спада по длине\n blur: 4, // мягкость краёв\n },\n },\n {\n // гибель танка: мягкое пятно с ярким центром — вспышка, огненный шар,\n // языки пламени, искры и отсвет (аддитивно, цвет даёт tint)\n name: 'wreckFireTexture',\n component: 'WreckFire',\n params: { radius: 32, rings: 24, blur: 2 },\n },\n {\n // клубы дыма над остовом: размытый круг, белый — цвет даёт tint\n name: 'wreckSmokeTexture',\n component: 'WreckFire',\n params: { radius: 8, blur: 3, quality: 20, color: 0xffffff },\n },\n {\n // копоть под остовом: те же пятна, что у разрушенного пропа\n name: 'wreckScorchTexture',\n component: 'WreckFire',\n params: {\n baseRadius: 20,\n irregularity: 5,\n blur: 4,\n numPoints: 16,\n color: 0x15120f,\n coreColor: 0x060505,\n coreRatio: 0.5,\n variants: 3,\n },\n },\n ],\n radar: [\n {\n name: 'tankRadarTexture',\n component: 'TankRadar',\n params: {\n radius: 6,\n borderWidth: 2,\n crossSize: 9,\n crossThickness: 1.5,\n colors: {\n teamId1: 0x552222,\n teamId2: 0x225522,\n },\n },\n },\n ],\n },\n\n // карта зависимостей компонентов\n componentDependencies: {\n // Map печёт свои слои в текстуру; по рендереру восстанавливают центр\n // камеры все, кто рисуется на высоте (проекция 2.5D,\n // src/client/parallax.js): слои и тела карты, танк, следы, дым, бомбы,\n // эффекты и пожар остова\n renderer: [\n 'Map',\n 'Tank',\n 'Tracks',\n 'Smoke',\n 'Dust',\n 'Bomb',\n 'ShotEffect',\n 'ExplosionEffect',\n 'WreckFire',\n ],\n // база ассетов игры: Map строит из неё URL тайл-листов и спрайтов\n // динамических тел (`${assetsBase}img/<file>`), см. assets/img/\n assetsBase: ['Map'],\n // компоненты использующие звук\n // Map — треск разрушения пропа (`propBreak`), WreckFire — взрыв\n // гибели танка (`tankExplosion`)\n soundManager: [\n 'ExplosionEffect',\n 'ShotEffect',\n 'Bomb',\n 'Tank',\n 'Dust',\n 'Map',\n 'WreckFire',\n ],\n // геометрия динамики карты (рендерные боксы ящиков): эффект попадания\n // держит якорь на теле и спрашивает, где тело нарисовано, в момент\n // запуска эффекта.\n // Сервис есть только при включённом client-side prediction\n mapDynamics: ['ShotEffect'],\n // прогоны рамп из ядра: по ним слой строит клин горки — та же\n // геометрия, по которой физика ставит стражей прогона (решение 2\n // плана review-multilevel-3), и эффект выстрела кладёт осколки\n // попадания на склон (`heightAt`)\n rampRuns: ['Map', 'ShotEffect'],\n // поверхности клеток из ядра (песок, грязь, вода, масло, бустер): по\n // ним пыль, брызги и следы выбирают эффект — та же таблица, что у\n // физики\n surfaces: ['Dust', 'Tracks', 'Tank'],\n // высоты объёмов карты по клеткам уровней: пишут слои Map, читает\n // эффект выстрела — попадание в стену кончается на её видимой грани,\n // а не на подножии (src/client/volumes.js); сервис освещения получает\n // его в `services()` напрямую\n volumes: ['Map', 'ShotEffect'],\n // «танк выстрелил»: эффект выстрела сообщает id стрелка, танк играет\n // визуальную отдачу (src/client/recoil.js)\n shots: ['ShotEffect', 'Tank'],\n // «взрыв»: эффект взрыва и гибель танка сообщают точку, радиус и\n // уровень, танки в радиусе играют визуальную реакцию\n // (src/client/blastJolt.js)\n blasts: ['ExplosionEffect', 'Tank', 'WreckFire'],\n // 2.5D: где локальный игрок и на каком он уровне. Пишет Tank, читают\n // все, кто уступает ему видимость: плита моста и ящики на ней, чужие\n // танки, дым, бомбы, эффекты, следы и пожар остова — по одной формуле\n // (levelView.alphaFor)\n levelView: [\n 'Tank',\n 'Map',\n 'MapRadar',\n 'Smoke',\n 'Bomb',\n 'ShotEffect',\n 'ExplosionEffect',\n 'Tracks',\n 'Dust',\n 'WreckFire',\n ],\n // «своя ли это сущность» — движковый сервис; локальный танк\n // единственный, кто вправе писать в levelView, а свой танк и свой\n // выстрел вдобавок звучат непространственно (spatial: false)\n localPlayer: ['Tank', 'ShotEffect'],\n // ночь и освещение (src/client/lighting/): слои карты заводят карты\n // освещённости уровней и фонари, танк — фары, эффекты —\n // вспышки, пожар остова — мерцающий свет и вспышку гибели\n lighting: ['Map', 'Tank', 'ExplosionEffect', 'ShotEffect', 'WreckFire'],\n // журнал клиентских ошибок движка (сервис `diagnostics`, vimp-engine\n // ≥ 0.35.0): необязательный — на старом движке парт получает undefined, и\n // в requires манифеста его нет намеренно\n diagnostics: ['Tank', 'Map'],\n },\n\n // 2.5D-рендер: прозрачность уровней над игроком, проекция высоты и\n // объёмные элементы.\n // Источник этих чисел — src/config/render.js: партам движок конфиг не\n // отдаёт (фабрика даёт только сервисы), поэтому они импортируют модуль\n // напрямую, а здесь он лежит как часть клиентского конфига игры\n seeThrough,\n parallax,\n volume,\n shadow,\n tilt,\n landing,\n lighting,\n animations,\n\n // звуковые ассеты\n sounds,\n },\n\n initIdList: ['vimp', 'radar', 'panel', 'chat'],\n\n // ***** modules ***** //\n modules: {\n canvasManager: {\n // полотна создаёт main.js из этого конфига (canvas#<id> в DOM);\n // width/height — стартовый размер до первого resize\n canvases: {\n vimp: {\n width: 960,\n height: 600,\n aspectRatio: '16:9',\n baseScale: '5:1',\n dynamicCamera: true,\n shakeCamera: true,\n },\n radar: {\n width: 150,\n height: 150,\n fixSize: '150',\n baseScale: '1:8',\n },\n },\n },\n\n controls: {\n keySetList: [\n // spectator keyset\n {\n 78: 'nextPlayer', // next player (n)\n 80: 'prevPlayer', // prev player (p)\n },\n // player keyset\n {\n 87: 'forward', // forward (w)\n 83: 'back', // back (s)\n 65: 'left', // left (a)\n 68: 'right', // right (d)\n 85: 'gunCenter', // gun center (u)\n 75: 'gunLeft', // gun left (k)\n 76: 'gunRight', // gun right (l)\n 74: 'fire', // fire (j)\n 78: 'nextWeapon', // next weapon (n)\n 80: 'prevWeapon', // prev weapon (p)\n },\n ],\n },\n\n chat: {\n params: {\n messages: {\n // teams/status\n s: [\n 'Team {0} is full. Your current team: {1}', // 0\n 'Your team: {0}', // 1\n 'Your new team: {0}', // 2\n 'Your new status: spectator', // 3\n '⚔️ {0} killed {1}', // 4\n '⚡ {0} joined the game', // 5\n '👋 {0} left the game', // 6\n ],\n // vote\n v: [\n 'A vote has been created',\n 'Voting has started',\n 'Your vote has been accepted',\n 'Voting is temporarily unavailable',\n 'Vote passed',\n 'Vote failed',\n ],\n // map\n m: ['Current map: {0}', 'Next map: {0}'],\n // command\n c: ['Command not found', 'Your rank: {0}'],\n // name\n n: ['Invalid name', '{0} changed name to {1}'],\n // bots\n b: [\n 'Only active players can use /bot',\n 'Invalid bot count',\n 'Invalid team name',\n '{0} bot(s) created for {1}',\n 'All bots removed from {0}',\n '{0} bot(s) created',\n 'All bots removed',\n ],\n },\n },\n },\n\n panel: {\n keys: {\n t: 'time',\n h: 'health',\n wa: 'activeWeapon',\n w1: 'bullet',\n w2: 'bomb',\n },\n // схема DOM панели: PanelView генерирует ячейки в порядке fields;\n // семантику задаёт type ('bar'|'value'|'time'|'weapon'), не имя поля\n fields: [\n {\n name: 'health',\n elem: 'panel-health',\n type: 'bar',\n max: 100,\n blocks: 30,\n },\n { name: 'bullet', elem: 'panel-bullet', type: 'weapon' },\n { name: 'bomb', elem: 'panel-bomb', type: 'weapon' },\n { name: 'time', elem: 'panel-time', type: 'time' },\n ],\n },\n\n stat: {\n params: {\n // подписи колонок scoreboard (StatView генерирует шапку и таблицы)\n columns: ['names', 'status', 'score', 'deaths', 'latency'],\n heads: {\n 1: 'team1',\n 2: 'team2',\n },\n bodies: {\n 1: 'team1',\n 2: 'team2',\n 3: 'spectators',\n },\n sortList: {\n team1: [\n [2, true],\n [3, false],\n ],\n team2: [\n [2, true],\n [3, false],\n ],\n },\n },\n },\n\n vote: {\n params: {\n templates: {\n teamChange: ['Choose a team', 'teams', true],\n mapChangeBySystem: ['Choose the next map'],\n mapChangeByUser: ['{0} suggested the map: {1}', ['Yes', 'No']],\n createBotsForTeam: [\n '{0} suggests creating {1} bot(s) to {2}. Agree?',\n ['Yes', 'No'],\n ],\n removeBotsForTeam: [\n '{0} suggests removing all bots from {1}. Agree?',\n ['Yes', 'No'],\n ],\n createBots: [\n '{0} suggests creating {1} bot(s) to the game. Agree?',\n ['Yes', 'No'],\n ],\n removeBots: [\n '{0} suggests removing all bots from the game. Agree?',\n ['Yes', 'No'],\n ],\n },\n menu: [\n ['teamChange', ['Switch team', 'teams']],\n ['mapChange', ['Suggest map', 'maps']],\n ],\n },\n },\n },\n\n // game information\n gameInform: {\n list: ['{0} WINS!', 'ROUND START!', 'GAME OVER!'],\n },\n};\n","import models from '../data/models.js';\n\n// Конфиг авторизации игры: форма (elems/params) и игровые валидаторы.\n// params уходят клиенту по AUTH_DATA (порт 1); validators — код, работает\n// на обеих сторонах (хост — validateAuth в Worker'е, клиент — форма).\n// Приезжает через HostPlugin.authSchema.\nexport default {\n elems: {\n authId: 'auth',\n errorId: 'auth-error',\n enterId: 'auth-enter',\n fieldsId: 'auth-fields',\n titleId: 'auth-title',\n informsId: 'auth-informs',\n },\n // тексты формы (заголовок + help-секции): auth.pug — нейтральный каркас,\n // игровые тексты подставляет AuthView из этих данных\n texts: {\n title: 'Tanks',\n sections: [\n {\n heading: 'Controls',\n lines: [\n { keys: 'w, a, s, d', text: 'move the tank' },\n { keys: 'k, l', text: 'turn the gun' },\n { keys: 'u', text: 'center the gun' },\n { keys: 'j', text: 'fire' },\n { keys: 'n, p', text: 'switch weapon/player', last: true },\n { keys: 'c', text: 'chat/command line' },\n { keys: 'm', text: 'menu' },\n { keys: '<Tab>', text: 'stats', last: true },\n ],\n },\n {\n heading: 'Command line',\n lines: [\n { keys: '/name <name>', text: 'change nickname' },\n { keys: '/mapname', text: 'show current map name' },\n { keys: '/timeleft', text: 'map time left' },\n { separator: true },\n { keys: '/bot <count>', text: 'add bot(s)' },\n { keys: '/bot <count> <team>', text: 'add to team' },\n { keys: '/bot 0', text: 'remove all bots' },\n { keys: '/bot 0 <team>', text: 'remove from team' },\n ],\n },\n ],\n },\n params: [\n {\n name: 'model',\n value: 'm1',\n options: {\n control: 'select',\n label: 'Model',\n options: Object.keys(models), // варианты для control: 'select', не путать с внешним ключом протокола `options` (PS_AUTH_DATA)\n validator: 'isValidModel',\n storage: 'model',\n },\n },\n ],\n validators: {\n // валидная модель — любая из данных игры\n isValidModel: model => model in models,\n },\n};\n","// Чат-команда /bot танков: создание/удаление ботов + голосования.\n// /bot 5 team1 # создаёт 5 ботов в team1\n// /bot 10 # создаёт 10 ботов, распределив их равномерно\n// /bot 0 team2 # удаляет ботов team2\n// /bot 0 # удаляет всех ботов\n// Регистрируется в движковом CommandProcessor через HostPlugin.chatCommands.\n// Worker-safe.\n//\n// ctx — контекст меты движка: participants, chat, scripted (модуль ботов),\n// roundManager, voteCoordinator, teams, spectatorTeam, spectatorId.\n\n// исполняет команду /bot\nfunction executeBotCommand(ctx, count, team) {\n if (team) {\n ctx.scripted.removeScripted(team);\n\n if (count > 0) {\n count = ctx.scripted.createScripted(count, team);\n ctx.chat.pushSystem('BOT_CREATED_FOR_TEAM', [count, team]);\n } else {\n ctx.chat.pushSystem('BOT_REMOVED_FROM_TEAM', [team]);\n }\n } else {\n ctx.scripted.removeScripted();\n\n if (count > 0) {\n count = ctx.scripted.createScripted(count, null);\n ctx.chat.pushSystem('BOT_CREATED', [count]);\n } else {\n ctx.chat.pushSystem('BOT_REMOVED');\n }\n }\n\n ctx.roundManager.initiateNewRound();\n}\n\n// инициирует голосование за ботов\nfunction initiateBotVote(ctx, gameId, count, team) {\n const userName = ctx.participants.get(gameId).name;\n const voteCategory = 'botManagement';\n let voteName;\n let voteArgs;\n\n if (!ctx.voteCoordinator.canCreateVote(voteCategory, gameId)) {\n return;\n }\n\n if (team) {\n if (count > 0) {\n voteName = 'createBotsForTeam';\n voteArgs = [userName, count, team];\n } else {\n voteName = 'removeBotsForTeam';\n voteArgs = [userName, team];\n }\n } else {\n if (count > 0) {\n voteName = 'createBots';\n voteArgs = [userName, count];\n } else {\n voteName = 'removeBots';\n voteArgs = [userName];\n }\n }\n\n const payload = { name: voteName, params: voteArgs };\n const userList = ctx.participants\n .getHumans()\n .map(u => u.gameId)\n .filter(id => id !== gameId);\n\n ctx.voteCoordinator.createVote({\n voteName,\n voteCategory,\n payload,\n resultFunc: result => {\n if (result === 'Yes') {\n ctx.chat.pushSystem('VOTE_PASSED');\n executeBotCommand(ctx, count, team);\n } else {\n ctx.chat.pushSystem('VOTE_FAILED');\n }\n },\n userList,\n gameId,\n });\n}\n\nexport default {\n name: '/bot',\n\n handler(ctx, gameId, args) {\n const user = ctx.participants.get(gameId);\n\n if (user.teamId === ctx.spectatorId) {\n ctx.chat.pushSystemByUser(gameId, 'BOT_PLAYERS_ONLY');\n return;\n }\n\n const count = parseInt(args[0], 10);\n const team = args[1] || null;\n\n if (isNaN(count) || count < 0) {\n ctx.chat.pushSystemByUser(gameId, 'BOT_INVALID_COUNT');\n return;\n }\n\n // если команда не соответствует\n if (team && (!ctx.teams[team] || team === ctx.spectatorTeam)) {\n ctx.chat.pushSystemByUser(gameId, 'BOT_INVALID_TEAM');\n return;\n }\n\n // если команда на удаление ботов, но удалять нечего\n if (count === 0) {\n if (team && ctx.scripted.getCountForTeam(team) === 0) {\n ctx.chat.pushSystemByUser(gameId, 'BOT_REMOVED_FROM_TEAM', [team]);\n return;\n }\n\n if (ctx.scripted.getCount() === 0) {\n ctx.chat.pushSystemByUser(gameId, 'BOT_REMOVED');\n return;\n }\n }\n\n // проверка количества активных игроков\n const activePlayerCount = ctx.participants\n .getHumans()\n .filter(u => u.teamId !== ctx.spectatorId).length;\n\n // если игрок один — исполнение команды, иначе запуск голосования\n if (activePlayerCount <= 1) {\n executeBotCommand(ctx, count, team);\n } else {\n initiateBotVote(ctx, gameId, count, team);\n }\n },\n};\n","// Команды, которые раньше разбирал сам движок. Своих команд у движка больше\n// нет: CommandProcessor — только реестр, и весь набор, доступный игроку,\n// объявляет игра (HostPlugin.chatCommands). Одно и то же имя в разных играх\n// может делать разное или отсутствовать вовсе — танкам нужны все пять.\n//\n// Коды системных сообщений здесь движковые (RANK, COMMANDS_NOT_FOUND — группа\n// 'c'), тексты лежат в modules.chat.params.messages (src/config/client.js).\n//\n// ctx — контекст меты движка: participants, chat, scripted, roundManager,\n// voteCoordinator, timerManager, playerDataSync, teams, spectatorTeam,\n// spectatorId, isDevMode. Worker-safe.\n\n// mm:ss из миллисекунд\nfunction formatTime(ms) {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');\n const seconds = String(totalSeconds % 60).padStart(2, '0');\n\n return `${minutes}:${seconds}`;\n}\n\n/// '/name <ник>' — проверку и рассылку делает движок (RoundManager сам шлёт\n/// сообщения группы 'n')\nexport const nameCommand = {\n name: '/name',\n\n handler(ctx, gameId, args) {\n ctx.roundManager.changeName(gameId, args.join(' '));\n },\n};\n\n/// '/nr' — новый раунд, только в dev-сборке\nexport const newRoundCommand = {\n name: '/nr',\n\n handler(ctx, gameId) {\n if (ctx.isDevMode) {\n ctx.roundManager.initiateNewRound();\n } else {\n ctx.chat.pushSystemByUser(gameId, 'COMMANDS_NOT_FOUND');\n }\n },\n};\n\n/// '/timeleft' — сколько осталось до смены карты\nexport const timeLeftCommand = {\n name: '/timeleft',\n\n handler(ctx, gameId) {\n ctx.chat.pushSystemByUser(gameId, [\n formatTime(ctx.timerManager.getMapTimeLeft()),\n ]);\n },\n};\n\n/// '/mapname' — название текущей карты\nexport const mapNameCommand = {\n name: '/mapname',\n\n handler(ctx, gameId) {\n ctx.chat.pushSystemByUser(gameId, [ctx.roundManager.currentMap]);\n },\n};\n\n/// '/rank' — ранг игрока с auth-сервиса (движок держит его в PlayerDataSync)\nexport const rankCommand = {\n name: '/rank',\n\n handler(ctx, gameId) {\n ctx.chat.pushSystemByUser(gameId, 'RANK', [\n ctx.playerDataSync.getRank(gameId),\n ]);\n },\n};\n","// Игровые коды системных сообщений танков (группа b:* — боты).\n// Merge в движковый реестр через registerCodes; в этапе 6 приедут через\n// HostPlugin.systemMessages. Тексты шаблонов — на клиенте (конфиг чата).\nexport default {\n BOT_PLAYERS_ONLY: 'b:0', // Only active players can use /bot\n BOT_INVALID_COUNT: 'b:1', // Invalid bot count\n BOT_INVALID_TEAM: 'b:2', // Invalid team name\n BOT_CREATED_FOR_TEAM: 'b:3', // {0} bot(s) created for {1}\n BOT_REMOVED_FROM_TEAM: 'b:4', // All bots removed from {0}\n BOT_CREATED: 'b:5', // {0} bot(s) created\n BOT_REMOVED: 'b:6', // All bots removed\n};\n","// Игровой scripted-модуль танков: тонкий менеджер ботов поверх ядра.\n// ИИ, навигация и пространственная сетка живут в Rust-ядре\n// (spawn_scripted_actor создаёт танк + контроллер) — здесь только\n// регистрация участников и связка со Stat/Panel. Контракт scripted-модуля\n// (дергает движок — RoundManager, HostGame): createMap,\n// createScripted(count, team?), removeScripted(team?), removeOneForHuman(team),\n// getCount(), getCountsPerTeam(). Остальные методы — внутриигровые\n// (чат-команда /bot). Worker-safe: только изоморфные API.\nexport default class TanksBotManager {\n /**\n * @param {Object} ctx - контекст движка (HostPlugin.createModules):\n * participants (единый реестр), coreAdapter (спавн/удаление танка),\n * panel, stat, scripted ({ namePrefix, defaultModel } из конфига игры).\n */\n constructor({ participants, coreAdapter, panel, stat, scripted }) {\n this._participants = participants;\n this._coreAdapter = coreAdapter;\n this._panel = panel;\n this._stat = stat;\n\n this._model = scripted.defaultModel; // модель танка для ботов\n this._respawns = null; // данные респаунов текущей карты\n }\n\n // запоминает респауны карты (для распределения ботов по командам)\n createMap(mapData) {\n this._respawns = mapData.respawns;\n }\n\n // создаёт заданное количество ботов-участников (танки в ядре — на старте\n // раунда через RoundManager → coreAdapter.createPlayer → core.spawn_scripted_actor)\n createScripted(count, teamName = null) {\n if (!this._respawns) {\n return 0;\n }\n\n const playableTeams = this._participants.getPlayableTeams();\n\n let createdCount = 0;\n\n for (let i = 0; i < count; i += 1) {\n if (this._participants.isFull) {\n break; // достигнут глобальный лимит игроков\n }\n\n let targetTeam = teamName;\n\n if (!targetTeam) {\n // равномерное распределение по наименее заполненной команде\n targetTeam = playableTeams.sort(\n (a, b) =>\n this._participants.getTeamSize(a) -\n this._participants.getTeamSize(b),\n )[0];\n }\n\n if (\n !targetTeam ||\n !this._respawns[targetTeam] ||\n this._participants.getTeamSize(targetTeam) >=\n this._respawns[targetTeam].length\n ) {\n continue; // нет свободных мест в команде или команда не найдена\n }\n\n const gameId = this._participants.createScripted({\n team: targetTeam,\n model: this._model,\n });\n const participant = this._participants.get(gameId);\n\n this._stat.addUser(gameId, participant.teamId, {\n name: participant.name,\n status: 'dead',\n latency: 'BOT',\n });\n this._panel.addUser(gameId);\n\n createdCount += 1;\n }\n\n return createdCount;\n }\n\n // удаляет ботов (всех либо конкретной команды)\n removeScripted(teamName = null) {\n const botsToRemove = teamName\n ? this._participants.getScripted().filter(bot => bot.team === teamName)\n : this._participants.getScripted();\n\n botsToRemove.forEach(bot => this._removeBotById(bot.gameId));\n }\n\n // удаляет одного бота из команды, чтобы освободить место игроку\n removeOneForHuman(teamName) {\n for (const bot of this._participants.getScripted()) {\n if (bot.team === teamName) {\n this._removeBotById(bot.gameId);\n return true;\n }\n }\n\n return false;\n }\n\n // удаляет бота по gameId из всех систем и ядра\n _removeBotById(gameId) {\n const participant = this._participants.get(gameId);\n\n if (!participant || !participant.isScripted) {\n return;\n }\n\n this._stat.removeUser(gameId, participant.teamId);\n this._panel.removeUser(gameId);\n this._coreAdapter.removePlayer(gameId); // → core.remove_scripted_actor (танк + ИИ)\n\n this._participants.remove(gameId);\n }\n\n getBotById(gameId) {\n const participant = this._participants.get(gameId);\n\n return participant && participant.isScripted ? participant : undefined;\n }\n\n getBots() {\n return this._participants.getScripted();\n }\n\n getCount() {\n return this._participants.getScripted().length;\n }\n\n getCountForTeam(teamName) {\n return this._participants\n .getScripted()\n .filter(bot => bot.team === teamName).length;\n }\n\n getCountsPerTeam() {\n const counts = {};\n\n for (const bot of this._participants.getScripted()) {\n counts[bot.team] = (counts[bot.team] || 0) + 1;\n }\n\n return counts;\n }\n}\n","import TanksBotManager from './TanksBotManager.js';\n\n// Фабрика игровых host-модулей (HostPlugin.createModules).\n// ctx — движковый контекст: participants, coreAdapter, panel, stat, chat,\n// socketManager, scripted.\nexport default function createModules(ctx) {\n return { scripted: new TanksBotManager(ctx) };\n}\n","// В браузере и в Worker'е ядро грузится по URL .wasm-ассета\n// (GameManifest.entries.wasm) через init() web-глюe. Headless-раннер\n// движка (vimp-sim) работает в Node, где fetch() не умеет file:, и\n// передаёт вместо ассета file:-URL nodejs-глюe (entries.wasmNode): там\n// wasm подгружен самим модулем, init() не существует.\n//\n// Общий модуль для обеих половин плагина (host и client) — ветка выбора\n// ядра обязана быть одинаковой, иначе headless-прогон пойдёт на разных\n// сборках ядра.\n\n/**\n * @param {string} [wasmUrl]\n * @returns {boolean} Это путь к nodejs-глюe, а не к .wasm-ассету.\n */\nexport const isNodeCore = wasmUrl =>\n // сравнивается путь, а не весь URL: у ассета бывает ?v=… кэш-бастер\n (wasmUrl ?? '').split(/[?#]/)[0].endsWith('.js');\n\n/**\n * @param {string} wasmUrl - file:-URL nodejs-глюe (CommonJS).\n * @returns {Promise<Object>} Пространство имён модуля (GameCore/ClientCore).\n */\nexport const loadNodeCore = wasmUrl => import(/* @vite-ignore */ wasmUrl);\n","import { ENGINE_API_VERSION } from 'vimp-engine/config/opcodes.js';\nimport init, { GameCore } from '../../core/pkg-web/vimp_tanks_core.js';\nimport gameConfig from '../config/game.js';\nimport clientConfig from '../config/client.js';\nimport authSchema from '../config/auth.js';\nimport botCommand from './botCommand.js';\nimport {\n mapNameCommand,\n nameCommand,\n newRoundCommand,\n rankCommand,\n timeLeftCommand,\n} from './metaCommands.js';\nimport systemMessages from './systemMessages.js';\nimport createModules from './createModules.js';\nimport { isNodeCore, loadNodeCore } from '../nodeCore.js';\n\n// HostPlugin танков (Worker-safe): вся игровая половина хоста одним объектом.\n// default export host-entry игры (vite.config.js --mode host,\n// Этап 6.1); host.worker.js грузит его динамически по entries.host\n// GameManifest (Этап 6.4). Стандартный словарь событий ядра\n// (panelSet/panelActive/death/shake) движок роутит сам; из 'custom'-событий\n// танки разбирают только mapDerivedError (см. onCoreEvent).\nexport default {\n id: 'tanks',\n engineApi: ENGINE_API_VERSION,\n\n // без слоёных карт движка (`levels`/`ramps` в MAP_DATA, маски уровней в\n // физике) карта overpass соберётся без второго уровня и без единой\n // ошибки — поэтому возможность объявлена жёстким требованием\n requires: ['map.layers', 'map.levelsN', 'map.gameData', 'map.bodyState'],\n\n // wasmUrl — из GameManifest.entries.wasm (мастер, Этап 6.2); init() грузит\n // по явному url, а не через import.meta.url-резолюцию глюe-модуля\n // (риск #3 PLAN.md — важно для рабочего Worker'а)\n async createCore(coreConfigJson, { wasmUrl }) {\n if (isNodeCore(wasmUrl)) {\n const node = await loadNodeCore(wasmUrl);\n\n return new node.GameCore(coreConfigJson);\n }\n\n // eslint-disable-next-line camelcase -- wasm-bindgen init() option name\n await init({ module_or_path: wasmUrl });\n\n return new GameCore(coreConfigJson);\n },\n\n // 'custom'-событие ядра: движок передаёт сюда только его data.\n // mapDerivedError — поле карты `game` не разобралось при пересборке после\n // восстановления из дампа (core/src/tanks.rs, rebuild_map_derived): ядро\n // шагает дальше без производных данных, а причина уходит в консоль.\n // Прочие custom-события игнорируются\n onCoreEvent(data) {\n if (data?.type === 'mapDerivedError') {\n console.warn(`[tanks] map game data: ${data.message}`);\n }\n },\n\n gameConfig,\n authSchema,\n // движок своих команд не разбирает: весь набор объявляет игра\n // (src/host/metaCommands.js — бывшие движковые /name, /nr, /timeleft,\n // /mapname, /rank)\n chatCommands: [\n botCommand,\n nameCommand,\n newRoundCommand,\n timeLeftCommand,\n mapNameCommand,\n rankCommand,\n ],\n systemMessages,\n createModules,\n buildClientGameConfig: () => clientConfig,\n};\n"],"names":["GameCore","ptr","GameCoreFinalization","wasm","deferred1_0","deferred1_1","ret","getStringFromWasm0","v1","getArrayF32FromWasm0","game_id","seq","x","y","flags","action","key_name","ptr0","passStringToWasm0","len0","WASM_VECTOR_LEN","ptr1","len1","data","passArray8ToWasm0","takeFromExternrefTable0","op","payload","v3","getArrayU8FromWasm0","map_json","config_json","server_time","has_camera","camera_x","camera_y","force_reset","shake","player_id","isLikeNone","team_id","angle_deg","level","model","dt","__wbg_get_imports","arg0","arg1","table","offset","len","getFloat32ArrayMemory0","getUint8ArrayMemory0","cachedFloat32ArrayMemory0","decodeText","cachedUint8ArrayMemory0","arg","malloc","realloc","buf","cachedTextEncoder","mem","code","view","idx","value","cachedTextDecoder","MAX_SAFARI_DECODE_BYTES","numBytesDecoded","__wbg_finalize_init","instance","module","__wbg_load","imports","e","expectedResponseType","bytes","type","__wbg_init","module_or_path","wallHeightsOf","map","out","add","layers","volumes","layer","height","tile","heights","config","withWallHeights","wallHeights","canopy","generatedObjects","posX","posY","tiles","buildWall","x1","y1","x2","y2","CELL","SHEET_COLUMNS","T","FRAME_COUNT","frames","_","id","width","step","overpass","overpassRampWest","overpassRampEast","overpassLedgeNorth","overpassLedgeSouth","parking","jumpBoost","jumpRamp","parkingLedge","canal","bridges","bridgeDeck","bridgeRampNorth","bridgeRampSouth","ford","fordBoost","buildings","grid","fill","rect","x0","y0","hline","vline","inRange","from","to","dashes","level0","b","bridge","level1","at","body","w","h","extra","fence","crate","barrel","spawn","angle","lamp","every","by","make","v","belt","downtown","dynamicBaseDefenses","createDefenseLine","startX","startY","count","isVertical","i","position","end","wallPos","garden","drawBorder","placeRocks","cx","cy","radius","density","oasisCx","oasisCy","oasisRadius","rampNorth","rampSouth","ledgeNorth","ledgeSouth","openNorth","openSouth","box","size","spawnOn","poolMini","originalMap","newMap","row","newRow1","newRow2","terrace","upper","rampLong","rampSide","rampSteep","rampStep","ledgeTerrace","ledgeUpper","building","slabRect","floorTile","wallTile","isOpen","edge","level2","terraces","maps","name","models","weapons","snapshot","gameConfig","sounds","sounds$1","seeThrough","parallax","volume","shadow","tilt","landing","animations","lighting","clientConfig","authSchema","executeBotCommand","ctx","team","initiateBotVote","gameId","userName","voteCategory","voteName","voteArgs","userList","u","result","botCommand","args","formatTime","ms","totalSeconds","minutes","seconds","nameCommand","newRoundCommand","timeLeftCommand","mapNameCommand","rankCommand","systemMessages","TanksBotManager","participants","coreAdapter","panel","stat","scripted","mapData","teamName","playableTeams","createdCount","targetTeam","a","participant","bot","counts","createModules","isNodeCore","wasmUrl","loadNodeCore","index","coreConfigJson","node","init"],"mappings":"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AG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,EAAG,CACd,EACM,CACE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,EACrE,EAAG,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"}
|