@vimp-games/tanks 0.22.8 → 0.22.9
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-BpIg8BDL.wasm → vimp_tanks_core_bg-C2KiYZdD.wasm} +0 -0
- package/dist/client-DhqcpbRq.js +210 -0
- package/dist/client-DhqcpbRq.js.map +1 -0
- package/dist/core-node/vimp_tanks_core_bg.wasm +0 -0
- package/dist/{host-DQx-nxzB.js → host-D-W8g3C1.js} +1 -1
- package/dist/{host-DQx-nxzB.js.map → host-D-W8g3C1.js.map} +1 -1
- 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 +2 -2
- package/dist/client-CUg2hnAe.js +0 -210
- package/dist/client-CUg2hnAe.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-CUg2hnAe.js","sources":["../node_modules/vimp-engine/src/config/opcodes.js","../core/pkg-web/vimp_tanks_core.js","../src/config/render.js","../src/client/levelZ.js","../src/client/camera.js","../src/client/parallax.js","../src/client/parts/map/tileGrid.js","../src/client/seeThrough.js","../src/client/parts/map/holeOverlay.js","../src/client/parts/bakeTileLayer.js","../src/client/parts/rampLanes.js","../src/client/parts/map/extrusion.js","../src/client/parts/map/layerAssets.js","../src/client/parts/map/layerSeeThrough.js","../src/client/lighting/lightMath.js","../src/client/animationClock.js","../src/client/parts/map/animatedTiles.js","../src/client/parts/map/decals.js","../src/client/neonFlicker.js","../src/client/bakers/blurMargin.js","../src/client/parts/map/neonCache.js","../src/client/parts/map/NeonSign.js","../src/client/parts/map/layerAnimations.js","../src/client/parts/map/MapLayer.js","../node_modules/vimp-engine/src/lib/math.js","../src/client/parts/effects/BaseEffect.js","../src/client/parts/effects/DebrisEffect.js","../src/client/snapshotFields.js","../src/client/parts/map/MapObject.js","../src/client/parts/Map.js","../src/client/levelColors.js","../src/client/parts/MapRadar.js","../src/client/parts/TankRadar.js","../src/client/tilt.js","../src/client/landing.js","../src/client/recoil.js","../src/client/blastJolt.js","../src/client/tank3d/model.js","../src/client/tank3d/transform.js","../src/client/tank3d/project.js","../src/client/tank3d/uv.js","../src/client/tank3d/shader.js","../src/client/tank3d/modelMesh.js","../src/client/tank3d/shadow.js","../src/client/tankLight.js","../src/client/parts/Tank.js","../src/client/parts/Bomb.js","../src/client/parts/effects/explosion/ExplosionEffect.js","../src/client/parts/ParticlePool.js","../src/client/parts/effects/explosion/SmokeEffect.js","../src/client/parts/effects/explosion/FunnelEffect.js","../src/client/parts/effects/explosion/ExplosionEffectController.js","../src/client/parts/Smoke.js","../src/client/parts/tracks/TrackMark.js","../src/client/parts/tracks/Tracks.js","../src/client/parts/Dust.js","../src/client/parts/effects/shot/TracerEffect.js","../src/client/parts/effects/shot/tracerPieces.js","../src/client/parts/effects/shot/ImpactEffect.js","../src/client/parts/effects/shot/MuzzleFlashEffect.js","../src/client/parts/effects/shot/ShotEffectController.js","../src/client/parts/index.js","../src/client/bakers/blurredCircleTexture.js","../src/client/bakers/funnelTexture.js","../src/client/bakers/tankTexture.js","../src/client/bakers/tankModelTexture.js","../src/client/bakers/tankRadarTexture.js","../src/client/bakers/bombTexture.js","../src/client/bakers/trackMarkTexture.js","../src/client/bakers/tankShadowTexture.js","../src/client/bakers/scorchTexture.js","../src/client/bakers/debrisTexture.js","../src/client/bakers/lightRadialTexture.js","../src/client/bakers/headlightConeTexture.js","../src/client/bakers/lampHeadTexture.js","../src/client/bakers/glintTexture.js","../src/client/bakers/lightShaftTexture.js","../src/client/bakers/index.js","../src/nodeCore.js","../src/client/levelView.js","../src/client/lighting/LevelLightMap.js","../src/client/lighting/createLighting.js","../src/client/shotEvents.js","../src/client/blastEvents.js","../src/client/index.js"],"sourcesContent":["// Разметка бинарного snapshot-протокола (порт SHOT_DATA).\n// Единый источник для хоста (упаковка) и клиента (распаковка) — обе стороны\n// живут в Rust-ядре: packages/engine/core/src/snapshot.rs (pack) и packages/engine/core/src/client/unpack.rs.\n\n// версия контракта движок ↔ игра-плагин (GameManifest, HostPlugin,\n// ClientPlugin, Wasm Host ABI — docs/{en,ru}/plugin-api.md).\n//\n// ЗАМОРОЖЕНО на 4. Больше не гейт совместимости и больше не бампается:\n// после этапов 1-4 плана plugin-forward-compat плагинная поверхность\n// append-only, и отвергать плагин за возраст стало не за что. Остаётся как\n// метка поколения контракта в манифестах и диагностике; удалять нельзя —\n// константу импортируют скрипты сборки всех уже существующих игр\n// (build-game-manifest.js) и правило контракта B2. Совместимость решается\n// переговорами о возможностях, см. lib/capabilities.js.\n//\n// История поколений (для чтения манифестов постарше):\n// v2: явная схема форм (roomForm/authSchema.params[].options — Form schema\n// в plugin-api.md), движок больше не выводит контролы из типа значения\n// v3: набор control сокращён до нативных элементов — 'select'|'text'|\n// 'checkbox'|'radio' (были ещё 'range'/'number'/'toggle'/'segmented')\n// v4: порт ACCOLADES_DATA (места участников в глобальном топе) и пятый\n// сервис пула зависимостей клиента (`accolades`) — snakes-v3 этап 4\nexport const ENGINE_API_VERSION = 4;\n\n// версия формата кадра: первый байт после порта;\n// увеличивать при любом изменении байтовой раскладки в ядре\n// v2: per-user player-блок (gameId, inputSeq, состояние своего танка) — Фаза 5b\n// v3: id автора в событиях оружия (tracers +shooterId, bombs +ownerId) — Фаза 5c\n// v4: опциональный хвост строки блока (schema.optionalFrom) — скорости\n// динамики карты (vx/vy/angvel) у движущихся тел, покоящиеся не дорожают\n// v5: угловая скорость танка (m1 +angvel) — предсказание доворота чужого\n// корпуса при контакте\nexport const SNAPSHOT_FORMAT_VERSION = 5;\n\n// Реестр ключей снапшота (SNAPSHOT_KEYS) — игровая схема: живёт в\n// gameConfig.snapshot игры (например src/config/snapshot.js в vimp-tanks),\n// движок передаёт её ядру (lib/coreConfig.js) и клиенту в CONFIG_DATA\n// (lib/buildClientConfig.js), не зная раскладки.\n\n// флаги hot-буфера рендер-тика клиентского ядра\n// (зеркало core/src/client/mod.rs в репозитории игры, например vimp-tanks)\nexport const HOT_FLAGS = {\n GAME: 1,\n CAMERA: 2,\n // «за группами есть хвостовые записи»: свой актор (render_overlay)\n // и/или тела, предсказанные игрой (render_rows)\n PREDICTED: 4,\n FRAMES: 8,\n};\n","/* @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 * Указатель на 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 * стрелка. Плоско: `[t0, t1, level, …]`, `t` — мировые единицы вдоль\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","// Настройки рендера 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 // копий, сколько занимает на экране, — так кирпич выходит одного размера\n // на любой стене. Аппаратный повтор для этого не годится (на батченом\n // меше координаты зажимаются), поэтому полоса конечна: у самой длинной\n // грани последняя копия тянется. Цена — текстура step × step·N на тайл\n faceTileRepeats: 12,\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// трассер выстрела 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 // собрана у головы, как у смазанной в движении точки\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 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 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 // свет верхнего уровня на рампах, ведущих к нему: фары танка на плите и\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\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 { parallax } from '../config/render.js';\n\n// Шаг zIndex между уровнями 2.5D-карты. Порядок отрисовки внутри уровня\n// задают базовые zIndex партов; шаг обязан быть больше любого базового,\n// иначе слой моста провалится под наземный навес (src/config/render.js).\nexport const LEVEL_Z_STRIDE = parallax.levelZStride;\n\nexport const levelZ = (base, level) => base + LEVEL_Z_STRIDE * (level || 0);\n\n// Уровень ОТРИСОВКИ тела 2.5D-карты: пока тело падает, хост держит `level`\n// тем уровнем, с которого оно сорвалось (`crate::level`, `map::step_body_level`),\n// и телом рисовались бы слой, тинт и прозрачность эстакады до самого\n// касания. По высоте тело переходит на нижний слой на середине падения.\n// Подъём правило не трогает: на рампе `level` и есть `round(z)`.\n//\n// Одно правило на все падающие тела — танк (`parts/Tank.js`) и ящик\n// (`parts/map/MapObject.js`): вторая копия разъехалась бы молча.\nexport const renderLevel = (level, z) => Math.min(level || 0, Math.round(z));\n","// Центр камеры в мировых координатах. Движкового API «дай камеру» нет:\n// камера — это трансформ сцены, им владеет движок\n// (CanvasManagerView.updateCoords: `stage.position = (w/2 - camX * scale,\n// h/2 - camY * scale)`, `stage.scale = scale`). Парт восстанавливает центр\n// обратной подстановкой — по своему родителю (он же сцена) и размеру полотна.\n//\n// null — пока парт не добавлен на сцену или рендерер недоступен: звать\n// раньше первого кадра нормально, а гадать за движок — нет.\n//\n// Уничтоженная сцена — тот же случай «камеры нет», и проверять её надо ДО\n// трансформа: `destroy()` в PixiJS обнуляет `position` и `scale`, то есть\n// чтение `parent.scale.x` там роняло бы кадр. Пережить уничтоженную сцену\n// сервис `levelView` может: он держит первую отданную ему сцену.\nexport function cameraCenter(parent, renderer) {\n const screen = renderer?.screen;\n\n if (!parent || parent.destroyed || !screen) {\n return null;\n }\n\n if (!parent.scale.x || !parent.scale.y) {\n return null;\n }\n\n return {\n x: (screen.width / 2 - parent.position.x) / parent.scale.x,\n y: (screen.height / 2 - parent.position.y) / parent.scale.y,\n scaleX: parent.scale.x,\n scaleY: parent.scale.y,\n };\n}\n","// Проекция 2.5D: объект на высоте h (в УРОВНЯХ) рисуется смещённым ОТ\n// центра камеры — чем выше и чем дальше от центра экрана, тем сильнее.\n// Ровно так читается высота в GTA 2. Формула одна на всех потребителей:\n//\n// точка: p' = p + (p - cam) * k\n// контейнер: тот же результат даёт масштаб (1 + k) вокруг центра камеры,\n// то есть p' = p * (1 + k) - cam * k\n//\n// k = высота в уровнях * shear (`src/config/render.js`, блок `parallax`).\n//\n// Разные знаки у разных потребителей — самая частая ошибка этой проекции:\n// смещается ОБЪЕКТ, а его тень остаётся в мировой точке (см.\n// Tank._updateShadow). Поэтому формулы больше нигде нет: её потребители —\n// слой уровня N и его объём (`Map`), клин рампы, корпус танка, следы\n// (`Tracks`) — зовут только эти две функции.\n\n// смещённая мировая точка. `camera` — центр камеры в мировых единицах\n// (src/client/camera.js); null (парт ещё не на сцене) и k === 0 дают\n// исходную точку\nexport function offsetPoint(x, y, camera, k) {\n if (!camera || !k) {\n return { x, y };\n }\n\n return {\n x: x + (x - camera.x) * k,\n y: y + (y - camera.y) * k,\n };\n}\n\n// тот же сдвиг трансформом контейнера: содержимое `target` авторится в\n// единицах, которые `baseScale` переводит в мировые (у слоя карты это\n// `data.scale`, у сущностей в мировых координатах — 1), а его родитель —\n// сцена. Даёт для каждой точки содержимого ровно `offsetPoint`, без\n// пересчёта единой вершины.\nexport function applyParallax(target, camera, k, baseScale = 1) {\n const scaleX = typeof baseScale === 'number' ? baseScale : baseScale.x;\n const scaleY = typeof baseScale === 'number' ? baseScale : baseScale.y;\n\n if (!camera || !k) {\n target.scale.set(scaleX, scaleY);\n target.position.set(0, 0);\n\n return;\n }\n\n target.scale.set(scaleX * (1 + k), scaleY * (1 + k));\n target.position.set(-camera.x * k, -camera.y * k);\n}\n","// Сетка тайлов слоя карты: базовый масштаб, два перевода мир → клетка и\n// выборка тайла. Общий модуль, потому что величины нужны и слою\n// (`MapLayer`), и телу карты (`MapObject`): копия разбора `data.scale` уже\n// жила в обоих.\n\n// Базовый масштаб карты числами. Контейнеру его каждый кадр пересчитывает\n// параллакс, поэтому в мировые единицы переводит именно БАЗОВЫЙ масштаб, а\n// не текущий. `data.scale` приходит и числом, и парой.\nexport function baseScale(scale) {\n const x = typeof scale === 'number' ? scale : scale.x;\n const y = typeof scale === 'number' ? scale : scale.y;\n\n return { x, y };\n}\n\n// Клетка, ВНУТРИ которой лежит мировая точка: тайл под игроком, под срезом\n// объёма. Полклетки влево — соседний тайл, поэтому пол, а не округление.\nexport function cellOfPoint(world, scale, step) {\n return Math.floor(world / scale / step);\n}\n\n// Клетка, на ГРАНИЦЕ которой стоит мировая координата: границы прогона\n// рампы приходят из ядра по кромкам тайлов, и `col0..col1` — полуинтервал\n// в клетках. Округление, а не пол: кромка `2 * step` — это клетка 2, а не\n// 1 с точностью до float.\nexport function cellOfEdge(world, scale, step) {\n return Math.round(world / scale / step);\n}\n\n// Грид слоя как значение: двумерная карта тайлов, базовый масштаб и шаг.\n// Им одним отвечают на вопрос «что нарисовано в этой мировой точке», и\n// поэтому он ходит параметром, а не читается из полей парта.\nexport function tileGrid(map, scale, step) {\n return { map, scale, step };\n}\n\n// Есть ли в мировой точке тайл из набора `tiles`: набор тайлов слоя\n// отвечает на вопрос «нарисован ли там объём», набор пола — «есть ли там\n// плита». Позиция приходит в мировых единицах, а грид слоя не\n// масштабирован, поэтому масштаб живёт ЗДЕСЬ.\nexport function tileAt(grid, worldX, worldY, tiles) {\n const col = cellOfPoint(worldX, grid.scale.x, grid.step);\n const row = cellOfPoint(worldY, grid.scale.y, grid.step);\n const tile = grid.map?.[row]?.[col];\n\n return tile !== undefined && tiles.has(tile);\n}\n\n// Закрывает ли набор клеток, нарисованный на высотах `ks`, нарисованную\n// точку `point` с запасом `margin` (мировые единицы). Срез на высоте k\n// рисует мировую точку w в `w + (w - cam) * k`, поэтому исходная точка\n// считается обратной формулой `w = (p + cam * k) / (1 + k)`. Проверяются\n// центр и при `margin > 0` ещё 4 точки по кругу: крыша начинает уступать\n// до того, как край корпуса въедет под неё. `test(worldX, worldY)` —\n// «есть ли в мировой точке клетка набора»\nfunction covers(test, point, camera, ks, margin) {\n if (!camera || !point) {\n return false;\n }\n\n const offsets = margin > 0\n ? [[0, 0], [margin, 0], [-margin, 0], [0, margin], [0, -margin]]\n : [[0, 0]];\n\n for (const k of ks) {\n const scale = 1 + k;\n\n for (const [dx, dy] of offsets) {\n if (\n test(\n (point.x + dx + camera.x * k) / scale,\n (point.y + dy + camera.y * k) / scale,\n )\n ) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n// вариант по гриду слоя и набору его тайлов (см. `tileAt`)\nexport function coversPoint(grid, tileSet, point, camera, ks, margin) {\n if (!grid?.map) {\n return false;\n }\n\n return covers(\n (x, y) => tileAt(grid, x, y, tileSet),\n point,\n camera,\n ks,\n margin,\n );\n}\n\n// вариант по набору клеток `Set('col,row')`: у сервиса освещения нет грида\n// слоя, только объединённые клетки масок\nexport function cellsCoverPoint(cellSet, step, scale, point, camera, ks, margin) {\n if (!cellSet || cellSet.size === 0) {\n return false;\n }\n\n return covers(\n (x, y) =>\n cellSet.has(\n `${cellOfPoint(x, scale.x, step)},${cellOfPoint(y, scale.y, step)}`,\n ),\n point,\n camera,\n ks,\n margin,\n );\n}\n","import {\n Filter,\n GlProgram,\n GpuProgram,\n UniformGroup,\n defaultFilterVert,\n} from 'pixi.js';\n\n// See-through уровней НАД игроком: единственная формула прозрачности на все\n// парты. Два режима (решение 8 мастер-плана, plan/multilevel-maps-2):\n//\n// 'hole' — радиальная дыра вокруг игрока: гаснет только то, что рядом,\n// остальная плита остаётся непрозрачной (GTA 2);\n// 'layer' — гаснет весь слой целиком (прежнее поведение, путь отхода).\n//\n// Точечные сущности (ящик, чужой танк, дым, бомба, эффект) считают свою alpha\n// через `seeThroughAlpha`, сплошная плита `Map` — фильтром `createHoleFilter`:\n// внутри одного слоя нужна не одна alpha, а поле по пикселям.\n\n// внутренний радиус дыры: до него прозрачность максимальная\nconst innerRadius = cfg => cfg.radius * (1 - cfg.softness);\n\n// сглаженная ступенька (та же кривая, что и в шейдере)\nconst smoothStep = (edge0, edge1, x) => {\n if (edge1 <= edge0) {\n return x < edge1 ? 0 : 1;\n }\n\n const t = Math.min(1, Math.max(0, (x - edge0) / (edge1 - edge0)));\n\n return t * t * (3 - 2 * t);\n};\n\n// alpha точечной сущности уровня `level` в мировой точке (x, y) при игроке\n// уровня `viewLevel` в точке (viewX, viewY)\nexport function seeThroughAlpha({\n viewLevel,\n viewX,\n viewY,\n level,\n x,\n y,\n cfg,\n}) {\n // всё, что не выше игрока, видно как есть: ниже — не мешает, вровень —\n // это и есть его собственный уровень\n if ((level || 0) <= (viewLevel || 0)) {\n return 1;\n }\n\n if (cfg.mode === 'layer') {\n return cfg.layerAlpha;\n }\n\n const dx = x - viewX;\n const dy = y - viewY;\n const distance = Math.sqrt(dx * dx + dy * dy);\n const t = smoothStep(innerRadius(cfg), cfg.radius, distance);\n\n return cfg.minAlpha + (1 - cfg.minAlpha) * t;\n}\n\n// tint сущности уровня `level`: уровни НИЖЕ игрока затемняются — это\n// единственный признак уровня, работающий на периферии экрана (задача 3)\nexport function seeThroughTint(viewLevel, level, cfg) {\n return (level || 0) < (viewLevel || 0) ? cfg.lowerTint : 0xffffff;\n}\n\n// GLSL ES 3.0: `uOutputFrame.xy + vTextureCoord * uInputSize.xy` — позиция\n// пикселя в кадре фильтра (та же арифметика, что в defaultFilter.vert,\n// только в обратную сторону), поэтому центр дыры приходит в тех же\n// экранных пикселях\n// precision highp float — обязательна: Pixi объявляет uInputSize/uOutputFrame\n// в defaultFilter.vert с точностью highp, а во фрагментном шейдере точность\n// float по умолчанию mediump, и программа не линкуется («Precisions of\n// uniform 'uInputSize' differ between VERTEX and FRAGMENT shaders»)\nconst fragment = `\nprecision highp float;\n\nin vec2 vTextureCoord;\n\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\n\nuniform vec2 uHoleCenter;\n// x - внешний радиус, y - внутренний, z - alpha в центре дыры\nuniform vec3 uHoleParams;\n\nvoid main(void)\n{\n vec4 color = texture(uTexture, vTextureCoord);\n vec2 pixel = uOutputFrame.xy + vTextureCoord * uInputSize.xy;\n float d = distance(pixel, uHoleCenter);\n float t = smoothstep(uHoleParams.y, uHoleParams.x, d);\n\n finalColor = color * mix(uHoleParams.z, 1.0, t);\n}\n`;\n\nconst wgsl = `\nstruct GlobalFilterUniforms {\n uInputSize:vec4<f32>,\n uInputPixel:vec4<f32>,\n uInputClamp:vec4<f32>,\n uOutputFrame:vec4<f32>,\n uGlobalFrame:vec4<f32>,\n uOutputTexture:vec4<f32>,\n};\n\nstruct HoleUniforms {\n uHoleCenter:vec2<f32>,\n uHoleParams:vec3<f32>,\n};\n\n@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d<f32>;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var<uniform> holeUniforms : HoleUniforms;\n\nstruct VSOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv : vec2<f32>\n};\n\nfn filterVertexPosition(aPosition:vec2<f32>) -> vec4<f32>\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y)\n - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2<f32> ) -> vec2<f32>\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\n@vertex\nfn mainVertex(@location(0) aPosition : vec2<f32>) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition)\n );\n}\n\n@fragment\nfn mainFragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {\n var color = textureSample(uTexture, uSampler, uv);\n var pixel = gfu.uOutputFrame.xy + uv * gfu.uInputSize.xy;\n var d = distance(pixel, holeUniforms.uHoleCenter);\n var t = smoothstep(holeUniforms.uHoleParams.y, holeUniforms.uHoleParams.x, d);\n\n return color * mix(holeUniforms.uHoleParams.z, 1.0, t);\n}\n`;\n\n// Фильтр «дыры» для сплошной плиты слоя. Обе ветки (WebGL и WebGPU) обязаны\n// быть, иначе на второй из них слой просто исчезнет без ошибки. Uniform'ы —\n// в пикселях кадра фильтра, их каждый кадр двигает Map._updateSeeThrough\nexport function createHoleFilter(cfg) {\n const holeUniforms = new UniformGroup({\n uHoleCenter: { value: new Float32Array([0, 0]), type: 'vec2<f32>' },\n uHoleParams: {\n value: new Float32Array([cfg.radius, innerRadius(cfg), cfg.minAlpha]),\n type: 'vec3<f32>',\n },\n });\n\n return new Filter({\n glProgram: GlProgram.from({\n vertex: defaultFilterVert,\n fragment,\n name: 'see-through-hole-filter',\n }),\n gpuProgram: GpuProgram.from({\n vertex: { source: wgsl, entryPoint: 'mainVertex' },\n fragment: { source: wgsl, entryPoint: 'mainFragment' },\n }),\n resources: { holeUniforms },\n });\n}\n\n// центр и размеры дыры в пикселях кадра фильтра. Радиус приходит в мировых\n// единицах, поэтому масштабируется трансформом сцены и разрешением рендера\nexport function setHoleUniforms(\n filter,\n { centerX, centerY, radius, softness, minAlpha },\n) {\n const uniforms = filter.resources.holeUniforms.uniforms;\n\n uniforms.uHoleCenter[0] = centerX;\n uniforms.uHoleCenter[1] = centerY;\n uniforms.uHoleParams[0] = radius;\n uniforms.uHoleParams[1] = radius * (1 - softness);\n uniforms.uHoleParams[2] = minAlpha;\n}\n","import { createHoleFilter, setHoleUniforms } from '../../seeThrough.js';\nimport { offsetPoint } from '../../parallax.js';\nimport { parallax as parallaxConfig } from '../../../config/render.js';\n\n// ниже этой силы дыра неотличима от её отсутствия: фильтр снимается совсем,\n// чтобы слой не платил за проход, которого не видно\nconst HOLE_EPSILON = 0.01;\n\n// «Дыра» вокруг игрока: состояние и его применение к одной цели. Слой и его\n// перекрыватель считают дыру ОДНИМ кодом, но своими экземплярами состояния —\n// у Pixi фильтр несёт свои uniform'ы, одним на две цели не обойтись.\nexport function createHole() {\n // `tick` — тик, на котором сила дыры уже двигалась: за один тик полотно\n // рисуется несколько раз, а сглаживание по времени обязано шагать один\n // раз (см. `layerSeeThrough.updateSeeThrough`)\n return { strength: 0, filter: null, attached: false, tick: null };\n}\n\n// сила дыры тянется к желаемой (0 — игрок не под слоем) с шагом тикера:\n// дыра открывается не рывком\nexport function advance(hole, wanted, rate) {\n hole.strength += ((wanted ? 1 : 0) - hole.strength) * rate;\n\n return hole.strength;\n}\n\n// внутри слоя нужна не одна alpha, а поле по пикселям, поэтому единственный\n// способ — фильтр. Центр приходит в пикселях кадра фильтра, то есть в\n// экранных: мировая точка умножается на трансформ сцены (камера — он и есть,\n// см. src/client/camera.js)\nexport function apply(target, hole, cfg, view, stage, camera) {\n if (hole.strength < HOLE_EPSILON || !stage) {\n if (hole.attached) {\n target.filters = [];\n hole.attached = false;\n }\n\n return;\n }\n\n if (!hole.filter) {\n hole.filter = createHoleFilter(cfg);\n }\n\n if (!hole.attached) {\n target.filters = [hole.filter];\n hole.attached = true;\n }\n\n // игрок под мостом нарисован не в своей мировой точке, а смещённым на\n // собственную высоту (Tank), — и слой над ним смещён тоже. Центр дыры\n // обязан ехать по той же проекции, иначе она уползает от танка тем\n // сильнее, чем дальше он от центра экрана\n const point = offsetPoint(\n view.x,\n view.y,\n camera,\n view.z * parallaxConfig.shear,\n );\n\n setHoleUniforms(hole.filter, {\n centerX: point.x * stage.scale.x + stage.position.x,\n centerY: point.y * stage.scale.y + stage.position.y,\n radius: cfg.radius * stage.scale.x,\n softness: cfg.softness,\n // дыра открывается не рывком: сила перехода живёт в минимальной alpha\n minAlpha: 1 + (cfg.minAlpha - 1) * hole.strength,\n });\n}\n\n// снять фильтр с цели и отдать программу шейдера: зовётся из destroy\nexport function dispose(hole, target) {\n if (!hole.filter) {\n hole.attached = false;\n\n return;\n }\n\n target.filters = [];\n hole.attached = false;\n hole.filter.destroy();\n hole.filter = null;\n}\n","import { Container, Rectangle, Sprite, Spritesheet } from 'pixi.js';\n\n// «Запекание» одного рендер-слоя карты в единственную текстуру: тайлы\n// раскладываются во временный контейнер и снимаются одним\n// `renderer.generateTexture`. Слоёв на карте единицы, а тайлов — тысячи:\n// без запекания каждый кадр стоил бы обхода всей сетки.\n//\n// Общий код `Map` (плоский слой) и `MapVolume` (его же экструзия): обе\n// стороны обязаны видеть ОДНУ И ТУ ЖЕ картинку, иначе объём разъедется\n// со своим основанием.\n//\n// Текстура принадлежит вызывающему: она сделана под конкретный экземпляр\n// карты, и освобождать её обязан он (`textureSource: true`).\n\n// Разбор тайл-листа карты в `Spritesheet`: кадр `frame<i>` на каждый\n// элемент `spriteSheetData.frames`. Общий код запекания и анимированных\n// частей слоя (`layerAnimations.js`): текстуры обеих сторон делят один\n// источник и потому один батч. Лист принадлежит вызывающему: он же зовёт\n// `spriteSheet.destroy()` (сам baseTexture — общий ассет игры)\nexport async function parseSpriteSheet(baseTexture, spriteSheetData) {\n const framesData = {};\n\n spriteSheetData.frames.forEach((frameDef, index) => {\n const [x, y, width, height] = frameDef;\n\n framesData[`frame${index}`] = {\n frame: { x, y, w: width, h: height },\n };\n });\n\n // полная структура JSON для спрайт-листа\n const sheetDataForPixi = {\n frames: framesData,\n meta: {\n scale: '1', // масштаб спрайтшита\n },\n };\n\n const spriteSheet = new Spritesheet(baseTexture, sheetDataForPixi);\n await spriteSheet.parse();\n\n return spriteSheet;\n}\n\n// `exclude` — тайлы, которые в запекание не попадают: анимированные тайлы\n// карты (`game.animatedTiles`) рисуются живыми спрайтами поверх\nexport async function bakeTileLayer({\n baseTexture,\n spriteSheetData,\n map,\n tiles,\n step,\n renderer,\n exclude = [],\n}) {\n const spriteSheet = await parseSpriteSheet(baseTexture, spriteSheetData);\n const excluded = new Set(exclude);\n const baked = tiles.filter(tile => !excluded.has(tile));\n\n const mapWidth = map[0].length * step;\n const mapHeight = map.length * step;\n\n // временный контейнер для размещения всех тайлов\n const tempContainer = new Container();\n\n for (let y = 0, lenY = map.length; y < lenY; y += 1) {\n for (let x = 0, lenX = map[y].length; x < lenX; x += 1) {\n const tileIndex = map[y][x];\n\n if (baked.includes(tileIndex)) {\n // предполагается, что spriteSheet имеет свойство textures,\n // где ключ соответствует названию тайла\n const textureName = `frame${tileIndex}`;\n const texture = spriteSheet.textures[textureName];\n\n if (texture) {\n const sprite = new Sprite(texture);\n\n sprite.x = x * step;\n sprite.y = y * step;\n\n tempContainer.addChild(sprite);\n } else {\n console.warn(\n `Texture not found. Tile: ${tileIndex}, sprite: ${textureName}`,\n );\n }\n }\n }\n }\n\n const bakedTexture = renderer.generateTexture({\n target: tempContainer,\n frame: new Rectangle(0, 0, mapWidth, mapHeight),\n });\n\n // очищаем временный контейнер и разбор тайл-листа: `Spritesheet` держит\n // по текстуре на кадр, а нужен он был только на время запекания. Сам\n // baseTexture — общий ассет игры и остаётся в кеше Assets\n tempContainer.destroy({ children: true });\n spriteSheet.destroy();\n\n return bakedTexture;\n}\n","// Полосы рамп для КАРТИНКИ клина: прогоны ядра, переведённые в клетки\n// грида слоя и склеенные поперёк.\n//\n// Сами прогоны здесь НЕ строятся. Их строит ядро (`MapLevels::build_runs`,\n// packages/engine/core/src/map.rs) — та же геометрия, по которой физика\n// ставит стражей прогона, — и отдаёт клиенту сервисом `rampRuns`\n// (src/client/index.js). Второй обход грида на JS жил здесь до этапа 3\n// плана review-multilevel-3 и расходился бы с ядром молча: горка, которую\n// видно и нельзя проехать (или наоборот).\n//\n// Остаётся одно намеренное расхождение с ядром — ШИРИНА. Полосы одной\n// широкой горки здесь склеиваются в одну. Ядру полосы нужны раздельными\n// (вердикт гейта судится на полосу, `core/src/level.rs`), а клину — нет:\n// между полосами нет ни стенки, ни ступеньки, и юбки на их границах\n// нарисовали бы перегородки, которых в физике не существует.\n\n// Прогоны ядра → полосы в КЛЕТКАХ: `col0..col1` и `row0..row1` —\n// полуинтервалы, `sign` — направление подъёма вдоль оси, `from`/`to` —\n// уровни подножия и вершины (у нисходящей рампы `from > to`).\n//\n// `toCell(world, axis)` переводит мировую координату в клетку грида по\n// своей оси (0 = x, 1 = y): масштаб карты живёт у парта, ядро считает\n// в мировых единицах.\nexport function buildRampLanes(runs, toCell) {\n const lanes = [];\n\n if (!Array.isArray(runs)) {\n return lanes;\n }\n\n for (const run of runs) {\n const alongAxis = run.axis === 0 ? 0 : 1;\n const crossAxis = alongAxis === 0 ? 1 : 0;\n const along0 = toCell(run.min, alongAxis);\n const along1 = toCell(run.max, alongAxis);\n const cross0 = toCell(run.crossMin, crossAxis);\n const cross1 = toCell(run.crossMax, crossAxis);\n // границы БОРТОВ вдоль оси приходят из ядра\n // (`map::ramp_rail_span`): юбка клина обязана рисовать борта ровно\n // там, где физика ставит стражей. `null` — бортов нет вовсе (прогон\n // длиной в одну клетку)\n const hasRails =\n typeof run.railMin === 'number' && typeof run.railMax === 'number';\n\n lanes.push({\n axis: run.axis,\n sign: run.sign,\n from: run.from,\n to: run.to,\n block: run.block,\n rail0: hasRails ? toCell(run.railMin, alongAxis) : null,\n rail1: hasRails ? toCell(run.railMax, alongAxis) : null,\n col0: alongAxis === 0 ? along0 : cross0,\n col1: alongAxis === 0 ? along1 : cross1,\n row0: alongAxis === 0 ? cross0 : along0,\n row1: alongAxis === 0 ? cross1 : along1,\n });\n }\n\n return mergeLanes(lanes);\n}\n\n// Склейка полос одной широкой горки: полосы с одним номером БЛОКА,\n// лежащие вплотную поперёк оси, становятся одной полосой во всю ширину\n// блока. Номер блока даёт ядро (`push_run`, E map.rs): он уже означает\n// «та же ось, знак, уровни и границы ВДОЛЬ оси», поэтому полосы разной\n// длины (ступенчатый край блока) несут разные номера и остаются разными\n// полосами — ровно тот же признак «одна горка», что у физики.\nfunction mergeLanes(lanes) {\n const groups = new Map();\n\n for (const lane of lanes) {\n const group = groups.get(lane.block);\n\n if (group) {\n group.push(lane);\n } else {\n groups.set(lane.block, [lane]);\n }\n }\n\n const merged = [];\n\n for (const group of groups.values()) {\n // поперечная координата: колонка при оси y, строка при оси x\n const cross0 = lane => (lane.axis === 1 ? lane.col0 : lane.row0);\n const cross1 = lane => (lane.axis === 1 ? lane.col1 : lane.row1);\n\n group.sort((a, b) => cross0(a) - cross0(b));\n\n let current = null;\n\n for (const lane of group) {\n if (current && cross0(lane) === cross1(current)) {\n if (current.axis === 1) {\n current.col1 = lane.col1;\n } else {\n current.row1 = lane.row1;\n }\n\n continue;\n }\n\n current = { ...lane };\n merged.push(current);\n }\n }\n\n return merged;\n}\n","import { Sprite, MeshSimple } from 'pixi.js';\n\n// Геометрия экструзии слоя: объём (`volume`) и клин рампы. Модуль чистый —\n// он ничего не знает ни о парте, ни о сцене, ни о конфиге рендера: всё, что\n// нужно (масштаб, сдвиг, тинт, число срезов), приходит параметрами, а\n// наружу уходят готовые срезы формата `{ target, k, base, heights,\n// occluder, walls }` — тот же, что держит слой в `_slices`.\n\n// Меш с повершинной высотой (клин рампы, грани объёма) ВСЕГДА рисуется\n// батчером. При `batchMode: 'auto'` PixiJS\n// отправляет меш длиннее 100 вершин (`Mesh.batched`) в обход батчера — в\n// общий на весь рендерер шейдер `GlMeshAdaptor`. Тот держит источник\n// текстуры в своей `BindGroup`, а уничтожение источника (смена карты\n// освобождает текстуру клина) обнуляет её НАВСЕГДА: `BindGroup` слушает\n// `change`, видит `destroyed` и делает `resources = null`. Следующий\n// небатченый меш падает в `BindGroup.setResource` на чтении `resources[0]`\n// — пустой экран через одну смену карты. Батченый меш общего шейдера не\n// касается, поэтому режим задаётся явно и не зависит от длины прогона.\nfunction batchedMesh(options) {\n const mesh = new MeshSimple(options);\n\n mesh.geometry.batchMode = 'batch';\n\n return mesh;\n}\n\n// Объём слоя: K копий ТОЙ ЖЕ запечённой картинки, каждая следующая\n// сдвинута от центра камеры сильнее предыдущей. Срезы уходят в\n// контейнер-перекрыватель (`occluder: true`), потому что им нужен zIndex\n// выше динамики своего уровня.\nexport function buildVolumeSlices({\n bakedTexture,\n level,\n volume,\n shear,\n count,\n sideTint,\n}) {\n const slices = [];\n\n for (let i = 1; i <= count; i += 1) {\n const sprite = new Sprite(bakedTexture);\n\n // нижние срезы — боковые грани блока, они темнее; верхний остаётся\n // самим слоем и рисуется последним, поверх остальных\n sprite.tint = i === count ? 0xffffff : sideTint;\n\n slices.push({\n target: sprite,\n k: (level + (volume * i) / count) * shear,\n occluder: true,\n });\n }\n\n return slices;\n}\n\n// Монолитный объём: боковые грани одним мешем вместо стопки срезов. Когда\n// сдвиг верха больше нескольких пикселей (край экрана, отдалённая камера),\n// между копиями слоя видны ступени — здание «из слоёв». Грань — квад\n// от кромки на высоте уровня слоя до той же кромки на высоте `level +\n// volume`: сдвиг вдоль неё растёт непрерывно, как у юбки рампы.\n//\n// Кромки строятся только там, где соседняя клетка не из набора тайлов\n// слоя: внутренние рёбра здания закрыты его же верхом. Грань — на КАЖДУЮ\n// открытую сторону клетки.\n//\n// Текстура грани — НЕ общая запечённая картинка слоя, а своя на тайл:\n// ПОЛОСА из `faceTileRepeats` копий его картинки по вертикали (печёт\n// `layerAssets.js`). Полоса, а не повтор координат: аппаратный повтор\n// (`addressMode: 'repeat'`) на батченом меше не действует — координаты\n// зажимаются, и грань размазывала крайний столбец тайла горизонтальными\n// полосами. По ширине грани тайл идёт ровно раз, по высоте — столько\n// копий полосы, сколько грань занимает на экране (`updateWallMesh`),\n// поэтому кирпич везде одного размера.\n//\n// Верх объёма (копия слоя на высоте `level + volume`) здесь не строится:\n// это спрайт запечённой текстуры, его кладёт сборка слоя (`layerAssets.js`)\n\n// сдвиг граней под верх: срезы сортируются по k, и грань обязана\n// нарисоваться раньше верха своего объёма\nconst WALL_EPSILON = 1e-6;\n\n// квадов на меш: батч PixiJS не любит огромные меши, поэтому длинная\n// береговая линия режется на несколько\nconst MAX_WALL_QUADS = 1000;\n\n// ровно 4 вершины на квад: две на верхней кромке, две на нижней\nconst QUAD_INDICES = [0, 1, 3, 0, 3, 2];\n\n// Открытые кромки объёма — по одной на СТОРОНУ КЛЕТКИ:\n// `{ col, row, nx, ny, x0, y0, x1, y1 }`, где (nx, ny) — внешняя нормаль\n// грани, отрезок (x0, y0)–(x1, y1) — само ребро на границах клеток, а\n// (col, row) — клетка, чьим тайлом грань и текстурируется.\n//\n// Соседние кромки НЕ сливаются в прогон: каждая грань тянет картинку\n// СВОЕЙ клетки (`buildVolumeWalls`), а тайлы лежат в общей запечённой\n// текстуре без повтора — склеенная грань растянула бы один тайл на всю\n// стену. Цена — квад на клетку кромки (у `downtown` это ~1700 квадов на\n// всю карту, доли миллисекунды на кадр)\nexport function wallEdges(map, tileSet) {\n const edges = [];\n const rows = map.length;\n const cols = map.reduce((max, row) => Math.max(max, row.length), 0);\n const has = (col, row) => {\n const tile = map[row]?.[col];\n\n return tile !== undefined && tileSet.has(tile);\n };\n\n for (let row = 0; row < rows; row += 1) {\n for (let col = 0; col < cols; col += 1) {\n if (!has(col, row)) {\n continue;\n }\n\n // северная и южная стороны клетки: ребро идёт по колонке\n for (const ny of [-1, 1]) {\n if (has(col, row + ny)) {\n continue;\n }\n\n const y = ny < 0 ? row : row + 1;\n\n edges.push({\n tile: map[row][col],\n col,\n row,\n nx: 0,\n ny,\n x0: col,\n y0: y,\n x1: col + 1,\n y1: y,\n });\n }\n\n // западная и восточная: ребро идёт по строке\n for (const nx of [-1, 1]) {\n if (has(col + nx, row)) {\n continue;\n }\n\n const x = nx < 0 ? col : col + 1;\n\n edges.push({\n tile: map[row][col],\n col,\n row,\n nx,\n ny: 0,\n x0: x,\n y0: row,\n x1: x,\n y1: row + 1,\n });\n }\n }\n }\n\n return edges;\n}\n\nexport function buildVolumeWalls({\n map,\n tiles,\n step,\n baseScale,\n level,\n volume,\n shear,\n sideTint,\n textures,\n tileRepeats = 1,\n}) {\n const edges = wallEdges(map, new Set(tiles));\n const k0 = level * shear;\n const k1 = (level + volume) * shear;\n // порядок отрисовки — по верху объёма: грань обязана лечь ПОД верх, даже\n // когда нахлёст кадра (`faceBleedPx`) поднимает её кромку выше него\n const kSort = k1 - WALL_EPSILON;\n // мировой размер клетки: в него укладывается ровно один повтор текстуры\n // грани и по ширине, и по высоте — кирпич выходит квадратным\n const cellWorld = step * baseScale.x;\n const slices = [];\n // грани группируются по тайлу: у каждого своя текстура с повтором\n const byTile = new Map();\n\n for (const edge of edges) {\n if (!byTile.has(edge.tile)) {\n byTile.set(edge.tile, []);\n }\n\n byTile.get(edge.tile).push(edge);\n }\n\n for (const [tile, tileEdges] of byTile) {\n const texture = textures?.get(tile);\n\n if (!texture) {\n console.warn(`buildVolumeWalls: no side texture for tile ${tile}`);\n continue;\n }\n\n for (let first = 0; first < tileEdges.length; first += MAX_WALL_QUADS) {\n const chunk = tileEdges.slice(first, first + MAX_WALL_QUADS);\n const quads = chunk.length;\n const base = new Float32Array(quads * 8);\n const uvs = new Float32Array(quads * 8);\n const indices = new Uint32Array(quads * 6);\n // нормаль и центр ребра на квад: по ним грань каждый кадр решает,\n // смотрит ли она на камеру (`orderWallMesh`)\n const normals = new Float32Array(quads * 2);\n const centers = new Float32Array(quads * 2);\n\n for (let q = 0; q < quads; q += 1) {\n const { nx, ny, x0, y0, x1, y1 } = chunk[q];\n const ax = x0 * step;\n const ay = y0 * step;\n const bx = x1 * step;\n const by = y1 * step;\n // по ширине грани тайл идёт ровно раз: клетка — это и есть его\n // ширина, поэтому координаты остаются в пределах картинки\n const ua = 0;\n const ub = 1;\n const v = q * 4;\n\n // вершины 0, 1 — верхняя кромка, 2, 3 — нижняя, в тех же точках\n for (let i = 0; i < 4; i += 1) {\n const atB = i % 2 === 1;\n const top = i < 2;\n const slot = (v + i) * 2;\n\n base[slot] = (atB ? bx : ax) * baseScale.x;\n base[slot + 1] = (atB ? by : ay) * baseScale.y;\n uvs[slot] = atB ? ub : ua;\n // высота грани на экране зависит от удаления клетки от центра\n // камеры, поэтому нижняя координата считается в кадре\n // (`updateWallMesh`); здесь — стартовое значение до первого кадра\n uvs[slot + 1] = top ? 0 : 1 / tileRepeats;\n }\n\n for (let i = 0; i < 6; i += 1) {\n indices[q * 6 + i] = v + QUAD_INDICES[i];\n }\n\n normals[q * 2] = nx;\n normals[q * 2 + 1] = ny;\n centers[q * 2] = ((ax + bx) / 2) * baseScale.x;\n centers[q * 2 + 1] = ((ay + by) / 2) * baseScale.y;\n }\n\n const mesh = batchedMesh({\n texture,\n vertices: base.slice(),\n uvs,\n indices,\n });\n\n mesh.tint = sideTint;\n\n slices.push({\n target: mesh,\n k: kSort,\n base,\n k0,\n k1,\n cellWorld,\n tileRepeats,\n occluder: true,\n walls: true,\n normals,\n centers,\n // сторона каждого квада на прошлом кадре: 2 — ещё не считалась\n facing: new Uint8Array(quads).fill(2),\n });\n }\n }\n\n return slices;\n}\n\n// Кадр грани объёма: вершины и UV по высоте.\n//\n// Грань нарисована от кромки клетки до неё же, поднятой на высоту объёма,\n// поэтому её длина на экране растёт с удалением от центра камеры.\n// Растянутая на разную длину картинка давала кирпич разного размера,\n// поэтому нижняя UV-координата считается каждый кадр.\n//\n// Считается она по ГЛУБИНЕ грани — расстоянию до центра камеры вдоль её\n// нормали, а не по радиусу `|p - cam|`. Вдоль прямой стены радиус меняется\n// (в середине он меньше, по краям больше), и ряды кирпича разъезжались\n// веером — стена читалась выпуклой. Глубина вдоль нормали вдоль такой\n// стены постоянна, поэтому ряды идут параллельно, а размер кирпича\n// остаётся тем же: и глубина, и экранная длина грани растут вместе.\n//\n// На одну копию тайла в полосе приходится `cellWorld` мировых единиц —\n// столько же, сколько по ширине грани. Полоса конечна, поэтому у самых\n// длинных граней координата упирается в её конец и последняя копия\n// тянется.\n//\n// `bleedPx` — нахлёст под верх объёма в ЭКРАННЫХ пикселях: щель между\n// мешем грани и спрайтом верха открывается на дробном масштабе сцены, а\n// движок плавно меняет зум от скорости, поэтому запас в мировых единицах\n// на отдалённой камере переставал её закрывать\nexport function updateWallMesh(slice, camera, bleedPx = 0) {\n const { target, base, k0, k1, cellWorld, tileRepeats, normals } = slice;\n const vertices = target.vertices;\n const uvs = target.geometry.uvs;\n const rise = k1 - k0;\n const bleedWorld = bleedPx && camera.scaleX ? bleedPx / camera.scaleX : 0;\n\n for (let i = 0, len = base.length / 2; i < len; i += 1) {\n const x = base[i * 2];\n const y = base[i * 2 + 1];\n const dx = x - camera.x;\n const dy = y - camera.y;\n // клетка ровно в центре камеры сдвига не получает: делить на ноль\n // нельзя, а нахлёст и высота там и так вырождены\n const dist = Math.sqrt(dx * dx + dy * dy) || 1e-6;\n const top = i % 4 < 2;\n // нахлёст под верх — вдоль луча от камеры: так он остаётся ровно\n // `bleedPx` экранными пикселями на любом удалении\n const k = top ? k1 + bleedWorld / dist : k0;\n\n vertices[i * 2] = x + dx * k;\n vertices[i * 2 + 1] = y + dy * k;\n\n if (!top) {\n const quad = i >> 2;\n const depth = normals[quad * 2 + 1] !== 0 ? Math.abs(dy) : Math.abs(dx);\n const span = (depth * rise) / cellWorld / tileRepeats;\n\n uvs[i * 2 + 1] = span > 1 ? 1 : span;\n }\n }\n\n // autoUpdate меша заливает буфер позиций сам; UV обновляются вручную —\n // сеттер буфера видит тот же массив и только отмечает его грязным\n target.vertices = vertices;\n target.geometry.uvs = uvs;\n}\n\n// Порядок граней объёма. Грань, отвёрнутая от камеры, обязана рисоваться\n// раньше видимой: иначе дальняя грань узкой стены ложится поверх ближней.\n// Индексы переписываются только при смене стороны хоть одного квада.\n// Возвращает true, если порядок поменялся\nexport function orderWallMesh(slice, camera) {\n const { target, normals, centers, facing } = slice;\n const quads = facing.length;\n let changed = false;\n\n for (let q = 0; q < quads; q += 1) {\n const dot =\n normals[q * 2] * (camera.x - centers[q * 2]) +\n normals[q * 2 + 1] * (camera.y - centers[q * 2 + 1]);\n const front = dot > 0 ? 1 : 0;\n\n if (facing[q] !== front) {\n facing[q] = front;\n changed = true;\n }\n }\n\n if (!changed) {\n return false;\n }\n\n const indices = target.geometry.indices;\n let slot = 0;\n\n // сначала отвёрнутые (0), затем смотрящие на камеру (1)\n for (let pass = 0; pass < 2; pass += 1) {\n for (let q = 0; q < quads; q += 1) {\n if (facing[q] !== pass) {\n continue;\n }\n\n for (let i = 0; i < 6; i += 1) {\n indices[slot] = q * 4 + QUAD_INDICES[i];\n slot += 1;\n }\n }\n }\n\n // тот же массив: сеттер буфера только отмечает обновление\n target.geometry.indices = indices;\n\n return true;\n}\n\n// Клин рампы: горка — наклонная ПЛОСКОСТЬ, а не лестница из срезов.\n// На прогон строится один меш-полоса вдоль оси прогона; у каждой её\n// вершины своя высота (`level + rise * progress`), поэтому сдвиг\n// параллакса растёт вдоль прогона непрерывно, а не ступенями.\n//\n// Вершины авторятся сразу в МИРОВЫХ единицах (пиксель грида × scale\n// слоя): контейнер статического слоя единичный, и меш в нём стоит без\n// собственного трансформа — двигаются только его вершины\n// (`updateHeightMesh`)\nexport function buildRampMeshes({\n runs,\n texture,\n step,\n shear,\n baseScale,\n segments: segmentsPerCell,\n sideTint,\n}) {\n const meshes = [];\n const bakedWidth = texture.width || 1;\n const bakedHeight = texture.height || 1;\n const perCell = Math.max(1, Math.round(segmentsPerCell) || 1);\n\n for (const run of runs) {\n const alongAxis = run.axis === 0;\n const cells = alongAxis ? run.col1 - run.col0 : run.row1 - run.row0;\n const segments = Math.max(1, cells * perCell);\n // борта юбки строятся ровно по границам стражей прогона: их отдаёт\n // ядро (`map::ramp_rail_span`), а не считает эта функция — вторая\n // копия отступа рисовала бы стену там, где физика пускает\n const along0Cell = alongAxis ? run.col0 : run.row0;\n const rails =\n typeof run.rail0 === 'number' && typeof run.rail1 === 'number'\n ? {\n first: (run.rail0 - along0Cell) * perCell,\n last: (run.rail1 - along0Cell) * perCell,\n }\n : null;\n const x0 = run.col0 * step;\n const x1 = run.col1 * step;\n const y0 = run.row0 * step;\n const y1 = run.row1 * step;\n const points = segments + 1;\n // базовая плоскость прогона — его НИЖНИЙ уровень: у нисходящей\n // рампы (`from > to`) тайл лежит в гриде ВЕРХНЕГО, и насыпь стоит на\n // нижнем, а не на уровне слоя\n const low = Math.min(run.from, run.to);\n const high = Math.max(run.from, run.to);\n const kBase = low * shear;\n const base = new Float32Array(points * 4);\n const uvs = new Float32Array(points * 4);\n const skirtUvs = new Float32Array(points * 4);\n // высота каждой вершины в долях сдвига: её и просит offsetPoint\n const heights = new Float32Array(points * 2);\n const indices = new Uint32Array(segments * 6);\n\n // UV юбки берутся не с самой кромки прогона: дальняя кромка\n // (`col1`/`row1`) — это уже СЛЕДУЮЩАЯ клетка, тайла рампы в ней нет,\n // и вертикальная грань, растянувшая её пиксели, оказывалась\n // прозрачной — насыпь читалась как пустая с одной стороны. Сдвиг\n // внутрь на пиксель грида ставит выборку внутрь крайнего тайла и на\n // картинке не виден\n const uvInset = 1;\n\n for (let i = 0; i < points; i += 1) {\n const t = i / segments;\n // «в горку» у прогонов разных направлений — разная сторона\n // прямоугольника: знак берётся из run.sign, как в ядре\n const progress = run.sign > 0 ? t : 1 - t;\n // высота вершины — ровно то же `lerp(from, to, progress)`, по\n // которому ядро ведёт z танка (`core/src/level.rs`): у нисходящей\n // рампы подъём идёт от `from` ВНИЗ\n const k = (run.from + (run.to - run.from) * progress) * shear;\n const along0 = alongAxis ? x0 : y0;\n const along1 = alongAxis ? x1 : y1;\n const along = along0 + (along1 - along0) * t;\n const ax = alongAxis ? along : x0;\n const ay = alongAxis ? y0 : along;\n const bx = alongAxis ? along : x1;\n const by = alongAxis ? y1 : along;\n const slot = i * 4;\n\n base[slot] = ax * baseScale.x;\n base[slot + 1] = ay * baseScale.y;\n base[slot + 2] = bx * baseScale.x;\n base[slot + 3] = by * baseScale.y;\n\n uvs[slot] = ax / bakedWidth;\n uvs[slot + 1] = ay / bakedHeight;\n uvs[slot + 2] = bx / bakedWidth;\n uvs[slot + 3] = by / bakedHeight;\n\n // те же точки, подтянутые внутрь прогона: ими текстурируется юбка\n const insetA = alongAxis ? ay + uvInset : ax + uvInset;\n const insetB = alongAxis ? by - uvInset : bx - uvInset;\n const alongInset = Math.min(\n Math.max(along, (alongAxis ? x0 : y0) + uvInset),\n (alongAxis ? x1 : y1) - uvInset,\n );\n\n skirtUvs[slot] = (alongAxis ? alongInset : insetA) / bakedWidth;\n skirtUvs[slot + 1] = (alongAxis ? insetA : alongInset) / bakedHeight;\n skirtUvs[slot + 2] = (alongAxis ? alongInset : insetB) / bakedWidth;\n skirtUvs[slot + 3] = (alongAxis ? insetB : alongInset) / bakedHeight;\n\n heights[i * 2] = k;\n heights[i * 2 + 1] = k;\n }\n\n for (let i = 0; i < segments; i += 1) {\n const v = i * 2;\n const slot = i * 6;\n\n indices[slot] = v;\n indices[slot + 1] = v + 1;\n indices[slot + 2] = v + 3;\n indices[slot + 3] = v;\n indices[slot + 4] = v + 3;\n indices[slot + 5] = v + 2;\n }\n\n const mesh = batchedMesh({\n texture,\n vertices: base.slice(),\n uvs,\n indices,\n });\n\n // юбка рисуется ДО поверхности (её k — базовая плоскость прогона),\n // поэтому кладётся первой: сортировка срезов по k это же и даёт\n meshes.push(\n buildRampSkirt({\n base,\n uvs: skirtUvs,\n heights,\n points,\n texture,\n kBase,\n sideTint,\n // «верх» прогона: там торец закрыт, у подножия он остаётся\n // открытым — это законный вход\n endIndex: run.sign > 0 ? points - 1 : 0,\n rails,\n }),\n );\n\n meshes.push({\n target: mesh,\n // порядок отрисовки — по вершине клина: выше всего он у своего\n // верхнего торца, там же он и обязан перекрывать соседей\n k: high * shear,\n base,\n heights,\n });\n }\n\n return meshes;\n}\n\n// Юбка клина: горка — насыпь, а не парящая плоскость. Под поверхностью\n// прогона видна была бы земля, и карта врала бы — «пусто, значит проеду»,\n// хотя борта прогона закрыты коллайдерами-стражами движка\n// (`packages/engine/core/src/map.rs`, RAMP_GUARD_GROUP).\n//\n// Строятся ровно те грани, что закрыты физически: два борта ВДОЛЬ оси\n// (по обеим поперечным границам прогона) и торец на ВЕРХНЕМ конце.\n// Нижний торец остаётся открытым — через него на горку и заезжают.\n// Нижняя кромка ложится на базовую плоскость прогона (`kBase`), поэтому у\n// прогона 1 → 2 под юбкой остаётся видимый просвет — и проезд там\n// действительно есть.\n//\n// Борта идут не во всю длину прогона, а ровно между `rails.first` и\n// `rails.last` — вершинами, отвечающими границам коллайдеров-стражей\n// (`map::ramp_rail_span` в ядре движка): клетка подножия открыта со всех\n// сторон, а у прогона длиной в одну клетку бортов нет вовсе (`rails`\n// равен null) — тогда от юбки остаётся один торец.\n//\n// Ближняя к камере грань видна, дальняя всегда накрыта поверхностью,\n// поэтому выбирать сторону в рантайме не нужно.\nexport function buildRampSkirt(surface) {\n const {\n base,\n uvs,\n heights,\n points,\n texture,\n kBase,\n endIndex,\n sideTint,\n rails,\n } = surface;\n // вершин в одном борту: у прогона без бортов их нет вовсе\n const railFirst = rails ? Math.max(0, Math.min(rails.first, points - 1)) : 0;\n const railLast = rails\n ? Math.max(railFirst, Math.min(rails.last, points - 1))\n : -1;\n const railPoints = rails ? railLast - railFirst + 1 : 0;\n // колонки юбки: борт по одной поперечной границе, борт по другой и\n // две колонки торца\n const columns = railPoints * 2 + 2;\n const skirtBase = new Float32Array(columns * 4);\n const skirtUvs = new Float32Array(columns * 4);\n const skirtHeights = new Float32Array(columns * 2);\n // квадов: по (railPoints - 1) на каждый борт плюс один торцевой\n const quads = Math.max(0, railPoints - 1) * 2 + 1;\n const indices = new Uint32Array(quads * 6);\n\n // источник вершины поверхности для колонки юбки: сперва борт A, затем\n // борт B, затем пара торца\n const sourceOf = column => {\n if (column < railPoints) {\n return (railFirst + column) * 2;\n }\n\n if (column < railPoints * 2) {\n return (railFirst + column - railPoints) * 2 + 1;\n }\n\n return endIndex * 2 + (column - railPoints * 2);\n };\n\n for (let column = 0; column < columns; column += 1) {\n const source = sourceOf(column);\n const slot = column * 4;\n const x = base[source * 2];\n const y = base[source * 2 + 1];\n\n // верхняя кромка идёт по поверхности, нижняя стоит в той же мировой\n // точке — разводит их только высота\n skirtBase[slot] = x;\n skirtBase[slot + 1] = y;\n skirtBase[slot + 2] = x;\n skirtBase[slot + 3] = y;\n\n // UV нижней кромки — те же, что у верхней: пиксели кромки тянутся\n // вниз по грани\n skirtUvs[slot] = uvs[source * 2];\n skirtUvs[slot + 1] = uvs[source * 2 + 1];\n skirtUvs[slot + 2] = uvs[source * 2];\n skirtUvs[slot + 3] = uvs[source * 2 + 1];\n\n skirtHeights[column * 2] = heights[source];\n skirtHeights[column * 2 + 1] = kBase;\n }\n\n let quad = 0;\n\n for (let strip = 0; strip < 3; strip += 1) {\n const first = strip === 2 ? railPoints * 2 : strip * railPoints;\n const last = strip === 2 ? railPoints * 2 + 1 : first + railPoints - 1;\n\n for (let column = first; column < last; column += 1) {\n const v = column * 2;\n const slot = quad * 6;\n\n indices[slot] = v;\n indices[slot + 1] = v + 1;\n indices[slot + 2] = v + 3;\n indices[slot + 3] = v;\n indices[slot + 4] = v + 3;\n indices[slot + 5] = v + 2;\n\n quad += 1;\n }\n }\n\n const mesh = batchedMesh({\n texture,\n vertices: skirtBase.slice(),\n uvs: skirtUvs,\n indices,\n });\n\n // боковая грань насыпи темнее её поверхности — ровно как у объёмов\n mesh.tint = sideTint;\n\n return {\n target: mesh,\n k: kBase,\n base: skirtBase,\n heights: skirtHeights,\n };\n}\n\n// Вершины меша с высотой (клин рампы, грани объёма): у каждой своя высота,\n// поэтому контейнерным трансформом (applyParallax) их не сдвинуть —\n// считаем поточечно той же формулой, что и offsetPoint\n// (src/client/parallax.js)\nexport function updateHeightMesh(slice, camera) {\n const { target, base, heights } = slice;\n const vertices = target.vertices;\n\n for (let i = 0, len = heights.length; i < len; i += 1) {\n const x = base[i * 2];\n const y = base[i * 2 + 1];\n const k = heights[i];\n\n vertices[i * 2] = x + (x - camera.x) * k;\n vertices[i * 2 + 1] = y + (y - camera.y) * k;\n }\n\n // autoUpdate меша заливает буфер позиций сам на ближайшем рендере\n target.vertices = vertices;\n}\n","import { Container, Sprite } from 'pixi.js';\nimport { levelZ } from '../../levelZ.js';\nimport { applyParallax } from '../../parallax.js';\nimport { bakeTileLayer } from '../bakeTileLayer.js';\nimport { buildRampLanes } from '../rampLanes.js';\nimport { cellOfEdge } from './tileGrid.js';\nimport {\n buildVolumeSlices,\n buildVolumeWalls,\n buildRampMeshes,\n} from './extrusion.js';\nimport {\n parallax as parallaxConfig,\n volume as volumeConfig,\n} from '../../../config/render.js';\n\n// Сборка статического слоя карты: запекание тайл-листа, объём, клин рампы\n// и полосы рамп в клетках грида. Отдельный модуль, потому что у слоя\n// (`MapLayer.js`) это отдельная зона ответственности со своими тремя\n// `await` — и после каждого парт может быть уже уничтожен.\n//\n// Модуль НИЧЕГО не знает о парте: на вход идёт описание слоя (`spec`), на\n// выход — готовые объекты, а раскладывает их по своим полям сам слой.\n// Полей парта здесь нет ни одного: иначе переименование приватного поля\n// молча ломало бы соседний файл.\n//\n// `spec` — это:\n// container контейнер парта: в него кладётся клин рампы\n// layerRoot корневой контейнер слоя (`MapLayer`): в него первым\n// ребёнком кладётся запечённый спрайт; параллакс слою\n// даёт сам `layerRoot`\n// baseTexture промис тайл-листа, `spriteSheetData` его разметка\n// map, tiles, step грид слоя, имена его тайлов и размер тайла\n// exclude тайлы вне запекания (анимированные, `game.animatedTiles`)\n// renderer рендерер полотна (нужен запеканию)\n// level, layer уровень слоя и его базовый zIndex\n// volume высота слоя в уровнях (0 — плоский)\n// ramps конфиги рамп ЭТОГО слоя, `rampRuns` сервис прогонов\n// baseScale базовый масштаб карты (`tileGrid`)\n// parallaxK сдвиг слоя по высоте его уровня\n// extruding нужна ли экструзия вообще\n// assetUrl только для сообщения об ошибке\n// isAborted() парт уже уничтожен — бросать работу\n\n// Базовый zIndex контейнера-перекрывателя: объём слоя рисуется НАД\n// динамикой СВОЕГО уровня, иначе танк «наезжает» на стену вместо того,\n// чтобы уйти за неё (экструзия идёт ОТ центра камеры, то есть накрывает\n// область за стеной — ровно там танк и стоит). 5 — выше танка (3), дыма\n// (4), бомб и эффектов (2) и следов (1), и заведомо меньше шага уровней\n// (`parallax.levelZStride`): слой уровня N + 1 по-прежнему выше всего,\n// что принадлежит уровню N\nconst OCCLUDER_BASE_Z = 5;\n\nconst NOTHING = {\n mapSprite: null,\n occluder: null,\n slices: [],\n rampTexture: null,\n wallTextures: [],\n};\n\n// Освобождение всего, что успела собрать сборка уничтоженному парту.\n// Владение то же, что и в `MapLayer.destroy`: запечённую текстуру слоя\n// делят спрайт и срезы объёма — отдаёт её спрайт; текстуру клина делят\n// его меши — она своя и отдаётся отдельно\nfunction disposeAssets({\n mapSprite,\n bakedTexture,\n occluder,\n slices,\n rampTexture,\n wallTextures,\n}) {\n for (const slice of slices) {\n slice.target.parent?.removeChild(slice.target);\n slice.target.destroy({ texture: false, textureSource: false });\n }\n\n occluder?.destroy({ children: true, texture: false, textureSource: false });\n\n if (rampTexture) {\n rampTexture.destroy(true);\n }\n\n for (const texture of wallTextures ?? []) {\n texture.destroy(true);\n }\n\n // запечённая текстура отдаётся ОТДЕЛЬНО от спрайта, а не через\n // `texture: true`: спрайт уже лежал в контейнере парта, и `super.destroy`\n // парта (`children: true`) успел уничтожить его сам, обнулив `_texture`\n // — второй destroy с `texture: true` упал бы на `null.destroy()`, а\n // источник так и остался бы жив\n if (mapSprite) {\n mapSprite.parent?.removeChild(mapSprite);\n mapSprite.destroy({ texture: false, textureSource: false });\n }\n\n bakedTexture?.destroy(true);\n}\n\nexport async function buildLayerAssets(spec) {\n try {\n const baseTexture = await spec.baseTexture;\n\n // парт мог быть уничтожен, пока грузился ассет: смена карты сносит\n // старые парты в том же тике, в котором создаёт новые\n if (spec.isAborted()) {\n return NOTHING;\n }\n\n const bakedTexture = await bakeTileLayer({\n baseTexture,\n spriteSheetData: spec.spriteSheetData,\n map: spec.map,\n tiles: spec.tiles,\n step: spec.step,\n renderer: spec.renderer,\n exclude: spec.exclude,\n });\n\n // повторно: запекание — второй await, и текстура уже создана, поэтому\n // уничтоженному парту её нужно не бросить, а освободить\n if (spec.isAborted()) {\n bakedTexture.destroy(true);\n\n return NOTHING;\n }\n\n // один большой спрайт из \"запеченной\" текстуры. Трансформ у него\n // единичный: масштаб и параллакс несёт `layerRoot`, и анимированные\n // спрайты слоя (контейнер `animated` над ним) едут вместе с картинкой\n const mapSprite = new Sprite(bakedTexture);\n\n spec.layerRoot.addChildAt(mapSprite, 0);\n\n if (!spec.extruding) {\n return { ...NOTHING, mapSprite };\n }\n\n const extrusion = await buildExtrusion(spec, baseTexture, bakedTexture);\n const assets = { mapSprite, ...extrusion };\n\n // экструзия — это ещё один await (запекание клина), и парт мог уйти в\n // нём. Освобождать собранное обязан ЭТОТ вызов: слой уже прошёл свой\n // `destroy()`, поля ему присвоятся мёртвому и текстуру клина не\n // отдаст никто\n if (spec.isAborted()) {\n disposeAssets({ ...assets, bakedTexture });\n\n return NOTHING;\n }\n\n return assets;\n } catch (error) {\n console.error(\n `Failed to create static map with asset ${spec.assetUrl}:`,\n error,\n );\n\n return NOTHING;\n }\n}\n\n// Экструзия слоя: объём (`volume`) — боковые грани мешем и одна копия ТОЙ\n// ЖЕ запечённой картинки на высоте верха (`volume.faces`), либо, путём\n// отхода, K копий со ступенчатым сдвигом; клин\n// рампы — по мешу на прогон, наклонная плоскость с непрерывно растущей\n// высотой (`buildRampMeshes`, src/client/parts/map/extrusion.js).\n//\n// Срезы ОБЪЁМА уходят в контейнер-перекрыватель (сиблинг парта на сцене):\n// им нужен zIndex выше динамики своего уровня, иначе танк рисуется поверх\n// стены, за которой стоит. Прозрачность и «дыра» у перекрывателя свои —\n// считаются той же формулой, что у слоя (`layerSeeThrough.js`).\n//\n// Клин рампы, наоборот, остаётся ребёнком парта: танк, поднимающийся по\n// горке, обязан рисоваться ПОВЕРХ её поверхности\nasync function buildExtrusion(spec, baseTexture, bakedTexture) {\n const count = volumeConfig.slices;\n const shear = parallaxConfig.shear;\n const built = [];\n const wallTextures = [];\n let occluder = null;\n let rampTexture = null;\n\n if (spec.volume > 0) {\n occluder = new Container();\n occluder.zIndex = levelZ(\n Math.max(spec.layer, OCCLUDER_BASE_Z),\n spec.level,\n );\n\n if (volumeConfig.faces) {\n const textures = await bakeWallTextures(spec, baseTexture, wallTextures);\n\n // выпечка боковых текстур — это ещё await на тайл: парт мог уйти\n if (spec.isAborted()) {\n return { occluder, slices: built, rampTexture, wallTextures };\n }\n\n built.push(\n ...buildVolumeWalls({\n map: spec.map,\n tiles: spec.tiles,\n step: spec.step,\n baseScale: spec.baseScale,\n level: spec.level,\n volume: spec.volume,\n shear,\n sideTint: volumeConfig.sideTint,\n textures,\n tileRepeats: volumeConfig.faceTileRepeats,\n }),\n );\n\n // верх объёма — сам слой на полной высоте; грани лягут под него\n // сортировкой по k ниже\n const top = new Sprite(bakedTexture);\n\n top.tint = 0xffffff;\n built.push({\n target: top,\n k: (spec.level + spec.volume) * shear,\n occluder: true,\n });\n } else {\n built.push(\n ...buildVolumeSlices({\n bakedTexture,\n level: spec.level,\n volume: spec.volume,\n shear,\n count,\n sideTint: volumeConfig.sideTint,\n }),\n );\n }\n }\n\n const runs = rampLanes(spec);\n\n if (runs.length) {\n rampTexture = await bakeTileLayer({\n baseTexture,\n spriteSheetData: spec.spriteSheetData,\n map: spec.map,\n tiles: spec.ramps.map(ramp => ramp.tile),\n step: spec.step,\n renderer: spec.renderer,\n });\n\n // третий await: парт мог уйти, пока пеклась текстура клина. Работа\n // бросается, а освобождение уходит одним местом наверх\n // (`disposeAssets` в `buildLayerAssets`)\n if (spec.isAborted()) {\n return { occluder, slices: built, rampTexture, wallTextures };\n }\n\n built.push(\n ...buildRampMeshes({\n runs,\n texture: rampTexture,\n step: spec.step,\n shear,\n baseScale: spec.baseScale,\n segments: volumeConfig.rampSegments,\n sideTint: volumeConfig.sideTint,\n }),\n );\n }\n\n if (spec.isAborted()) {\n return { occluder, slices: built, rampTexture, wallTextures };\n }\n\n // порядок отрисовки — по высоте: выше срез, позже он нарисован. Плоский\n // слой остаётся основанием и уже лежит первым\n built.sort((a, b) => a.k - b.k);\n\n for (const slice of built) {\n // меш клина уже стоит в мировых вершинах: до первого кадра он лежит\n // без сдвига, как и слой без камеры\n if (!slice.base) {\n applyParallax(slice.target, null, slice.k, spec.baseScale);\n }\n\n (slice.occluder ? occluder : spec.container).addChild(slice.target);\n }\n\n return { occluder, slices: built, rampTexture, wallTextures };\n}\n\n// Боковая текстура на КАЖДЫЙ тайл слоя: ПОЛОСА из `faceTileRepeats` копий\n// его картинки по вертикали. Общая запечённая картинка слоя для граней не\n// годится — по ней тайл не повторяется, и кирпич растягивался бы тем\n// сильнее, чем длиннее и выше грань (`extrusion.js`, `updateWallMesh`).\n// Аппаратный повтор координат тоже не годится: на батченом меше он не\n// действует, координаты зажимаются, и грань размазывает крайний столбец\n// тайла.\n//\n// Собранные текстуры складываются в `sink`: владеет ими слой и отдаёт их\n// в своём `destroy` (как текстуру клина рампы)\nasync function bakeWallTextures(spec, baseTexture, sink) {\n const textures = new Map();\n\n const repeats = Math.max(1, Math.round(volumeConfig.faceTileRepeats) || 1);\n\n for (const tile of spec.tiles) {\n const texture = await bakeTileLayer({\n baseTexture,\n spriteSheetData: spec.spriteSheetData,\n map: Array.from({ length: repeats }, () => [tile]),\n tiles: [tile],\n step: spec.step,\n renderer: spec.renderer,\n });\n\n sink.push(texture);\n textures.set(tile, texture);\n }\n\n return textures;\n}\n\n// Полосы рамп ЭТОГО слоя в клетках его грида. Прогоны приходят из ядра\n// в МИРОВЫХ единицах (`tile_size == step * scale`), а грид слоя не\n// масштабирован. Перевод здесь по ГРАНИЦЕ клетки (`cellOfEdge`), а не по\n// точке внутри неё (`cellOfPoint` у `tileAt`): границы прогона идут по\n// кромкам тайлов, и пол вместо округления давал бы клетку левее.\n//\n// Уровень задаёт сервис (`forLevel`), а слой среди них берёт только свои\n// горки: у карты слой земли и слой стен делят один грид, и клин рисует\n// тот из них, чьи тайлы рампы он и рисует. Тайла в прогоне нет — рампы\n// сличаются по паре уровней «подножие/вершина»\nexport function rampLanes(spec) {\n if (!spec.ramps?.length || !spec.rampRuns) {\n return [];\n }\n\n const owned = new Set(spec.ramps.map(ramp => `${ramp.from}:${ramp.to}`));\n const runs = spec.rampRuns\n .forLevel(spec.level)\n .filter(run => owned.has(`${run.from}:${run.to}`));\n const toCell = (world, axis) =>\n cellOfEdge(\n world,\n axis === 0 ? spec.baseScale.x : spec.baseScale.y,\n spec.step,\n );\n\n return buildRampLanes(runs, toCell);\n}\n","import { Ticker } from 'pixi.js';\nimport { offsetPoint } from '../../parallax.js';\nimport { advance as advanceHole, apply as applyHole } from './holeOverlay.js';\nimport { parallax as parallaxConfig } from '../../../config/render.js';\nimport { coversPoint, tileAt } from './tileGrid.js';\n\n// See-through статического слоя: плита моста и её перекрыватель уступают\n// видимость локальному игроку — в двух режимах (`layer` и `hole`) и для\n// двух целей (сам слой и контейнер-перекрыватель). Отдельный модуль:\n// у слоя это своя зона ответственности, и в `MapLayer.js` она занимала\n// треть файла.\n//\n// Модуль НИЧЕГО не знает о парте: всё, что ему нужно, приходит одним\n// значением `view` (см. `MapLayer._seeThroughView`), а меняет он только\n// то, что в этом значении и передано, — alpha и фильтры двух контейнеров\n// плюс состояние двух «дыр». Полей парта здесь нет ни одного: иначе\n// переименование приватного поля молча ломало бы соседний файл.\n//\n// `view` — это:\n// levelView сервис «где локальный игрок»\n// container сам слой, `occluder` контейнер-перекрыватель (или null)\n// stage сцена: в её координатах фильтр считает центр дыры\n// hasSprite запечён ли уже слой (ассеты грузятся асинхронно)\n// hole, occluderHole состояния «дыры» (`holeOverlay`)\n// level уровень слоя\n// grid грид тайлов (`tileGrid`)\n// tileSet, floorSet наборы тайлов слоя и тайлов пола\n// roof слой — крыша (`game.roofs`)\n\n// прозрачность плиты моста над локальным игроком: в GTA 2 игрок под\n// эстакадой продолжает видеть свою машину. Считается по НАШЕМУ гриду\n// уровня: у слоя есть и карта, и список тайлов пола\nexport function updateSeeThrough(view, camera) {\n if (!view.levelView) {\n return;\n }\n\n const cfg = view.levelView.cfg;\n // Сглаживание по времени тикера общего приложения — РОВНО раз на тик.\n // Полотно может рисоваться несколько раз за тик (`vimp-engine` до 0.34\n // зовёт `app.render()` из `updateCoords` на каждый кадр камеры), а\n // `deltaMS` у всех этих отрисовок один и тот же: без этой отсечки плита\n // гасла бы тем быстрее, чем больше кадров пришло в тик, то есть\n // `fadeRate` означал бы разное на разном пинге. Нулевой шаг оставляет\n // силу дыры как есть, а её ПРИМЕНЕНИЕ идёт каждую отрисовку: центр дыры\n // едет за камерой\n const tick = Ticker.shared.lastTime;\n const stepped = view.hole.tick === tick;\n\n view.hole.tick = tick;\n\n const dt = Ticker.shared.deltaMS / 1000;\n const rate = stepped ? 0 : Math.min(1, cfg.fadeRate * dt);\n\n if (view.hasSprite) {\n updateLayerSeeThrough(view, cfg, rate, camera);\n }\n\n if (view.occluder) {\n updateOccluderSeeThrough(view, cfg, rate, camera);\n }\n}\n\n// сам слой: гаснет только то, что НАД игроком\nfunction updateLayerSeeThrough(view, cfg, rate, camera) {\n const { levelView, container } = view;\n\n // Крыша уступает видимость, только когда закрывает сам танк: иначе\n // дыра открывала её всякий раз, когда игрок ниже, и сквозь неё\n // проступала верхушка стены без неона своего уровня\n const roofHides = view.roof\n ? levelView.level < view.level && roofHidesPlayer(view, cfg, camera)\n : null;\n\n // путь отхода: гаснет весь слой целиком (прежнее поведение)\n if (levelView.mode === 'layer') {\n const under = view.roof\n ? roofHides\n : levelView.level < view.level &&\n tileAt(view.grid, levelView.x, levelView.y, view.floorSet);\n const target = under ? cfg.layerAlpha : 1;\n\n container.alpha += (target - container.alpha) * rate;\n\n return;\n }\n\n // режим 'hole': проверки пола нет — дыра ездит за игроком, и её край\n // сам показывает, где кончается плита\n const above = view.roof ? roofHides : levelView.level < view.level;\n\n advanceHole(view.hole, above, rate);\n applyHole(container, view.hole, cfg, levelView, view.stage, camera);\n}\n\n// Перекрыватель гаснет, только когда его уровень НАД игроком — вместе со\n// своим слоем (перила моста обязаны исчезать вместе с плитой). Объём\n// СВОЕГО уровня не гаснет никогда: на скорости упреждение камеры уводит\n// её за грань стены, верх стены нависает над танком, и дыра гасила объём —\n// стена становилась плоской. Танк у стены на секунду-другую может быть\n// частично закрыт её верхом, пока камера его не догонит.\nfunction updateOccluderSeeThrough(view, cfg, rate, camera) {\n const { levelView, occluder } = view;\n const above = view.level > levelView.level;\n\n if (levelView.mode === 'layer') {\n // путь отхода: чужой уровень гаснет целиком, свой не гаснет вовсе\n const alpha = above ? cfg.layerAlpha : 1;\n\n occluder.alpha += (alpha - occluder.alpha) * rate;\n\n return;\n }\n\n advanceHole(view.occluderHole, above, rate);\n applyHole(occluder, view.occluderHole, cfg, levelView, view.stage, camera);\n}\n\n// нарисованная точка локального игрока: он смещён проекцией на свою высоту\nfunction playerPoint(view, camera) {\n const player = view.levelView;\n\n return offsetPoint(player.x, player.y, camera, player.z * parallaxConfig.shear);\n}\n\n// Накрывает ли крыша нарисованную точку игрока (с запасом `roofMargin`):\n// крыша плоская и рисуется на высоте своего уровня\nfunction roofHidesPlayer(view, cfg, camera) {\n if (!camera) {\n return false;\n }\n\n return coversPoint(\n view.grid,\n view.tileSet,\n playerPoint(view, camera),\n camera,\n [view.level * parallaxConfig.shear],\n cfg.roofMargin ?? 0,\n );\n}\n","import { offsetPoint } from '../parallax.js';\nimport { renderLevel } from '../levelZ.js';\nimport { baseScale } from '../parts/map/tileGrid.js';\n\n// Чистые функции освещения: ключ карты, клетки → мир, проекция источника,\n// отсечение по экрану, мерцание и затухание вспышки. Здесь нет ни одного\n// объекта PixiJS — всё проверяется без рендерера.\n\n// базовые zIndex внутри уровня (`levelZ`): карта освещённости выше всей\n// динамики своего уровня (`Tank` 3, дым 4, окклюдер объёмов 5), эмиссив —\n// над ней. Слои карты с базой >= LIGHT_OVERLAY_BASE_Z на ночной карте\n// запрещены: их бы затемнило не по уровню\nexport const LIGHT_OVERLAY_BASE_Z = 40;\nexport const EMISSIVE_BASE_Z = 45;\n// головы фонарей — светильники в асфальте: над дорогой и следами (1), под\n// эффектами (2) и танком (3), поэтому наехавший танк их закрывает. Они под\n// картой освещённости и светятся за счёт пятна своего же фонаря\nexport const LAMP_HEAD_BASE_Z = 1.5;\n\n// FNV-1a (32 бита) по строке: короткий стабильный отпечаток конфига\nexport function fnv1a(text) {\n let hash = 0x811c9dc5;\n\n for (let i = 0; i < text.length; i += 1) {\n hash ^= text.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n\n return hash >>> 0;\n}\n\n// Ключ карты для сервиса освещения. Собирается ТОЛЬКО из того, что есть у\n// каждой статической части: размеры сетки (у всех уровней одинаковы —\n// движок отклоняет несовпадение) и `game.lighting`. Номера уровня и `setId`\n// в ключе нет — у частей разных уровней одной карты ключ обязан совпасть\nexport function mapKeyOf(data) {\n const rows = data?.map?.length || 0;\n const cols = data?.map?.[0]?.length || 0;\n const hash = fnv1a(JSON.stringify(data?.game?.lighting ?? null));\n\n return `${cols}x${rows}:${hash.toString(16)}`;\n}\n\n// центр клетки `[col, row]` в мировых единицах: обратная к `cellOfPoint`\n// (`tileGrid.js`) — шаг сетки не масштабирован, масштаб карты отдельно\nexport function cellCenter(col, row, step, scale) {\n const { x, y } = baseScale(scale);\n\n return { x: (col + 0.5) * step * x, y: (row + 0.5) * step * y };\n}\n\n// клетки грида, чей тайл входит в набор `tiles`: `[[col, row], ...]`\nexport function cellsOfTiles(map, tiles) {\n const set = new Set(tiles || []);\n const cells = [];\n\n if (!map || set.size === 0) {\n return cells;\n }\n\n for (let row = 0; row < map.length; row += 1) {\n const line = map[row] || [];\n\n for (let col = 0; col < line.length; col += 1) {\n if (set.has(line[col])) {\n cells.push([col, row]);\n }\n }\n }\n\n return cells;\n}\n\n// клетки маски → горизонтальные прогоны `{ col, row, length }`: маска\n// этажа рисуется прямоугольниками прогонов, а не клеток\nexport function cellRuns(cells) {\n const rows = new Map();\n\n for (const [col, row] of cells) {\n if (!rows.has(row)) {\n rows.set(row, new Set());\n }\n\n rows.get(row).add(col);\n }\n\n const runs = [];\n\n for (const row of [...rows.keys()].sort((a, b) => a - b)) {\n const cols = [...rows.get(row)].sort((a, b) => a - b);\n let start = cols[0];\n let prev = cols[0];\n\n for (let i = 1; i <= cols.length; i += 1) {\n const col = cols[i];\n\n if (col === prev + 1) {\n prev = col;\n } else {\n runs.push({ col: start, row, length: prev - start + 1 });\n start = col;\n prev = col;\n }\n }\n }\n\n return runs;\n}\n\n// Нарисованная точка источника на высоте `z` (уровни) и её экранная\n// позиция: проекция 2.5D (`offsetPoint`), затем трансформ сцены — как у\n// центра дыры в `holeOverlay.js`. `scale` — масштаб проекции `1 + k`\nexport function projectLight(x, y, z, camera, stage, shear) {\n const k = z * shear;\n const point = offsetPoint(x, y, camera, k);\n\n return {\n x: point.x,\n y: point.y,\n scale: 1 + k,\n screenX: point.x * stage.scale.x + stage.position.x,\n screenY: point.y * stage.scale.y + stage.position.y,\n };\n}\n\n// уровни, в карты освещённости которых светит источник на высоте z:\n// на рампе — оба соседних, иначе — один уровень отрисовки. Клин рампы\n// нарисован в уровне `from` и затемняется его оверлеем, а плита, на которую\n// выезжает луч, — оверлеем верхнего уровня: свет нужен обоим\nexport function lightLevels(level, z, airborne) {\n if (!airborne && Math.abs(z - Math.round(z)) > 1e-3) {\n return [Math.floor(z), Math.ceil(z)];\n }\n\n return [renderLevel(level, z)];\n}\n\n// попадает ли круг охвата `reach` (экранные пиксели) в прямоугольник экрана\nexport function isOnScreen(screenX, screenY, reach, width, height) {\n return (\n screenX + reach >= 0 &&\n screenX - reach <= width &&\n screenY + reach >= 0 &&\n screenY - reach <= height\n );\n}\n\n// Детерминированное мерцание: множитель яркости в `[1 - strength, 1]`.\n// Сумма синусов с разными частотами и фазой от `seed` — у соседних фонарей\n// мерцание не синхронно, а у одного фонаря одинаково на всех клиентах\nexport function flicker(seed, timeMs, strength) {\n if (!strength) {\n return 1;\n }\n\n const t = timeMs / 1000;\n const noise =\n Math.sin(t * 7.3 + seed * 12.9898) * 0.5 +\n Math.sin(t * 13.1 + seed * 78.233) * 0.3 +\n Math.sin(t * 23.7 + seed * 3.7) * 0.2;\n\n return 1 - strength * ((noise + 1) / 2);\n}\n\n// затухание вспышки: 1 в момент старта, 0 к концу, быстрый спад сначала\nexport function flashFactor(elapsedMs, durationMs) {\n if (!(durationMs > 0) || elapsedMs >= durationMs) {\n return 0;\n }\n\n const t = 1 - Math.max(0, elapsedMs) / durationMs;\n\n return t * t;\n}\n\n// --- засветы и лучи (этап 10) ---\n\n// спад пятна фонаря по расстоянию: тот же `(1 − t)²`, что у текстуры\n// `lightRadialTexture`, — засвет гаснет там же, где пятно\nexport function radialFalloff(distance, radius) {\n if (!(radius > 0) || distance >= radius) {\n return 0;\n }\n\n const t = 1 - Math.max(0, distance) / radius;\n\n return t * t;\n}\n\n// Сила источника в мировой точке `(x, y)` без мерцания. Конус — как его\n// текстура (`headlightConeTexture`): клин от вершины по `rotation`,\n// полуширина `along · spread`, яркость спадает по длине и к краям клина\nexport function lightStrength(light, x, y) {\n const dx = x - light.x;\n const dy = y - light.y;\n const intensity = light.intensity ?? 1;\n\n if (light.kind !== 'cone') {\n return intensity * radialFalloff(Math.hypot(dx, dy), light.radius);\n }\n\n const cos = Math.cos(light.rotation || 0);\n const sin = Math.sin(light.rotation || 0);\n const along = dx * cos + dy * sin;\n const across = Math.abs(dy * cos - dx * sin);\n\n if (!(light.radius > 0) || along <= 0 || along >= light.radius) {\n return 0;\n }\n\n const halfWidth = along * (light.spread ?? 0.5);\n\n if (across >= halfWidth) {\n return 0;\n }\n\n const t = along / light.radius;\n\n return intensity * (1 - t) * (1 - t * 0.35) * (1 - across / halfWidth);\n}\n\n// Отбор источников для точки: `candidates` — `[{ light, factor }]`, где\n// `factor` — мерцание или затухание вспышки. Возвращает до `limit`\n// сильнейших `{ light, strength, angle, color }`; `angle` — направление ОТ\n// точки НА источник (рад)\nexport function selectLights(candidates, x, y, limit) {\n const hits = [];\n\n for (const { light, factor } of candidates) {\n const strength = lightStrength(light, x, y) * factor;\n\n if (strength > 0) {\n hits.push({\n light,\n strength,\n angle: Math.atan2(light.y - y, light.x - x),\n color: light.color ?? 0xffffff,\n });\n }\n }\n\n hits.sort((a, b) => b.strength - a.strength);\n\n return hits.slice(0, Math.max(0, limit));\n}\n\nconst gridKey = (col, row) => `${col},${row}`;\n\n// Сетка источников для запроса по точке: каждый источник лежит во всех\n// клетках, которые задевает квадрат его радиуса. Запрос читает одну клетку\n// и не обходит все фонари карты\nexport function buildLightGrid(lights, cellSize) {\n const size = cellSize > 0 ? cellSize : 1;\n const cells = new Map();\n\n for (const light of lights) {\n const radius = light.radius || 0;\n const col0 = Math.floor((light.x - radius) / size);\n const col1 = Math.floor((light.x + radius) / size);\n const row0 = Math.floor((light.y - radius) / size);\n const row1 = Math.floor((light.y + radius) / size);\n\n for (let row = row0; row <= row1; row += 1) {\n for (let col = col0; col <= col1; col += 1) {\n const key = gridKey(col, row);\n\n if (!cells.has(key)) {\n cells.set(key, []);\n }\n\n cells.get(key).push(light);\n }\n }\n }\n\n return { cellSize: size, cells };\n}\n\n// источники клетки сетки, в которую попала точка\nexport function queryLightGrid(grid, x, y) {\n if (!grid) {\n return [];\n }\n\n const col = Math.floor(x / grid.cellSize);\n const row = Math.floor(y / grid.cellSize);\n\n return grid.cells.get(gridKey(col, row)) || [];\n}\n\n// Клин тени предмета в лучах: предмет — круг `(cx, cy, radius)`, источник —\n// точка `(lx, ly)`. Четырёхугольник от точек касания до дальности `reach`\n// от источника: `[x0, y0, …, x3, y3]`. null — источник внутри предмета или\n// предмет дальше `reach`\nexport function shadowWedge(lx, ly, cx, cy, radius, reach) {\n const distance = Math.hypot(cx - lx, cy - ly);\n\n if (!(radius > 0) || distance <= radius) {\n return null;\n }\n\n const tangent = Math.sqrt(distance * distance - radius * radius);\n\n if (tangent >= reach) {\n return null;\n }\n\n const angle = Math.atan2(cy - ly, cx - lx);\n const half = Math.asin(radius / distance);\n const a0 = angle - half;\n const a1 = angle + half;\n\n return [\n lx + Math.cos(a0) * tangent,\n ly + Math.sin(a0) * tangent,\n lx + Math.cos(a0) * reach,\n ly + Math.sin(a0) * reach,\n lx + Math.cos(a1) * reach,\n ly + Math.sin(a1) * reach,\n lx + Math.cos(a1) * tangent,\n ly + Math.sin(a1) * tangent,\n ];\n}\n\n// медленное покачивание лучей фонаря (рад): две медленные синусоиды с\n// фазой от `seed` — у соседних фонарей не синхронно, у одного одинаково\n// на всех клиентах\nexport function shaftSway(seed, timeMs, amount) {\n const t = timeMs / 1000;\n\n return (\n amount *\n (Math.sin(t * 0.37 + seed * 1.7) * 0.6 +\n Math.sin(t * 0.61 + seed * 4.1) * 0.4)\n );\n}\n\n// --- свет верхнего уровня на рампах (этап 11) ---\n\n// Контур клина рампы в НАРИСОВАННЫХ координатах — та же проекция, что у меша\n// клина (`buildRampMeshes` + `updateHeightMesh`, extrusion.js): высота\n// вершины `lerp(from, to, progress)`, сдвиг `p + (p − cam) · k`. Высота\n// линейна вдоль прогона, а сдвиг от неё — нет, поэтому кромка режется на\n// `segmentsPerCell` отрезков на клетку, как меш. `lane` — полоса в клетках\n// (`buildRampLanes`). Возвращает `[x0, y0, …]`: одна кромка туда, другая\n// обратно\nexport function rampWedgePolygon(lane, step, scale, camera, shear, segmentsPerCell) {\n const alongX = lane.axis === 0;\n const cells = alongX ? lane.col1 - lane.col0 : lane.row1 - lane.row0;\n const segments = Math.max(1, cells * Math.max(1, Math.round(segmentsPerCell) || 1));\n const x0 = lane.col0 * step * scale.x;\n const x1 = lane.col1 * step * scale.x;\n const y0 = lane.row0 * step * scale.y;\n const y1 = lane.row1 * step * scale.y;\n const project = (x, y, k) => [x + (x - camera.x) * k, y + (y - camera.y) * k];\n const sideA = [];\n const sideB = [];\n\n for (let i = 0; i <= segments; i += 1) {\n const t = i / segments;\n const progress = lane.sign > 0 ? t : 1 - t;\n const k = (lane.from + (lane.to - lane.from) * progress) * shear;\n\n if (alongX) {\n const x = x0 + (x1 - x0) * t;\n\n sideA.push(project(x, y0, k));\n sideB.push(project(x, y1, k));\n } else {\n const y = y0 + (y1 - y0) * t;\n\n sideA.push(project(x0, y, k));\n sideB.push(project(x1, y, k));\n }\n }\n\n return [...sideA, ...sideB.reverse()].flat();\n}\n\n// --- фары и стены (этап 12) ---\n\n// Расстояние от точки `(ox, oy)` по единичному направлению `(dx, dy)` до\n// входа в первую занятую клетку (клетка `cellW × cellH` в мировых\n// единицах) — обход сетки по граням клеток (DDA, Amanatides–Woo). Нет\n// стены ближе `maxDist` — `maxDist`; начало в занятой клетке — 0.\n// `isBlocked(col, row, prevCol, prevRow, x, y)` — занята ли клетка при\n// входе в неё из соседней `(prevCol, prevRow)` в точке `(x, y)` (рампе\n// важно, через какой край и на какой высоте в неё вошли); для клетки\n// начала соседней нет\nexport function castRay(ox, oy, dx, dy, maxDist, isBlocked, cellW, cellH) {\n let col = Math.floor(ox / cellW);\n let row = Math.floor(oy / cellH);\n\n if (isBlocked(col, row, null, null, ox, oy)) {\n return 0;\n }\n\n const stepCol = dx > 0 ? 1 : -1;\n const stepRow = dy > 0 ? 1 : -1;\n const deltaX = dx !== 0 ? Math.abs(cellW / dx) : Infinity;\n const deltaY = dy !== 0 ? Math.abs(cellH / dy) : Infinity;\n let nextX = Infinity;\n let nextY = Infinity;\n\n if (dx !== 0) {\n nextX = ((dx > 0 ? col + 1 : col) * cellW - ox) / dx;\n }\n\n if (dy !== 0) {\n nextY = ((dy > 0 ? row + 1 : row) * cellH - oy) / dy;\n }\n\n for (;;) {\n const prevCol = col;\n const prevRow = row;\n let t;\n\n if (nextX < nextY) {\n t = nextX;\n col += stepCol;\n nextX += deltaX;\n } else {\n t = nextY;\n row += stepRow;\n nextY += deltaY;\n }\n\n if (!(t < maxDist)) {\n return maxDist;\n }\n\n if (isBlocked(col, row, prevCol, prevRow, ox + dx * t, oy + dy * t)) {\n return Math.max(0, t);\n }\n }\n}\n\n// на сколько уровней клин рампы может быть выше фары в точке, где луч\n// входит в него через борт или верхний торец, и всё ещё её пропускать:\n// низкий край у подножия ловит свет и сбоку\nexport const RAMP_CLEARANCE = 0.2;\n\n// Рампа как препятствие свету уровня её подножия. `lane` — полоса клетки,\n// в которую входит луч, `prevLane` — полоса клетки, из которой он вышел\n// (null — не рампа); полосы — `buildRampLanes` (клетки, `axis`, `sign`,\n// `from`, `to`). `(x, y)` — точка входа, `z` — высота фары в уровнях.\n// Правила (источник вне рампы):\n// - вошёл через подножие (шаг вдоль оси в сторону подъёма) — склон\n// освещён, луч идёт по полосе;\n// - вошёл через борт или верхний торец — стоп, если клин в точке входа\n// выше фары больше чем на `clearance`;\n// - вышел из полосы — стоп: склон — конечная поверхность, пол за горкой\n// и под её торцом свет не получает\nexport function rampBlocks({\n lane,\n prevLane,\n col,\n row,\n prevCol,\n prevRow,\n x,\n y,\n z,\n cellW,\n cellH,\n clearance = RAMP_CLEARANCE,\n}) {\n if (prevLane && prevLane !== lane) {\n return true;\n }\n\n if (!lane || prevLane === lane || prevCol === null || prevCol === undefined) {\n return false;\n }\n\n const alongX = lane.axis === 0;\n const step = alongX ? col - prevCol : row - prevRow;\n\n if (step === lane.sign) {\n return false;\n }\n\n const a0 = alongX ? lane.col0 * cellW : lane.row0 * cellH;\n const a1 = alongX ? lane.col1 * cellW : lane.row1 * cellH;\n const along = alongX ? x : y;\n const span = a1 - a0 || 1;\n const t = Math.min(1, Math.max(0, (along - a0) / span));\n const progress = lane.sign > 0 ? t : 1 - t;\n const height = lane.from + (lane.to - lane.from) * progress;\n\n return height - (z || 0) > clearance;\n}\n\n// Точка упора оси: первая стена на луче не дальше `length` —\n// `{ distance, x, y }`, иначе null\nexport function firstHit(ox, oy, dx, dy, length, isBlocked, cellW, cellH) {\n const distance = castRay(ox, oy, dx, dy, length, isBlocked, cellW, cellH);\n\n if (distance >= length) {\n return null;\n }\n\n return { distance, x: ox + dx * distance, y: oy + dy * distance };\n}\n\n// Полигон видимости конуса фары — веер из вершины `(x, y)` до ближней стены\n// или до края прямоугольника текстуры: `alongMax` вперёд по оси `rotation`,\n// `alongBack` назад (размытие за фарой), `acrossMax` поперёк. Вперёд —\n// `rays` лучей поровну на ±90° от оси: размытый край текстуры у самой фары\n// шире клина на постоянный отступ, и веер на угол клина срезал бы его —\n// конус у стены становился резким. Назад — несколько лучей, замыкающих\n// веер: без них на линии через вершину поперёк оси обрывалось размытие за\n// фарой. Результат — `{ points, clipped, closed }`: `points` — `[x, y, x0,\n// y0, …]` (вершина, затем концы лучей по кругу) в мировых единицах,\n// `clipped` — хоть один луч упёрся в стену (иначе конус целый и рисуется\n// прежним спрайтом), `closed` — веер замкнут (`fanIndices`)\nexport function coneFan(\n { x, y, rotation, alongMax, alongBack = 0, acrossMax, rays },\n isBlocked,\n cellW,\n cellH,\n) {\n const count = Math.max(2, Math.round(rays) || 2);\n const closed = alongBack > 0;\n const back = closed ? Math.max(3, Math.round(count / 8)) : 0;\n const offsets = [];\n\n for (let i = 0; i < count; i += 1) {\n offsets.push(-Math.PI / 2 + (Math.PI * i) / (count - 1));\n }\n\n for (let j = 0; j < back; j += 1) {\n offsets.push(Math.PI / 2 + (Math.PI * (j + 1)) / (back + 1));\n }\n\n const points = new Float32Array((offsets.length + 1) * 2);\n let clipped = false;\n\n points[0] = x;\n points[1] = y;\n\n offsets.forEach((offset, i) => {\n const angle = rotation + offset;\n const dx = Math.cos(angle);\n const dy = Math.sin(angle);\n const along = Math.cos(offset);\n const across = Math.abs(Math.sin(offset));\n let reach = across > 1e-9 ? acrossMax / across : Infinity;\n\n if (along > 1e-9) {\n reach = Math.min(reach, alongMax / along);\n } else if (along < -1e-9) {\n reach = Math.min(reach, alongBack / -along);\n }\n\n const distance = castRay(x, y, dx, dy, reach, isBlocked, cellW, cellH);\n\n if (distance < reach - 1e-6) {\n clipped = true;\n }\n\n points[(i + 1) * 2] = x + dx * distance;\n points[(i + 1) * 2 + 1] = y + dy * distance;\n });\n\n return { points, clipped, closed };\n}\n\n// UV точек веера в текстуре конуса — та же раскладка, что у спрайта\n// (`itemOf`): вершина — в `(margin, height / 2)` текстуры, ось — по `+x`,\n// `sx`/`sy` — мировых единиц на пиксель текстуры вдоль и поперёк оси\nexport function fanUvs(points, { x, y, rotation, sx, sy, margin, width, height }) {\n const cos = Math.cos(rotation);\n const sin = Math.sin(rotation);\n const uvs = new Float32Array(points.length);\n\n for (let i = 0; i < points.length; i += 2) {\n const rx = points[i] - x;\n const ry = points[i + 1] - y;\n const along = rx * cos + ry * sin;\n const across = ry * cos - rx * sin;\n\n uvs[i] = (margin + along / sx) / width;\n uvs[i + 1] = (height / 2 + across / sy) / height;\n }\n\n return uvs;\n}\n\n// индексы веера из `count` лучей: треугольники (вершина, луч i, луч i + 1);\n// `closed` — ещё и последний луч с первым\nexport function fanIndices(count, closed = false) {\n const open = Math.max(0, count - 1);\n const indices = new Uint32Array((open + (closed && count > 2 ? 1 : 0)) * 3);\n\n for (let i = 0; i < open; i += 1) {\n indices[i * 3] = 0;\n indices[i * 3 + 1] = i + 1;\n indices[i * 3 + 2] = i + 2;\n }\n\n if (closed && count > 2) {\n indices[open * 3] = 0;\n indices[open * 3 + 1] = count;\n indices[open * 3 + 2] = 1;\n }\n\n return indices;\n}\n","import { Ticker } from 'pixi.js';\n\n// Общие часы анимаций карты: секунды с запуска, одно приращение на тик\n// общего тикера. Движок может рисовать полотно несколько раз за тик, и все\n// эти отрисовки обязаны видеть одно и то же время — иначе кадр тайла\n// менялся бы тем быстрее, чем больше отрисовок пришло в тик. Часы одни на\n// все карты и слои: вода и конвейеры разных слоёв идут синхронно.\nexport function createAnimationClock(ticker = Ticker.shared) {\n let lastTime = null;\n let seconds = 0;\n\n return {\n // секунды с первого обращения; повторный вызов в том же тике не\n // двигает время\n now() {\n const time = ticker.lastTime;\n\n if (time !== lastTime) {\n if (lastTime !== null) {\n seconds += Math.max(0, time - lastTime) / 1000;\n }\n\n lastTime = time;\n }\n\n return seconds;\n },\n };\n}\n\nexport const animationClock = createAnimationClock();\n\n// Время, квантованное по `maxFps`: смена кадра не чаще этого числа раз в\n// секунду. 0 или отсутствие потолка — время как есть\nexport function quantizeTime(seconds, maxFps) {\n if (!(maxFps > 0)) {\n return seconds;\n }\n\n return Math.floor(seconds * maxFps) / maxFps;\n}\n","import { Container, Sprite } from 'pixi.js';\n\n// Анимированные тайлы слоя карты (`game.animatedTiles`): вода, конвейеры.\n// В запекание они не попадают (`bakeTileLayer`, `exclude`) и рисуются\n// живыми спрайтами — по одному на клетку, в контейнере `animated` слоя.\n// Координаты — клетки грида в немасштабированных единицах, как у\n// запечённого спрайта: параллакс и масштаб слою даёт `layerRoot`.\n//\n// Спрайты группируются по описанию: все спрайты группы разделяют номер\n// кадра, и обновление — один проход по группе. Спрайты не поворачиваются:\n// направление ленты уже нарисовано в кадрах.\n\n// id тайлов, у которых есть описание анимации\nexport function animatedTileIds(defs) {\n return new Set(Object.keys(defs || {}).map(Number));\n}\n\n// Частота смены кадров описания. Конвейер задаёт `speed` (ед./с, та же, что\n// `belt` его поверхности): шевроны сдвигаются на 1/N тайла за кадр, значит\n// `fps = speed · N / (step · scale)`. `step` на клиенте не масштабирован,\n// масштаб карты лежит отдельно\nexport function resolveFps(def, step, scale = 1) {\n if (def.speed !== undefined) {\n const count = def.frames?.length || 0;\n const tile = step * (typeof scale === 'number' ? scale : scale.x);\n\n return tile > 0 ? (def.speed * count) / tile : 0;\n }\n\n return def.fps || 0;\n}\n\n// номер кадра в момент `t` (секунды)\nexport function frameIndex(t, fps, count) {\n if (!(count > 0) || !(fps > 0)) {\n return 0;\n }\n\n return Math.floor(t * fps) % count;\n}\n\n// Спрайты анимированных тайлов слоя. `data` — `{ map, tiles, step, scale }`\n// слоя, `sheet` — разобранный тайл-лист (`parseSpriteSheet`), `defs` —\n// `game.animatedTiles`. Возвращает `{ container, groups }`; пустой список\n// групп — у слоя анимированных тайлов нет\nexport function buildAnimatedTiles(data, sheet, defs) {\n const container = new Container();\n const groups = [];\n\n container.label = 'animatedTiles';\n\n if (!defs) {\n return { container, groups };\n }\n\n const own = new Set(data.tiles);\n\n for (const [key, def] of Object.entries(defs)) {\n const id = Number(key);\n\n if (!own.has(id)) {\n continue;\n }\n\n const textures = (def.frames || []).map(frame => sheet.textures[`frame${frame}`]).filter(Boolean);\n\n if (textures.length === 0) {\n console.warn(`Animated tile ${id}: no frames found in the sprite sheet`);\n continue;\n }\n\n const group = {\n id,\n fps: resolveFps(def, data.step, data.scale),\n textures,\n sprites: [],\n container: new Container(),\n frame: 0,\n };\n\n group.container.label = `animatedTile-${id}`;\n\n for (let y = 0; y < data.map.length; y += 1) {\n const row = data.map[y];\n\n for (let x = 0; x < row.length; x += 1) {\n if (row[x] !== id) {\n continue;\n }\n\n const sprite = new Sprite(textures[0]);\n\n sprite.x = x * data.step;\n sprite.y = y * data.step;\n sprite.cullable = true;\n group.sprites.push(sprite);\n group.container.addChild(sprite);\n }\n }\n\n if (group.sprites.length === 0) {\n group.container.destroy();\n continue;\n }\n\n container.addChild(group.container);\n groups.push(group);\n }\n\n return { container, groups };\n}\n\n// Кадры групп в момент `t`. `isVisible(container)` — виден ли контейнер\n// группы: группа целиком вне экрана не обновляется (догоняет сразу, как\n// вернётся на экран — номер кадра считается от времени, а не шагами)\nexport function updateAnimatedTiles(groups, t, isVisible = () => true) {\n for (let i = 0; i < groups.length; i += 1) {\n const group = groups[i];\n\n if (!isVisible(group.container)) {\n continue;\n }\n\n const frame = frameIndex(t, group.fps, group.textures.length);\n\n if (frame === group.frame) {\n continue;\n }\n\n group.frame = frame;\n\n const texture = group.textures[frame];\n\n for (let j = 0; j < group.sprites.length; j += 1) {\n group.sprites[j].texture = texture;\n }\n }\n}\n\n// Текстуры принадлежат тайл-листу слоя: спрайты их не освобождают\nexport function destroyAnimatedTiles(built) {\n built.container.parent?.removeChild(built.container);\n built.container.destroy({ children: true, texture: false, textureSource: false });\n built.groups.length = 0;\n}\n","import { Sprite } from 'pixi.js';\nimport { frameIndex } from './animatedTiles.js';\n\n// Декали слоя карты (`game.decals`): вентиляторы на крышах и прочие мелкие\n// анимированные спрайты в центре клетки. Живут в контейнере `animated`\n// слоя, в немасштабированных координатах грида — параллакс и прозрачность\n// слоя достаются им вместе с `layerRoot`.\n//\n// `kind: 'rotate'` — вращение `rps` оборотов в секунду по общим часам;\n// `kind: 'frames'` — смена кадров, как у анимированных тайлов.\n\n// Элементы карты, которыми владеет слой `(level, layer)`: ровно один\n// владелец на вывеску и декаль\nexport function ownedBy(items, level, layer) {\n return (items || []).filter(\n item => (item.level || 0) === level && Number(item.layer) === layer,\n );\n}\n\nexport function buildDecals(items, sheet, step, animated) {\n const decals = [];\n\n for (const item of items) {\n const [col, row] = item.cell || [0, 0];\n const frames = item.kind === 'frames' ? item.frames || [item.frame] : [item.frame];\n const textures = frames.map(frame => sheet.textures[`frame${frame}`]).filter(Boolean);\n\n if (textures.length === 0) {\n console.warn(`Decal at [${col}, ${row}]: frame not found in the sprite sheet`);\n continue;\n }\n\n const sprite = new Sprite(textures[0]);\n\n sprite.anchor.set(0.5);\n sprite.position.set((col + 0.5) * step, (row + 0.5) * step);\n animated.addChild(sprite);\n\n decals.push({ item, sprite, textures, frame: 0 });\n }\n\n return decals;\n}\n\n// позы декалей в момент `t` (секунды)\nexport function updateDecals(decals, t) {\n for (let i = 0; i < decals.length; i += 1) {\n const decal = decals[i];\n\n if (decal.item.kind === 'rotate') {\n decal.sprite.rotation = (decal.item.rps || 0) * 2 * Math.PI * t;\n continue;\n }\n\n if (decal.item.kind === 'frames') {\n const frame = frameIndex(t, decal.item.fps, decal.textures.length);\n\n if (frame !== decal.frame) {\n decal.frame = frame;\n decal.sprite.texture = decal.textures[frame];\n }\n }\n }\n}\n\n// текстуры принадлежат тайл-листу слоя\nexport function destroyDecals(decals) {\n for (const decal of decals) {\n decal.sprite.parent?.removeChild(decal.sprite);\n decal.sprite.destroy({ texture: false, textureSource: false });\n }\n\n decals.length = 0;\n}\n","// Мерцание неоновой вывески: чистая функция времени и зерна. Никакого\n// `Math.random` — одинаковая картина у всех клиентов и в тестах.\n//\n// Составляющие:\n// - базовый пульс `1 − pulse·(0.5 + 0.5·sin)`;\n// - «сбои» — короткие провалы по псевдослучайному расписанию: время режется\n// на окна, и для каждого окна SplitMix32 от зерна и номера окна решает,\n// будет ли в нём сбой и где он начнётся. Сбой гасит ядро (`core`)\n// сильнее, чем ореол (`glow`): газ в трубке гаснет, а стекло ещё светится.\n\n// частота базового пульса, Гц\nconst PULSE_HZ = 0.8;\n\n// длина окна расписания сбоев и длительность одного сбоя, с\nexport const DROPOUT_WINDOW = 0.6;\nexport const DROPOUT_DURATION = 0.09;\n\n// яркость ядра и ореола во время сбоя\nconst DROPOUT_CORE = 0.12;\nconst DROPOUT_GLOW = 0.55;\n\n// SplitMix32: беззнаковое 32-битное число из 32-битного входа\nexport function splitMix32(value) {\n let z = (value + 0x9e3779b9) >>> 0;\n\n z = Math.imul(z ^ (z >>> 16), 0x85ebca6b) >>> 0;\n z = Math.imul(z ^ (z >>> 13), 0xc2b2ae35) >>> 0;\n\n return (z ^ (z >>> 16)) >>> 0;\n}\n\n// зерно вывески из её клетки и уровня: соседние вывески мерцают не синхронно\nexport function cellSeed(col, row, level = 0) {\n return splitMix32(\n (Math.imul(col, 73856093) ^ Math.imul(row, 19349663) ^ Math.imul(level, 83492791)) >>> 0,\n );\n}\n\n// доля [0, 1) из пары (зерно, номер окна, соль)\nfunction unit(seed, window, salt) {\n return splitMix32((seed ^ Math.imul(window + salt, 0x27d4eb2d)) >>> 0) / 4294967296;\n}\n\nconst clamp01 = value => Math.min(1, Math.max(0, value));\n\n// идёт ли сбой в момент `t` (секунды)\nexport function inDropout(t, seed, dropouts) {\n if (!(dropouts > 0)) {\n return false;\n }\n\n const window = Math.floor(t / DROPOUT_WINDOW);\n\n if (unit(seed, window, 0) >= dropouts) {\n return false;\n }\n\n const start = window * DROPOUT_WINDOW + unit(seed, window, 1) * (DROPOUT_WINDOW - DROPOUT_DURATION);\n\n return t >= start && t < start + DROPOUT_DURATION;\n}\n\n// яркость ядра и ореола в `[0, 1]`. `cfg` — `sign.flicker`:\n// `pulse` (глубина пульса) и `dropouts` (доля окон со сбоем); без cfg — 1\nexport function brightness(t, seed, cfg) {\n const pulse = clamp01(cfg?.pulse || 0);\n const phase = (seed % 1000) / 1000;\n const base = 1 - pulse * (0.5 + 0.5 * Math.sin(2 * Math.PI * (PULSE_HZ * t + phase)));\n\n if (inDropout(t, seed, cfg?.dropouts || 0)) {\n return { core: clamp01(base * DROPOUT_CORE), glow: clamp01(base * DROPOUT_GLOW) };\n }\n\n return { core: clamp01(base), glow: clamp01(base) };\n}\n","// запас прозрачных пикселей вокруг фигуры под размытие\n// BlurFilter Pixi (ядро 5, несколько проходов) даёт сигму ≈ 1.29 * strength,\n// а носитель цепочки ядер обрывается на 2 * s0 * (1 + 1/2 + 1/4 + ...)\n// ≈ 3.46 * strength (s0 ≈ 0.866 * strength - сила первого прохода),\n// поэтому 4 * strength гарантированно покрывает размытие\n// без запаса generateTexture обрезает размытие рамкой,\n// и спрайт получает видимый прямоугольный край\n// ВАЖНО: запас работает только в паре с filter.padding = blurPadding(blur),\n// иначе Pixi сам обрежет фильтр на 2 * strength вокруг фигуры\nconst BLUR_SPREAD_FACTOR = 4;\n\nexport default function blurMargin(strength) {\n return Math.ceil(strength * BLUR_SPREAD_FACTOR);\n}\n\n// padding фильтра при выпечке: область фильтра вдвое шире запаса холста.\n// Промежуточные проходы BlurFilter (WebGL) пишут во временную текстуру\n// пула без очистки, и отсчёты у края области тянут внутрь мусор прошлых\n// кадров — светлую рамку по краю текстуры (квадрат вокруг воронки).\n// Мусор проникает не глубже носителя ядра (blurMargin), поэтому при таком\n// padding он остаётся за рамкой frame = фигура + blurMargin\nexport function blurPadding(strength) {\n return blurMargin(strength) * 2;\n}\n","import { BlurFilter, Container, Rectangle, Text } from 'pixi.js';\nimport blurMargin, { blurPadding } from '../../bakers/blurMargin.js';\n\n// Кэш текстур неоновых вывесок. Бейкер здесь не годится: текстов много, и\n// приходят они из карты. Текстуры белые — цвет вывеска задаёт `tint`,\n// поэтому ключ — только текст и размер.\n//\n// Кэш общий на сессию, со счётчиком ссылок по рендереру и ключу: на нуле\n// текстуры освобождаются. Счётчик обязателен и из-за потери WebGL-контекста:\n// движок при восстановлении уничтожает все части, счётчик падает до нуля,\n// мёртвые текстуры уходят, и новые вывески запекаются заново. Кэш без\n// счётчика отдал бы новым частям текстуры мёртвого контекста.\n// `Assets.unload` здесь ни при чём — это не ассеты.\n\n// renderer -> Map(key -> { core, glow, count })\nconst caches = new WeakMap();\n\nexport function neonKey({ text, size }) {\n return `${size}|${text}`;\n}\n\n// сила размытия ореола от размера шрифта\nexport function neonBlur(size) {\n return Math.max(2, Math.round(size / 4));\n}\n\nfunction bake(renderer, { text, size }) {\n const blur = neonBlur(size);\n const margin = blurMargin(blur);\n const wrapper = new Container();\n const label = new Text({\n text: String(text),\n style: {\n fontFamily: 'monospace',\n fontWeight: 'bold',\n fontSize: size,\n fill: 0xffffff,\n },\n });\n\n label.position.set(margin, margin);\n wrapper.addChild(label);\n\n // ядро и ореол — одного размера кадра: оба спрайта вывески ставятся\n // якорем в центр и совпадают без подгонки\n const frame = new Rectangle(\n 0,\n 0,\n Math.ceil(label.width) + margin * 2,\n Math.ceil(label.height) + margin * 2,\n );\n const core = renderer.generateTexture({ target: wrapper, frame });\n const filter = new BlurFilter({ strength: blur, quality: 6 });\n\n // запас работает только в паре с padding (см. blurMargin), а область\n // шире рамки — чтобы мусор пула с края не попал в текстуру\n filter.padding = blurPadding(blur);\n label.filters = [filter];\n\n const glow = renderer.generateTexture({ target: wrapper, frame });\n\n label.filters = [];\n filter.destroy();\n wrapper.destroy({ children: true });\n\n return { core, glow };\n}\n\n// +1 к счётчику; на первом взятии ключа текстуры запекаются\nexport function getNeonTextures(renderer, spec) {\n let cache = caches.get(renderer);\n\n if (!cache) {\n cache = new Map();\n caches.set(renderer, cache);\n }\n\n const key = neonKey(spec);\n let entry = cache.get(key);\n\n if (!entry) {\n entry = { ...bake(renderer, spec), count: 0 };\n cache.set(key, entry);\n }\n\n entry.count += 1;\n\n return { core: entry.core, glow: entry.glow };\n}\n\n// −1 к счётчику; на нуле текстуры освобождаются. Зовётся, когда спрайты\n// вывески уже сняты со сцены\nexport function releaseNeonTextures(renderer, spec) {\n const cache = caches.get(renderer);\n const key = neonKey(spec);\n const entry = cache?.get(key);\n\n if (!entry) {\n return;\n }\n\n entry.count -= 1;\n\n if (entry.count > 0) {\n return;\n }\n\n cache.delete(key);\n entry.core.destroy(true);\n entry.glow.destroy(true);\n}\n","import { Sprite } from 'pixi.js';\nimport { offsetPoint } from '../../parallax.js';\nimport { brightness, cellSeed } from '../../neonFlicker.js';\nimport { getNeonTextures, releaseNeonTextures } from './neonCache.js';\nimport { parallax as parallaxConfig } from '../../../config/render.js';\n\n// Неоновая вывеска карты (`game.signs`): два аддитивных спрайта — ореол\n// (`glow`) и ядро (`core`) — из общего неонового кэша, цвет — `tint`.\n//\n// Где рисуется:\n// - ночью спрайты отдаются в эмиссивный слой сервиса освещения\n// (`lighting.addEmissive`) и светят поверх затемнения, а вывеска\n// дополнительно заводит источник света своего цвета (`sign.light`).\n// Эмиссив живёт в координатах сцены, поэтому позицию и масштаб проекции\n// вывеска ставит сама каждую отрисовку;\n// - без ночи (или при `lighting.enabled = false`) `addEmissive` возвращает\n// `false`: спрайты остаются в контейнере `animated` своего слоя, в\n// координатах грида, и параллакс с прозрачностью им даёт слой.\n//\n// Строится в асинхронной сборке слоя, то есть ПОСЛЕ синхронного\n// `acquireMap` в конструкторе `MapLayer`: к этому моменту сервис уже знает,\n// ночная ли карта, и результат `addEmissive` верен.\nexport default class NeonSign {\n // `sign` — элемент `game.signs`; `step`/`scale` — грид слоя; `animated` —\n // контейнер слоя для дневного режима; `lighting` — сервис или null\n constructor({ sign, renderer, lighting, animated, step, scale, level }) {\n const [col, row] = sign.cell || [0, 0];\n\n this._sign = sign;\n this._renderer = renderer;\n this._lighting = lighting || null;\n this._level = level;\n this._seed = cellSeed(col, row, level);\n this._scale = scale;\n this._light = null;\n\n // центр клетки: в единицах грида (дневной режим) и в мировых (эмиссив)\n this._gridX = (col + 0.5) * step;\n this._gridY = (row + 0.5) * step;\n this._worldX = this._gridX * scale.x;\n this._worldY = this._gridY * scale.y;\n\n const textures = getNeonTextures(renderer, sign);\n\n this.glow = this._sprite(textures.glow);\n this.core = this._sprite(textures.core);\n\n this.emissive =\n Boolean(this._lighting?.addEmissive(this.glow, level)) &&\n this._lighting.addEmissive(this.core, level);\n\n if (this.emissive) {\n if (sign.light) {\n this._light = this._lighting.addLight({\n kind: 'radial',\n level,\n x: this._worldX,\n y: this._worldY,\n z: level,\n radius: sign.light.radius ?? 0,\n color: sign.color ?? 0xffffff,\n intensity: sign.light.intensity ?? 1,\n });\n }\n } else {\n // ореол мог уйти в эмиссив, а ядро — нет (сервис сменил карту между\n // вызовами): вывеска целиком остаётся у слоя\n this._lighting?.removeEmissive(this.glow);\n this.glow.position.set(this._gridX, this._gridY);\n this.core.position.set(this._gridX, this._gridY);\n animated.addChild(this.glow, this.core);\n }\n }\n\n _sprite(texture) {\n const sprite = new Sprite(texture);\n\n sprite.anchor.set(0.5);\n sprite.blendMode = 'add';\n sprite.tint = this._sign.color ?? 0xffffff;\n sprite.angle = this._sign.angle || 0;\n\n return sprite;\n }\n\n // `t` — время анимаций (секунды) или null, когда анимации выключены;\n // `camera` — центр камеры кадра; `levelView` — прозрачность эмиссива;\n // `roofAlpha` — прозрачность крыши под вывеской (null — не на крыше):\n // крыша непрозрачна, пока не закрывает танк, и вывеска обязана гаснуть\n // вместе с ней, а не по кругу вокруг игрока\n update(t, camera, levelView, roofAlpha = null) {\n const light = t === null ? { core: 1, glow: 1 } : brightness(t, this._seed, this._sign.flicker);\n\n if (!this.emissive) {\n this.glow.alpha = light.glow;\n this.core.alpha = light.core;\n\n return;\n }\n\n const k = this._level * parallaxConfig.shear;\n const point = offsetPoint(this._worldX, this._worldY, camera, k);\n let see = 1;\n\n if (roofAlpha !== null) {\n see = roofAlpha;\n } else if (levelView) {\n see = levelView.alphaFor(this._level, point.x, point.y, 0);\n }\n\n for (const sprite of [this.glow, this.core]) {\n sprite.position.set(point.x, point.y);\n sprite.scale.set(this._scale.x * (1 + k), this._scale.y * (1 + k));\n }\n\n this.glow.alpha = see * light.glow;\n this.core.alpha = see * light.core;\n\n if (this._light) {\n this._lighting.updateLight(this._light, {\n intensity: (this._sign.light.intensity ?? 1) * light.glow,\n });\n }\n }\n\n // Снимает источник, эмиссив и спрайты. Текстуры кэша отдаёт возвращаемый\n // колбэк: слой зовёт его после того, как парт снят со сцены\n destroy() {\n if (this._lighting) {\n if (this._light) {\n this._lighting.removeLight(this._light);\n this._light = null;\n }\n\n this._lighting.removeEmissive(this.glow);\n this._lighting.removeEmissive(this.core);\n }\n\n for (const sprite of [this.glow, this.core]) {\n sprite.parent?.removeChild(sprite);\n sprite.destroy({ texture: false, textureSource: false });\n }\n\n const renderer = this._renderer;\n const sign = this._sign;\n\n this._lighting = null;\n this._renderer = null;\n\n return () => releaseNeonTextures(renderer, sign);\n }\n}\n","import { Ticker } from 'pixi.js';\nimport { animationClock, quantizeTime } from '../../animationClock.js';\nimport { parseSpriteSheet } from '../bakeTileLayer.js';\nimport {\n animatedTileIds,\n buildAnimatedTiles,\n destroyAnimatedTiles,\n updateAnimatedTiles,\n} from './animatedTiles.js';\nimport { buildDecals, destroyDecals, ownedBy, updateDecals } from './decals.js';\nimport NeonSign from './NeonSign.js';\nimport { animations as animationsConfig } from '../../../config/render.js';\n\n// Анимированные элементы статического слоя: тайлы (`game.animatedTiles`),\n// декали (`game.decals`) и неоновые вывески (`game.signs`). Как и\n// `layerAssets`, модуль ничего не знает о парте: на вход — описание, на\n// выход — готовое состояние, которое слой держит у себя.\n\n// Что из анимаций достаётся слою `(level, layer)` с тайлами `tiles`.\n// Считается синхронно в конструкторе: от этого зависит, нужен ли слою\n// колбэк `onRender`\nexport function layerAnimationSpec(game, tiles, level, layer) {\n const ids = animatedTileIds(game?.animatedTiles);\n const own = new Set(tiles);\n\n return {\n exclude: [...ids],\n hasTiles: [...ids].some(id => own.has(id)),\n signs: ownedBy(game?.signs, level, layer),\n decals: ownedBy(game?.decals, level, layer),\n };\n}\n\nexport function hasAnimations(spec) {\n return spec.hasTiles || spec.signs.length > 0 || spec.decals.length > 0;\n}\n\n// `spec` — `layerAnimationSpec`; остальное — грид слоя, его контейнер\n// `animated`, рендерер и сервис освещения (или null). `isAborted()` — парт\n// уже уничтожен. Возвращает состояние анимаций или null\nexport async function buildLayerAnimations({\n spec,\n game,\n baseTexture,\n spriteSheetData,\n map,\n tiles,\n step,\n scale,\n level,\n animated,\n renderer,\n lighting,\n isAborted,\n}) {\n let sheet = null;\n\n if (spec.hasTiles || spec.decals.length > 0) {\n sheet = await parseSpriteSheet(baseTexture, spriteSheetData);\n\n if (isAborted()) {\n sheet.destroy();\n\n return null;\n }\n }\n\n const state = {\n sheet,\n tiles: null,\n decals: [],\n signs: [],\n tick: null,\n };\n\n if (spec.hasTiles) {\n state.tiles = buildAnimatedTiles(\n { map, tiles, step, scale },\n sheet,\n game.animatedTiles,\n );\n animated.addChild(state.tiles.container);\n }\n\n if (sheet) {\n state.decals = buildDecals(spec.decals, sheet, step, animated);\n }\n\n state.signs = spec.signs.map(\n sign =>\n new NeonSign({ sign, renderer, lighting, animated, step, scale, level }),\n );\n\n return state;\n}\n\n// виден ли контейнер на экране: границы в глобальных координатах полотна\nfunction onScreen(screen) {\n if (!screen) {\n return () => true;\n }\n\n return container => {\n const bounds = container.getBounds();\n\n return (\n bounds.maxX >= 0 &&\n bounds.minX <= screen.width &&\n bounds.maxY >= 0 &&\n bounds.minY <= screen.height\n );\n };\n}\n\n// Каждая отрисовка слоя. Кадры тайлов и декалей — один раз на тик (и\n// проверка экрана тоже); позиции эмиссивных вывесок — каждую отрисовку:\n// они едут за камерой. `roofAlpha` — прозрачность слоя-крыши (или null):\n// вывеска на крыше гаснет вместе с ней\nexport function updateLayerAnimations(\n state,\n { camera, levelView, screen, roofAlpha = null },\n) {\n const enabled = animationsConfig.enabled;\n const t = enabled\n ? quantizeTime(animationClock.now(), animationsConfig.maxFps)\n : null;\n const tick = Ticker.shared.lastTime;\n\n if (enabled && state.tick !== tick) {\n state.tick = tick;\n\n if (state.tiles) {\n updateAnimatedTiles(state.tiles.groups, t, onScreen(screen));\n }\n\n updateDecals(state.decals, t);\n }\n\n for (let i = 0; i < state.signs.length; i += 1) {\n state.signs[i].update(t, camera, levelView, roofAlpha);\n }\n}\n\n// Снимает всё со слоя и из сервиса освещения. Текстуры (тайл-лист слоя и\n// неоновый кэш) отдаёт возвращаемый колбэк — после снятия парта со сцены\nexport function destroyLayerAnimations(state) {\n if (!state) {\n return () => {};\n }\n\n if (state.tiles) {\n destroyAnimatedTiles(state.tiles);\n state.tiles = null;\n }\n\n destroyDecals(state.decals);\n\n const releases = state.signs.map(sign => sign.destroy());\n const sheet = state.sheet;\n\n state.signs = [];\n state.sheet = null;\n\n return () => {\n releases.forEach(release => release());\n sheet?.destroy();\n };\n}\n","import { Container, Texture, Assets } from 'pixi.js';\nimport { levelZ } from '../../levelZ.js';\nimport { cameraCenter } from '../../camera.js';\nimport { applyParallax } from '../../parallax.js';\nimport { baseScale, tileGrid } from './tileGrid.js';\nimport { createHole, dispose as disposeHole } from './holeOverlay.js';\nimport { buildLayerAssets, rampLanes } from './layerAssets.js';\nimport { updateSeeThrough } from './layerSeeThrough.js';\nimport {\n updateHeightMesh,\n updateWallMesh,\n orderWallMesh,\n} from './extrusion.js';\nimport { cellsOfTiles, mapKeyOf } from '../../lighting/lightMath.js';\nimport {\n buildLayerAnimations,\n destroyLayerAnimations,\n hasAnimations,\n layerAnimationSpec,\n updateLayerAnimations,\n} from './layerAnimations.js';\nimport {\n parallax as parallaxConfig,\n volume as volumeConfig,\n} from '../../../config/render.js';\n\n// Статический слой карты: запечённый тайл-лист, его параллакс, экструзия\n// объёма и клина рампы, прозрачность плиты моста. Стратегия рисует В\n// КОНТЕЙНЕР парта: ей нужны и дети, и zIndex, и alpha, и filters самого\n// парта.\nexport default class MapLayer {\n constructor(container, data, dependencies, imageBase, assets = {}) {\n this._container = container;\n this._renderer = dependencies.renderer;\n\n // где локальный игрок (сервис игры, src/client/levelView.js): нужен\n // только слоям уровня >= 1 — они уступают ему видимость\n this._levelView = dependencies.levelView || null;\n // журнал ошибок движка (необязательный сервис): отдаём его levelView —\n // карта может появиться раньше танка (наблюдатель)\n this._levelView?.setDiagnostics(dependencies.diagnostics ?? null);\n\n // прогоны рамп из ядра (сервис игры, src/client/index.js): по ним слой\n // строит клин горки. Источник один с физикой — второй обход грида на\n // JS расходился бы с ядром молча\n this._rampRuns = dependencies.rampRuns || null;\n\n // режим 'hole': фильтр «дыры» и её сила (0 — игрок не под слоем).\n // Фильтр создаётся лениво: слоёв уровня >= 1 на карте может не быть\n // вовсе, а программу шейдера тогда компилировать не за что. У слоя и у\n // его перекрывателя фильтры РАЗНЫЕ: у Pixi фильтр несёт свои uniform'ы,\n // одним на две цели не обойтись\n this._hole = createHole();\n this._occluderHole = createHole();\n\n // контейнер-перекрыватель: срезы объёма слоя живут не в парте, а\n // сиблингом на сцене — только так они получают zIndex ВЫШЕ динамики\n // своего уровня (см. OCCLUDER_BASE_Z). Клин рампы остаётся в парте:\n // танк, поднимающийся по горке, обязан рисоваться ПОВЕРХ её поверхности\n this._occluder = null;\n\n // масштаб карты держим числами: у статического слоя сам контейнер\n // остаётся единичным (мировые координаты), а `data.scale` носят его\n // дети — каждый вместе со своим сдвигом параллакса\n this._baseScale = baseScale(data.scale);\n\n // срезы экструзии: объём слоя и клин рампы. `{ target, k }` — контейнер\n // (или спрайт) и его высота в долях сдвига\n this._slices = [];\n this._rampTexture = null;\n // боковые текстуры объёма: по одной на тайл слоя, с повтором\n this._wallTextures = [];\n this.mapSprite = null; // спрайт для \"запеченной\" карты\n\n this._assetUrl = `${imageBase}${data.spriteSheet.img}`;\n this._baseTexturePromise = Assets.load(this._assetUrl);\n\n // data состоит из:\n // layer - слой,\n // tiles - массив с названиями тайлов,\n // spriteSheet - объект с данными картинки (например, PIXI.Spritesheet),\n // map - двумерный массив карты,\n // step - размер шага,\n // volume - высота слоя в уровнях (0 — плоский),\n // ramps - конфиги рамп ЭТОГО уровня как объявлены в карте.\n this._map = data.map;\n this._tiles = data.tiles;\n this._level = data.level || 0;\n this._floor = data.floor || [];\n\n // наборы для пофреймовых проверок (`_hasTileAt`): `includes` по\n // массиву зовётся до `volume.slices` раз на слой в кадр\n this._tileSet = new Set(this._tiles);\n this._floorSet = new Set(this._floor);\n\n // крыша (`game.roofs`): слой, ВСЕ тайлы которого — крыши его уровня. Она\n // не прозрачна и не снимает затемнение, пока не закрывает сам танк.\n // Крыше нужен свой рендер-слой: в смеси с плитой прозрачность решалась\n // бы одним правилом на обе\n this._roof = isRoofLayer(data.game, this._level, this._tiles);\n this._spriteSheetData = data.spriteSheet;\n this._step = data.step;\n this._layer = Number(data.layer) || 1;\n container.zIndex = levelZ(this._layer, this._level);\n\n // слой уровня N висит НАД землёй: его сдвиг от центра камеры — тот же\n // параллакс, что у объёмов и у танков. Без него плита моста рисуется\n // ровно в координатах асфальта и читается как глухая стена\n this._parallaxK = this._level * parallaxConfig.shear;\n\n // Корень слоя: запечённый спрайт и над ним контейнер `animated`\n // (анимированные тайлы, декали, дневной неон). Параллакс и масштаб\n // карты — у корня, поэтому живые спрайты едут вместе с картинкой.\n // Фильтр дыры и alpha плиты остаются на контейнере парта: в нём же\n // лежит клин рампы, и гаснуть обязаны оба\n this._layerRoot = new Container();\n this._layerRoot.label = 'layerRoot';\n this._animated = new Container();\n this._animated.label = 'animated';\n this._layerRoot.addChild(this._animated);\n applyParallax(this._layerRoot, null, this._parallaxK, this._baseScale);\n container.addChild(this._layerRoot);\n\n // анимации карты (`game.animatedTiles`, `signs`, `decals`), которыми\n // владеет ЭТОТ слой; строятся в `_build`\n this._animationSpec = layerAnimationSpec(\n data.game,\n this._tiles,\n this._level,\n this._layer,\n );\n this._animations = null;\n this._game = data.game;\n\n this._volume = Number(data.volume) || 0;\n\n // клин строит только тот рендер-слой, который сами тайлы рампы и\n // рисует: у карты слой земли и слой стен делят один грид\n this._ramps = (data.ramps || []).filter(ramp =>\n this._tiles.includes(ramp.tile),\n );\n\n this._extruding =\n volumeConfig.enabled &&\n volumeConfig.slices >= 1 &&\n (this._volume > 0 || this._ramps.length > 0);\n\n // грид тайлов значением: им одним отвечают на вопрос «что нарисовано\n // в этой мировой точке» (`tileGrid.tileAt`)\n this._grid = tileGrid(this._map, this._baseScale, this._step);\n\n // Ночь и освещение (сервис игры, src/client/lighting/). Строго СИНХРОННО,\n // до `_build` и любого await: иначе сервис станет «ночным» с задержкой,\n // и эмиссив первого кадра (неон) получит `addEmissive → false` и\n // останется без свечения. Часть не знает, какие ещё слои есть на карте, поэтому\n // «первой» не бывает — только счётчик по ключу карты; фонари создаёт\n // сам сервис\n this._lighting = dependencies.lighting?.enabled\n ? dependencies.lighting\n : null;\n this._mapKey = null;\n\n if (this._lighting) {\n this._lighting.registerTextures({\n radial: assets.lightRadialTexture,\n head: assets.lampHeadTexture,\n glint: assets.glintTexture,\n shaft: assets.lightShaftTexture,\n });\n this._mapKey = mapKeyOf(data);\n this._lighting.acquireMap(\n this._mapKey,\n data.game?.lighting,\n this._step,\n data.scale,\n {\n cols: Math.max(0, ...this._map.map(row => row.length)),\n rows: this._map.length,\n },\n );\n\n // вклад слоя в маску этажа: клетки его тайлов пола (перила уже в\n // `floor`); вклады слоёв одного уровня сервис объединяет. Крыша\n // вкладывает только свои клетки — в отдельную карту крыш\n if (this._level >= 1) {\n this._lighting.setLevelMask(\n this._level,\n cellsOfTiles(this._map, this._roof ? this._tiles : this._floor),\n this,\n { roof: this._roof },\n );\n }\n\n // клинья рамп слоя, ведущих вверх: их освещает и свет уровня вершины\n // (клин нарисован здесь, в части уровня подножия)\n if (this._ramps.length) {\n this._lighting.setRampWedges(\n this._level,\n rampLanes({\n ramps: this._ramps,\n rampRuns: this._rampRuns,\n level: this._level,\n baseScale: this._baseScale,\n step: this._step,\n }),\n this,\n );\n }\n\n // вершины объёмов: карта освещённости уровня закрывает их полумраком,\n // иначе верхний срез стены ловит фары танка на земле\n if (this._volume > 0) {\n this._lighting.setVolumeTops(\n this._level,\n cellsOfTiles(this._map, this._tiles),\n this._volume,\n this,\n );\n }\n }\n\n // прозрачность считает только плита моста, параллакс — она же и любой\n // слой с объёмом: вешать колбэк на плоский слой уровня 0 значило бы\n // звать его каждый кадр ради выхода по первой же строке. На ночной\n // карте колбэк нужен любому слою: он ведёт сервис освещения\n this.needsRender =\n this._level >= 1 ||\n this._extruding ||\n Boolean(this._lighting?.isNight()) ||\n hasAnimations(this._animationSpec);\n\n this._build(data);\n }\n\n // Сборка ассетов слоя. Спецификация уходит в `layerAssets` значением, а\n // готовые объекты возвращаются оттуда и раскладываются по полям ЗДЕСЬ:\n // владелец состояния — парт, модуль сборки его полей не знает.\n async _build(data) {\n const assets = await buildLayerAssets({\n container: this._container,\n layerRoot: this._layerRoot,\n exclude: this._animationSpec.exclude,\n baseTexture: this._baseTexturePromise,\n spriteSheetData: data.spriteSheet,\n map: this._map,\n tiles: this._tiles,\n step: this._step,\n renderer: this._renderer,\n level: this._level,\n layer: this._layer,\n volume: this._volume,\n ramps: this._ramps,\n rampRuns: this._rampRuns,\n baseScale: this._baseScale,\n parallaxK: this._parallaxK,\n extruding: this._extruding,\n assetUrl: this._assetUrl,\n isAborted: () => this._container.destroyed,\n });\n\n this.mapSprite = assets.mapSprite;\n this._occluder = assets.occluder;\n this._slices = assets.slices;\n this._rampTexture = assets.rampTexture;\n this._wallTextures = assets.wallTextures ?? [];\n\n // Анимации — после запекания (тайл-лист уже загружен) и после\n // синхронного `acquireMap` конструктора: вывеска спрашивает у сервиса\n // освещения, ночь ли, и ответ фиксирует навсегда\n if (\n !this.mapSprite ||\n this._container.destroyed ||\n !hasAnimations(this._animationSpec)\n ) {\n return;\n }\n\n this._animations = await buildLayerAnimations({\n spec: this._animationSpec,\n game: this._game,\n baseTexture: await this._baseTexturePromise,\n spriteSheetData: data.spriteSheet,\n map: this._map,\n tiles: this._tiles,\n step: this._step,\n scale: this._baseScale,\n level: this._level,\n animated: this._animated,\n renderer: this._renderer,\n lighting: this._lighting,\n isAborted: () => this._container.destroyed,\n });\n }\n\n // Что нужно `layerSeeThrough` — одним значением, без единого поля парта\n // на той стороне. Собирается каждый кадр: `mapSprite` и `_occluder`\n // появляются асинхронно, а `parent` парт получает уже после конструктора\n _seeThroughView() {\n return {\n levelView: this._levelView,\n container: this._container,\n stage: this._container.parent,\n occluder: this._occluder,\n hasSprite: Boolean(this.mapSprite),\n hole: this._hole,\n occluderHole: this._occluderHole,\n level: this._level,\n grid: this._grid,\n tileSet: this._tileSet,\n floorSet: this._floorSet,\n roof: this._roof,\n };\n }\n\n // Каждый кадр у статического слоя: видимость (плита моста) и параллакс —\n // и слоя, и его срезов. Зовётся из колбэка `onRender` парта-диспетчера\n // (`Map.js`): там он кладётся СВОЙСТВОМ, потому что `onRender` у\n // Container — аксессор, а не метод\n render() {\n this._attachOccluder();\n\n // центр камеры считается ОДИН раз за кадр и уходит параметром во всё,\n // что его просит: и прозрачность, и параллакс, и клин рампы работают\n // одним и тем же числом, а не тремя одинаковыми объектами. Считает его\n // сервис (`levelView`), а не слой: иначе дыра в плите ехала бы по\n // свежей проекции, а alpha сущностей — по прошлой\n if (this._levelView) {\n this._levelView.attachStage(this._container.parent, this._renderer);\n }\n\n const camera = this._levelView\n ? this._levelView.camera()\n : cameraCenter(this._container.parent, this._renderer);\n\n // карты освещённости: реальная работа — раз на трансформ сцены\n if (this._lighting) {\n this._lighting.attachStage(this._container.parent, this._renderer);\n this._lighting.render();\n }\n\n if (this._animations) {\n // вывески на крыше гаснут вместе с ней, а не по кругу вокруг игрока\n const roofAlpha =\n this._roof && this._levelView\n ? 1 + (this._levelView.cfg.minAlpha - 1) * this._hole.strength\n : null;\n\n updateLayerAnimations(this._animations, {\n camera,\n levelView: this._levelView,\n screen: this._renderer?.screen,\n roofAlpha,\n });\n }\n\n // прозрачность считает плита моста и любой слой с перекрывателем: у\n // второго объём гаснет уже на уровне игрока\n if (this._level >= 1 || this._occluder) {\n updateSeeThrough(this._seeThroughView(), camera);\n }\n\n if (!this._parallaxK && !this._slices.length) {\n return;\n }\n\n if (!camera) {\n return;\n }\n\n applyParallax(this._layerRoot, camera, this._parallaxK, this._baseScale);\n\n for (let i = 0; i < this._slices.length; i += 1) {\n const slice = this._slices[i];\n\n if (slice.walls) {\n updateWallMesh(slice, camera, volumeConfig.faceBleedPx);\n orderWallMesh(slice, camera);\n } else if (slice.base) {\n updateHeightMesh(slice, camera);\n } else {\n applyParallax(slice.target, camera, slice.k, this._baseScale);\n }\n }\n }\n\n // Перекрыватель кладётся на сцену рядом с партом, а не в него: порядок\n // отрисовки решает zIndex, а внутри парта объём был бы связан со слоем.\n // Сцена появляется позже конструктора (парт добавляют после создания), а\n // экструзия и вовсе асинхронная, поэтому привязка ленивая\n _attachOccluder() {\n const stage = this._container.parent;\n\n if (!this._occluder || !stage || this._occluder.parent === stage) {\n return;\n }\n\n stage.addChild(this._occluder);\n }\n\n // Освобождение слоя. Порядок обязателен: снять фильтры -> снять\n // перекрыватель со сцены -> обнулить текстуры уцелевших срезов -> убрать\n // `mapSprite` из детей. Всё, что обязано пережить `super.destroy` парта,\n // уходит в возвращаемый колбэк: диспетчер зовёт его ПОСЛЕ, иначе\n // последний кадр рендерит источник, у которого `resource === null`\n destroy() {\n const container = this._container;\n\n // анимации первыми: вывески снимают свои источники и эмиссив из\n // сервиса, пока он ещё держит карту; текстуры — в колбэке ниже\n const releaseAnimations = destroyLayerAnimations(this._animations);\n\n this._animations = null;\n\n // ключ карты освещения и вклад в маску этажа снимаются первыми: на нуле\n // счётчика текущего ключа сервис освобождает карту (сначала со сцены)\n if (this._lighting) {\n this._lighting.releaseMap(this._mapKey, this);\n this._lighting = null;\n }\n\n disposeHole(this._hole, container);\n\n // перекрыватель живёт на сцене, а не в парте: снимается вручную —\n // сначала со сцены, потом ресурсы. Запечённую текстуру он делит со\n // слоем, поэтому отдаёт её не он (см. ниже, mapSprite)\n if (this._occluder) {\n const occluder = this._occluder;\n\n this._occluder = null;\n occluder.filters = [];\n disposeHole(this._occluderHole, occluder);\n\n occluder.parent?.removeChild(occluder);\n occluder.destroy({\n children: true,\n texture: false,\n textureSource: false,\n });\n }\n\n // сначала снять со сцены, потом освобождать GPU-ресурс — иначе\n // последний кадр рендерит источник, у которого `resource === null`.\n // Спрайт выводится из-под `children: true`: иначе `super.destroy`\n // уничтожил бы его сам, обнулив `_texture`, и наш вызов с\n // `texture: true` упал бы на `null.destroy()`\n const mapSprite = this.mapSprite;\n const rampTexture = this._rampTexture;\n const wallTextures = this._wallTextures;\n const slices = this._slices;\n\n this.mapSprite = null;\n this._rampTexture = null;\n this._wallTextures = [];\n this._slices = [];\n\n // ссылки на общие источники снимаются ДО их освобождения: даже если\n // какой-то срез переживёт уничтожение (кадр, начатый до смены карты),\n // он нарисуется пустой текстурой, а не мёртвым источником — именно это\n // падало в BindGroup.setResource на смене карты\n for (const slice of slices) {\n if (!slice.target.destroyed) {\n slice.target.texture = Texture.EMPTY;\n }\n }\n\n if (mapSprite) {\n mapSprite.parent?.removeChild(mapSprite);\n }\n\n // обнуление ссылок\n this._baseTexturePromise = null;\n this._assetUrl = null;\n this._map = null;\n this._tiles = null;\n this._floor = null;\n this._tileSet = null;\n this._floorSet = null;\n this._grid = null;\n this._ramps = null;\n this._spriteSheetData = null;\n this._renderer = null;\n this._levelView = null;\n this._game = null;\n\n return () => {\n releaseAnimations();\n\n // срезы объёма делят запечённую текстуру с плоским слоем —\n // освобождает её один владелец, спрайт слоя. Текстуру клина делят его\n // меши, и она тоже своя: источник отдаётся здесь один раз\n if (rampTexture) {\n rampTexture.destroy(true);\n }\n\n // боковые текстуры объёма: своя на тайл, делят их только меши граней\n for (const texture of wallTextures) {\n texture.destroy(true);\n }\n\n if (mapSprite) {\n mapSprite.destroy({\n children: true,\n texture: true,\n textureSource: true,\n });\n }\n\n // Ассет игры — общий ресурс: один тайл-лист делят все слои карты, а\n // b1.png — ещё и все динамические тела ЛЮБОЙ карты.\n // Assets.unload в Pixi 8 не считает ссылки: он уничтожает\n // TextureSource, а тот эмитит change, по которому PixiJS обнуляет\n // BindGroup (BindGroup.onResourceChange), и следующий проход фильтра\n // падает в setResource. Кеш Assets переживает смену карты штатно,\n // поэтому здесь не выгружается ничего.\n };\n }\n}\n\n// Все ли тайлы слоя — крыши его уровня (`game.roofs`, ключ — уровень\n// строкой или числом). Слой, где крыши смешаны с другими тайлами, крышей не\n// считается: предупреждение и обычное поведение\nfunction isRoofLayer(game, level, tiles) {\n const roofs = game?.roofs?.[level] ?? game?.roofs?.[String(level)];\n\n if (!Array.isArray(roofs) || roofs.length === 0 || !tiles?.length) {\n return false;\n }\n\n const set = new Set(roofs);\n const count = tiles.filter(tile => set.has(tile)).length;\n\n if (count > 0 && count < tiles.length) {\n console.warn(\n `MapLayer: level ${level} layer mixes roof tiles with others; a roof needs its own layer`,\n );\n\n return false;\n }\n\n return count === tiles.length;\n}\n","/**\n * Линейная интерполяция\n * @param {number} a\n * @param {number} b\n * @param {number} t - значение от 0 до 1\n */\nexport const lerp = (a, b, t) => a + (b - a) * t;\n\n/**\n * Интерполяция угла в радианах по кратчайшему пути\n * @param {number} a\n * @param {number} b\n * @param {number} t - значение от 0 до 1\n */\nexport const lerpAngle = (a, b, t) => {\n let diff = b - a;\n\n while (diff > Math.PI) {\n diff -= Math.PI * 2;\n }\n\n while (diff < -Math.PI) {\n diff += Math.PI * 2;\n }\n\n return a + diff * t;\n};\n\n/**\n * Ограничивает число в диапазоне [min, max]\n * @param {number} value\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nexport const clamp = (value, min, max) => Math.min(max, Math.max(min, value));\n\n/**\n * Перевод градусов в радианы\n * @param {number} degrees\n * @returns {number}\n */\nexport const degToRad = degrees => degrees * (Math.PI / 180);\n\n/**\n * Перевод радиан в градусы\n * @param {number} radians\n * @returns {number}\n */\nexport const radToDeg = radians => radians * (180 / Math.PI);\n\n/**\n * Нормализует угол в радианах к диапазону [-PI, PI]\n * @param {number} angle\n * @returns {number}\n */\nexport const normalizeAngle = angle => {\n while (angle > Math.PI) {\n angle -= Math.PI * 2;\n }\n\n while (angle < -Math.PI) {\n angle += Math.PI * 2;\n }\n\n return angle;\n};\n\n/**\n * Случайное число в диапазоне [min, max)\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nexport const randomRange = (min, max) => Math.random() * (max - min) + min;\n","import { Container, Ticker } from 'pixi.js';\n\nexport default class BaseEffect extends Container {\n constructor(onCompleteCallback) {\n super();\n\n this._tickListener = null;\n this._isStarted = false;\n this.isComplete = false;\n this.onComplete = onCompleteCallback;\n }\n\n run() {\n if (this.isComplete || this._isStarted) {\n return;\n }\n\n this._isStarted = true;\n this._tickListener = ticker => this._update(ticker.deltaMS);\n Ticker.shared.add(this._tickListener);\n\n this._onEffectStart();\n\n // если эффект не был завершен сразу в _onEffectStart\n // начальный вызов для немедленной отрисовки/установки состояния\n if (!this.isComplete) {\n this._update(0);\n }\n }\n\n // хук для дополнительной логики старта в наследниках\n // по умолчанию ничего не делает, может быть переопределен\n _onEffectStart() {}\n\n // этот метод должен быть переопределен в классах-наследниках\n _update() {}\n\n _completeEffect() {\n if (this.isComplete) {\n return;\n }\n\n this.isComplete = true;\n\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n\n if (typeof this.onComplete === 'function') {\n this.onComplete();\n }\n }\n\n destroy(options) {\n this.isComplete = true;\n\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n\n if (this.parent) {\n this.parent.removeChild(this);\n }\n\n // уничтожение дочерних элементов по умолчанию, но не текстур,\n // т.к. они могут быть общими.\n // children: true идёт после ...options и не переопределяется извне:\n // отключение children оставило бы уже возвращённые в пул частицы\n // висеть в живом контейнере (двойное использование)\n super.destroy({\n texture: false,\n textureSource: false,\n ...options,\n children: true,\n });\n }\n}\n","import { Sprite } from 'pixi.js';\nimport BaseEffect from './BaseEffect.js';\nimport { lerp, randomRange } from 'vimp-engine/lib/math.js';\n\n// Разлёт обломков разрушенного пропа: несколько щепок из `debrisTexture`\n// разлетаются во все стороны, крутятся, тормозят и гаснут. Живёт в мировых\n// координатах, как ImpactEffect: за телом не следует. Разовый — его\n// запускает MapObject только на переходе в состояние «разрушен»\nexport default class DebrisEffect extends BaseEffect {\n // x, y — мировой центр тела; size — размер тела в мировых единицах\n // (разброс стартовых точек); debris — ассет { textures, contentSize }\n constructor(x, y, size, onComplete, debris) {\n super(onComplete);\n\n this.x = x;\n this.y = y;\n\n this._textures = debris.textures;\n this._contentSize = debris.contentSize;\n\n this.config = {\n particleCount: Math.round(randomRange(6, 10)),\n particleMinSize: 1.5, // длина щепки в мире\n particleMaxSize: 4,\n minInitialSpeed: 30,\n maxInitialSpeed: 140,\n maxSpin: 12, // рад/с\n dragCoefficient: 5,\n minLifetime: 1200, // мс\n maxLifetime: 2200,\n fadeOutStart: 0.6,\n color: [0x6b5a44, 0x5a4a38, 0x7a6a55, 0x3f3a33],\n };\n\n this.particlesData = [];\n\n this._createParticles(size / 2);\n }\n\n _createParticles(spread) {\n const { config } = this;\n\n for (let i = 0; i < config.particleCount; i += 1) {\n const angle = Math.random() * Math.PI * 2;\n const speed = randomRange(config.minInitialSpeed, config.maxInitialSpeed);\n const texture =\n this._textures[Math.floor(Math.random() * this._textures.length)];\n const sprite = new Sprite(texture);\n const start = randomRange(0, spread);\n\n sprite.anchor.set(0.5);\n sprite.tint =\n config.color[Math.floor(Math.random() * config.color.length)];\n sprite.scale.set(\n randomRange(config.particleMinSize, config.particleMaxSize) /\n this._contentSize,\n );\n\n this.particlesData.push({\n sprite,\n x: Math.cos(angle) * start,\n y: Math.sin(angle) * start,\n vx: Math.cos(angle) * speed,\n vy: Math.sin(angle) * speed,\n rotation: Math.random() * Math.PI * 2,\n spin: randomRange(-config.maxSpin, config.maxSpin),\n lifetime: randomRange(config.minLifetime, config.maxLifetime),\n age: 0,\n });\n\n this.addChild(sprite);\n }\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n const { config } = this;\n const dt = deltaMs / 1000;\n const damping = Math.max(0, 1 - config.dragCoefficient * dt);\n let active = 0;\n\n for (const particle of this.particlesData) {\n if (particle.age >= particle.lifetime) {\n continue;\n }\n\n particle.age += deltaMs;\n particle.vx *= damping;\n particle.vy *= damping;\n particle.spin *= damping;\n particle.x += particle.vx * dt;\n particle.y += particle.vy * dt;\n particle.rotation += particle.spin * dt;\n\n const progress = particle.age / particle.lifetime;\n const { sprite } = particle;\n\n sprite.position.set(particle.x, particle.y);\n sprite.rotation = particle.rotation;\n sprite.alpha =\n progress < config.fadeOutStart\n ? 1\n : lerp(\n 1,\n 0,\n Math.min(\n (progress - config.fadeOutStart) / (1 - config.fadeOutStart),\n 1,\n ),\n );\n\n if (particle.age >= particle.lifetime) {\n sprite.visible = false;\n } else {\n active += 1;\n }\n }\n\n if (active === 0 && this._isStarted) {\n this._completeEffect();\n }\n }\n\n destroy(options) {\n this.particlesData = [];\n super.destroy(options);\n }\n}\n","// Индексы полей в строках снапшота: порядок задаёт src/config/snapshot.js\n// (`fields` каждой схемы), а хост пишет строки в том же порядке —\n// `TankRow`/`TracerRow`/`BombRow`/`ExplosionRow` в core/src/tanks.rs и\n// core/src/bomb.rs. Правка схемы обязана править и эти константы: кадр\n// позиционен, ошибиться индексом молча очень легко.\n//\n// m1 — танк (hot, интерполируется движком между кадрами)\nexport const M1_X = 0;\nexport const M1_Y = 1;\nexport const M1_ANGLE = 2;\nexport const M1_GUN_ROTATION = 3;\nexport const M1_VX = 4;\nexport const M1_VY = 5;\nexport const M1_ENGINE_LOAD = 6;\nexport const M1_CONDITION = 7;\nexport const M1_SIZE = 8;\nexport const M1_TEAM = 9;\nexport const M1_ANGVEL = 10;\nexport const M1_Z = 11;\nexport const M1_LEVEL = 12;\nexport const M1_VZ = 13;\nexport const M1_PITCH = 14;\nexport const M1_ROLL = 15;\n\n// w1 — трассер выстрела (event)\nexport const W1_START_X = 0;\nexport const W1_START_Y = 1;\nexport const W1_END_X = 2;\nexport const W1_END_Y = 3;\nexport const W1_BODY_X = 4;\nexport const W1_BODY_Y = 5;\nexport const W1_WAS_HIT = 6;\nexport const W1_SHOOTER_ID = 7;\nexport const W1_START_LEVEL = 8;\nexport const W1_END_LEVEL = 9;\n// хвост сверх схемы: якорь попадания, который дописывает клиентское ядро\n// (core/src/client/mod.rs) — по сети он не едет\nexport const W1_ANCHOR = 10;\n\n// w2 — бомба (event)\nexport const W2_X = 0;\nexport const W2_Y = 1;\nexport const W2_ANGLE = 2;\nexport const W2_SIZE = 3;\nexport const W2_TIME = 4;\nexport const W2_OWNER_ID = 5;\nexport const W2_LEVEL = 6;\n\n// w2e — взрыв (event)\nexport const W2E_X = 0;\nexport const W2E_Y = 1;\nexport const W2E_RADIUS = 2;\nexport const W2E_LEVEL = 3;\n\n// c1/c2 — динамика карты (hot): схемы совпадают по форме\nexport const C_X = 0;\nexport const C_Y = 1;\nexport const C_ANGLE = 2;\nexport const C_Z = 3;\nexport const C_LEVEL = 4;\nexport const C_STATE = 5;\nexport const C_VX = 6;\nexport const C_VY = 7;\nexport const C_ANGVEL = 8;\n","import { Sprite, Assets } from 'pixi.js';\nimport { degToRad } from 'vimp-engine/lib/math.js';\nimport { levelZ, renderLevel } from '../../levelZ.js';\nimport { cameraCenter } from '../../camera.js';\nimport { applyParallax } from '../../parallax.js';\nimport {\n lighting as lightingConfig,\n parallax as parallaxConfig,\n} from '../../../config/render.js';\nimport { baseScale } from './tileGrid.js';\nimport DebrisEffect from '../effects/DebrisEffect.js';\nimport {\n C_X,\n C_Y,\n C_ANGLE,\n C_Z,\n C_LEVEL,\n C_STATE,\n} from '../../snapshotFields.js';\n\n// байт `state` строки тела: 0 — цел, 1 — повреждён, 2 — разрушен\nexport const STATE_INTACT = 0;\nexport const STATE_DAMAGED = 1;\nexport const STATE_DESTROYED = 2;\n\n// база zIndex обломков: под танками, на уровне следов (src/client/levelZ.js)\nconst DESTROYED_LAYER = 1;\n// база zIndex разлёта щепок: слой эффектов\nconst DEBRIS_LAYER = 2;\n// пятно копоти крупнее тела: взрыв выжигает и землю вокруг\nconst SCORCH_SCALE = 1.4;\n\n// Динамическое тело карты (ящик, проп): точечная сущность со своим\n// спрайтом, своим `update` и одной alpha на всё тело. Стратегия рисует В\n// КОНТЕЙНЕР парта: ей нужны и дети, и zIndex, и alpha самого парта.\n//\n// Состояние пропа едет байтом `state` строки кадра. Картинки состояний —\n// `img` (цел), `game.imgDamaged`, `game.imgDestroyed`; без картинки\n// повреждённый остаётся на `img`, а разрушенный рисуется процедурной\n// копотью (`scorchTexture`). Все эти текстуры — общие ассеты: ни одна не\n// выгружается (`Assets.unload` запрещён, см. architecture.md)\nexport default class MapObject {\n constructor(container, data, dependencies, imageBase, assets = {}) {\n this._container = container;\n this._renderer = dependencies.renderer;\n this._soundManager = dependencies.soundManager || null;\n this._assets = assets || {};\n\n // где локальный игрок (сервис игры, src/client/levelView.js)\n this._levelView = dependencies.levelView || null;\n\n // ночь (src/client/lighting/): засвет пропа и его тень в лучах фонарей.\n // `_glint` — `{ sprite, mask }`, заводится на первом кадре ночи\n this._lighting = dependencies.lighting?.enabled\n ? dependencies.lighting\n : null;\n this._glint = null;\n\n // масштаб карты держим числами: у тела сам контейнер несёт `data.scale`,\n // а в мир переводит базовый масштаб, а не текущий (его каждый кадр\n // пересчитывает параллакс)\n this._baseScale = baseScale(data.scale);\n\n // тело живёт в мировых координатах: масштаб носит сам контейнер, а\n // параллакс своего уровня добавляется к нему тем же трансформом\n container.scale = data.scale;\n\n this._assetUrl = `${imageBase}${data.img}`;\n this._baseTexturePromise = Assets.load(this._assetUrl);\n\n // картинки состояний пропа (необязательные)\n this._damagedUrl = data.game?.imgDamaged\n ? `${imageBase}${data.game.imgDamaged}`\n : null;\n this._destroyedUrl = data.game?.imgDestroyed\n ? `${imageBase}${data.game.imgDestroyed}`\n : null;\n\n // высота тела: пока ящик падает, уровень отрисовки ведёт она, а не\n // `level` строки (`renderLevel`)\n this._z = data.level || 0;\n this._level = data.level || 0;\n this._layer = Number(data.layer) || 2;\n container.zIndex = levelZ(this._layer, this._level);\n this._rotation = degToRad(data.angle);\n this._width = data.width;\n this._height = data.height;\n this._x = data.position[0];\n this._y = data.position[1];\n\n // мировая позиция тела: alpha ящика считается по ней, а sprite.x живёт\n // в НЕмасштабированных координатах контейнера\n this._worldX = this._x * this._baseScale.x;\n this._worldY = this._y * this._baseScale.y;\n\n // состояние из кадра; `null` — кадра ещё не было: первый кадр со\n // `state = 2` (подключение посреди раунда) показывает обломки сразу,\n // без разлёта и звука\n this._state = null;\n\n this.sprite = null;\n this._scorch = null;\n\n // разлёты щепок — соседи парта на сцене: снимает их сам парт\n this._effects = new Set();\n\n // ящик на мосту обязан гаснуть вместе с плитой и падать с неё видимо:\n // уровень тела меняется на лету (строка `c1`/`c2` везёт `level`),\n // поэтому колбэк нужен ЛЮБОМУ динамическому телу, а не только тому,\n // что родилось наверху\n this.needsRender = Boolean(this._levelView);\n\n this._create();\n }\n\n async _create() {\n try {\n const baseTexture = await this._baseTexturePromise;\n\n // парт мог быть уничтожен, пока грузился ассет: смена карты сносит\n // старые парты в том же тике, в котором создаёт новые\n if (this._container.destroyed) {\n return;\n }\n\n this.sprite = new Sprite(baseTexture);\n\n this.sprite.x = this._x;\n this.sprite.y = this._y;\n this.sprite.width = this._width;\n this.sprite.height = this._height;\n this.sprite.rotation = this._rotation;\n this._container.addChild(this.sprite);\n\n // кадр мог принести состояние раньше базовой текстуры\n if (this._state !== null && this._state !== STATE_INTACT) {\n this._showState(this._state);\n }\n } catch (error) {\n console.error(\n `Failed to create dynamic map with asset ${this._assetUrl}:`,\n error,\n );\n }\n }\n\n // ящик: точечная сущность, ей хватает одной alpha на всё тело. Заодно\n // затемняется, если игрок над ним (единый признак «ниже — темнее»), и\n // висит над землёй на своём уровне — тем же параллаксом, что и плита,\n // на которой стоит\n render() {\n if (!this._levelView || !this.sprite) {\n return;\n }\n\n this._updateGlint();\n this._updateCaster();\n\n const container = this._container;\n\n container.alpha = this._levelView.alphaFor(\n this._level,\n this._worldX,\n this._worldY,\n this._z,\n );\n container.tint = this._levelView.tintFor(this._level);\n\n // ящик на земле сдвига не имеет, но падающий — имеет: у него уровень\n // уже нулевой, а высота ещё нет\n if (!this._level && !this._z) {\n return;\n }\n\n // параллакс ведёт ВЫСОТА, а не уровень: иначе падающий ящик не\n // опускался бы вовсе и «телепортировался» на нижний слой в момент\n // касания — то же правило, что у корпуса танка\n applyParallax(\n container,\n this._levelView.camera() ??\n cameraCenter(container.parent, this._renderer),\n this._z * parallaxConfig.shear,\n this._baseScale,\n );\n }\n\n update(data) {\n if (this.sprite) {\n // `scale` у динамики пересчитывается параллаксом каждый кадр,\n // поэтому в мир переводит базовый масштаб карты, а не текущий\n this.sprite.x = data[C_X] / this._baseScale.x;\n this.sprite.y = data[C_Y] / this._baseScale.y;\n this.sprite.rotation = data[C_ANGLE];\n }\n\n this._worldX = data[C_X];\n this._worldY = data[C_Y];\n this._rotation = data[C_ANGLE];\n\n this._z = data[C_Z] || 0;\n\n // ящик может уехать на мост и упасть с него: уровень едет строкой\n // кадра, и порядок отрисовки обязан ехать за ним. Уровень ОТРИСОВКИ\n // ведёт высота (`renderLevel`): пока тело падает, хост держит `level`\n // тем уровнем, с которого оно сорвалось (`map::step_body_level`)\n const level = renderLevel(data[C_LEVEL], this._z);\n\n if (level !== this._level) {\n this._level = level;\n this._container.zIndex = levelZ(this._drawLayer(), level);\n\n // тело вернулось на землю: сдвиг высоты обязан сняться, иначе ящик\n // так и останется висеть в проекции уровня, с которого упал\n if (!level && !this._z) {\n applyParallax(this._container, null, 0, this._baseScale);\n }\n }\n\n const state = data[C_STATE] || STATE_INTACT;\n\n if (state !== this._state) {\n const firstFrame = this._state === null;\n\n this._state = state;\n this._showState(state);\n\n if (state === STATE_DESTROYED && !firstFrame) {\n this._burst();\n }\n }\n\n this._placeScorch();\n }\n\n // база zIndex по состоянию: обломки лежат под танками\n _drawLayer() {\n return this._state === STATE_DESTROYED ? DESTROYED_LAYER : this._layer;\n }\n\n // целое тело (не копоть): только оно ловит засвет и бросает тень\n _standing() {\n return this._state !== STATE_DESTROYED || Boolean(this._destroyedUrl);\n }\n\n // Засвет: блик на стороне тела, обращённой к сильнейшему источнику.\n // Градиент `glintTexture` режется маской — копией спрайта тела; блик\n // ложится по центру тела поверх него\n _updateGlint() {\n const glints = lightingConfig.glints;\n const asset = this._lighting?.texture('glint');\n // строка кадра везёт угол тела: свет меряется от его центра\n const center = this._localCenter();\n const worldX = center.x * this._baseScale.x;\n const worldY = center.y * this._baseScale.y;\n const reach = Math.max(\n this._width * this._baseScale.x,\n this._height * this._baseScale.y,\n );\n const hit =\n glints?.enabled &&\n asset &&\n this.sprite.visible &&\n this._standing() &&\n this._lighting.isNight() &&\n this._lighting.onScreen(worldX, worldY, this._z, reach)\n ? this._lighting.lightsAt(worldX, worldY, this._level, 1)[0]\n : null;\n\n if (!hit) {\n if (this._glint) {\n this._glint.sprite.visible = false;\n }\n\n return;\n }\n\n if (!this._glint) {\n const sprite = new Sprite(asset.texture);\n const mask = new Sprite();\n\n sprite.anchor.set(0.5);\n sprite.blendMode = 'add';\n sprite.mask = mask;\n this._container.addChild(mask, sprite);\n this._glint = { sprite, mask };\n }\n\n const { sprite, mask } = this._glint;\n const body = this.sprite;\n\n // маска повторяет спрайт тела: текстура состояния, габариты и поворот\n mask.texture = body.texture;\n mask.position.copyFrom(body.position);\n mask.scale.copyFrom(body.scale);\n mask.rotation = body.rotation;\n\n sprite.texture = asset.texture;\n sprite.visible = true;\n sprite.position.set(center.x, center.y);\n sprite.rotation = hit.angle;\n sprite.scale.set(\n (Math.max(this._width, this._height) * glints.size) / asset.contentSize,\n );\n sprite.tint = hit.color;\n sprite.alpha = Math.min(1, glints.intensity * hit.strength);\n }\n\n // тень тела в лучах фонарей: круг с полудиагональю тела вокруг центра.\n // Копоть тени не бросает\n _updateCaster() {\n if (!this._lighting) {\n return;\n }\n\n if (!this._standing()) {\n this._lighting.setCaster(this, null);\n\n return;\n }\n\n const local = this._localCenter();\n\n this._lighting.setCaster(this, {\n x: local.x * this._baseScale.x,\n y: local.y * this._baseScale.y,\n z: this._z,\n level: this._level,\n radius:\n Math.hypot(\n this._width * this._baseScale.x,\n this._height * this._baseScale.y,\n ) / 2,\n });\n }\n\n // картинка и порядок отрисовки состояния\n _showState(state) {\n this._container.zIndex = levelZ(this._drawLayer(), this._level);\n\n if (!this.sprite) {\n return; // покажет `_create`, когда загрузится базовая текстура\n }\n\n // разрушенный без своей картинки — копоть вместо спрайта\n if (state === STATE_DESTROYED && !this._destroyedUrl) {\n this.sprite.visible = false;\n this._showScorch();\n\n return;\n }\n\n if (this._scorch) {\n this._scorch.visible = false;\n }\n\n this.sprite.visible = true;\n\n let url = null;\n\n if (state === STATE_DAMAGED) {\n url = this._damagedUrl;\n } else if (state === STATE_DESTROYED) {\n url = this._destroyedUrl;\n }\n\n this._setTexture(\n url ? Assets.load(url) : this._baseTexturePromise,\n state,\n url ?? this._assetUrl,\n );\n }\n\n async _setTexture(promise, state, url) {\n try {\n const texture = await promise;\n\n // за время загрузки парт мог уйти со сцены, а тело — сменить\n // состояние ещё раз: старая текстура не должна перекрыть новую\n if (this._container.destroyed || !this.sprite || this._state !== state) {\n return;\n }\n\n this.sprite.texture = texture;\n // размер задан в единицах карты, а не в пикселях новой картинки\n this.sprite.width = this._width;\n this.sprite.height = this._height;\n } catch (error) {\n console.error(`Failed to load map object state asset ${url}:`, error);\n }\n }\n\n _showScorch() {\n const scorch = this._assets.scorchTexture;\n\n if (!scorch) {\n return;\n }\n\n if (!this._scorch) {\n const { textures, contentSize } = scorch;\n const sprite = new Sprite(\n textures[Math.floor(Math.random() * textures.length)],\n );\n\n sprite.anchor.set(0.5);\n sprite.rotation = Math.random() * Math.PI * 2;\n sprite.scale.set(\n (Math.max(this._width, this._height) * SCORCH_SCALE) / contentSize,\n );\n\n this._scorch = sprite;\n this._container.addChild(sprite);\n }\n\n this._scorch.visible = true;\n this._placeScorch();\n }\n\n // копоть — по центру тела, в НЕмасштабированных координатах контейнера\n _placeScorch() {\n if (!this._scorch?.visible) {\n return;\n }\n\n const center = this._localCenter();\n\n this._scorch.position.set(center.x, center.y);\n }\n\n // центр тела в координатах контейнера: строка кадра везёт «угол объекта»\n _localCenter() {\n const cos = Math.cos(this._rotation);\n const sin = Math.sin(this._rotation);\n const halfW = this._width / 2;\n const halfH = this._height / 2;\n\n return {\n x: this._worldX / this._baseScale.x + cos * halfW - sin * halfH,\n y: this._worldY / this._baseScale.y + sin * halfW + cos * halfH,\n };\n }\n\n // разовый разлёт щепок и треск — только на переходе в «разрушен»\n _burst() {\n const local = this._localCenter();\n const x = local.x * this._baseScale.x;\n const y = local.y * this._baseScale.y;\n\n this._soundManager?.registerSound('propBreak', { position: { x, y } });\n\n const parent = this._container.parent;\n const debris = this._assets.debrisTexture;\n\n if (!parent || !debris) {\n return;\n }\n\n const size = Math.max(\n this._width * this._baseScale.x,\n this._height * this._baseScale.y,\n );\n const effect = new DebrisEffect(\n x,\n y,\n size,\n () => {\n this._effects.delete(effect);\n effect.destroy();\n },\n debris,\n );\n\n effect.zIndex = levelZ(DEBRIS_LAYER, this._level);\n parent.addChild(effect);\n this._effects.add(effect);\n effect.run();\n }\n\n // спрайты тела (и копоть) — обычные дети контейнера: их освобождает\n // `super.destroy` парта (`children: true`). Разлёты — соседи на сцене,\n // их снимает сам парт. Здесь снимаются только ссылки\n destroy() {\n for (const effect of this._effects) {\n effect.destroy();\n }\n\n this._effects.clear();\n // тень в лучах — состояние сессии сервиса освещения; блик и маска —\n // дети контейнера, их снимет `super.destroy` парта\n this._lighting?.setCaster(this, null);\n this._lighting = null;\n this._glint = null;\n this.sprite = null;\n this._scorch = null;\n this._baseTexturePromise = null;\n this._assetUrl = null;\n this._renderer = null;\n this._levelView = null;\n this._soundManager = null;\n }\n}\n","import { Container } from 'pixi.js';\nimport MapLayer from './map/MapLayer.js';\nimport MapObject from './map/MapObject.js';\n\n// Парт карты — диспетчер двух стратегий. Движок отдаёт статические слои\n// (`s0..sN`) и динамические тела (`d0..dN`) в ОДИН и тот же список имён\n// (`gameSets[setId]`, src/config/client.js), поэтому имя парта одно на оба\n// вида данных, а разделяются они внутри: `MapLayer` — запечённый слой,\n// его объём и клин рампы, `MapObject` — точечное тело (ящик).\n//\n// Парт остаётся `Container`: движок создаёт его как\n// `new Part(data, assets, dependencies, context)` и кладёт результат на\n// сцену — подменить экземпляр из конструктора нельзя. Стратегия рисует В\n// ЭТОТ контейнер: ей нужны и дети, и zIndex, и alpha, и filters самого парта\nexport default class Map extends Container {\n constructor(data, assets, dependencies) {\n super();\n\n this._mode = null;\n\n // База ассетов активной игры — движок отдаёт её сервисом assetsBase\n // (объявлен в componentDependencies, src/config/client.js). Картинки\n // карт везёт сам пакет игры: assets/img/ -> dist/img/, а движок ни одного\n // игрового файла не раздаёт. Без базы вышел бы запрос на\n // \"undefinedimg/tiles.png\" — полотно осталось бы пустым без единой\n // ошибки, поэтому промах ловим здесь и вслух.\n //\n // Логируем, а не бросаем: конструктор вызывается из рендер-тика\n // (renderTick -> applyGameData -> GameCtrl.parse -> фабрика), где на всём\n // пути нет ни одного try/catch — исключение оборвало бы создание всех\n // остальных сущностей этого кадра. Тот же паттерн «громко в лог, ничего\n // наружу», что у самих стратегий.\n if (typeof dependencies.assetsBase !== 'string') {\n console.error(\n 'Map: сервис assetsBase недоступен — карта останется пустой. ' +\n 'Объявите assetsBase в componentDependencies и запустите игру ' +\n 'на vimp-engine >= 0.9.0',\n );\n\n return;\n }\n\n const imageBase = `${dependencies.assetsBase}img/`;\n\n this._mode =\n data.type === 'dynamic'\n ? new MapObject(this, data, dependencies, imageBase, assets)\n : new MapLayer(this, data, dependencies, imageBase, assets);\n\n // `onRender` у Container — АКСЕССОР, а не метод: назначается только\n // свойством. Метод с этим именем на прототипе подкласса затенил бы\n // сеттер, PixiJS не позвал бы ничего и фича умерла бы молча.\n // (в конструкторе renderGroup ещё null, но RenderGroup.addChild сам\n // подхватит `_onRender` при добавлении на сцену)\n if (this._mode.needsRender) {\n this.onRender = () => this._mode.render();\n }\n }\n\n update(data) {\n this._mode?.update?.(data);\n }\n\n destroy(options) {\n // стратегия освобождает своё ДО `super.destroy` и возвращает то, что\n // обязано пережить его: GPU-ресурсы отдаются только после того, как\n // парт снят со сцены\n const released = this._mode?.destroy();\n\n this._mode = null;\n\n super.destroy({\n children: true,\n texture: false,\n textureSource: false,\n ...options,\n });\n\n released?.();\n }\n}\n","// Палитра уровней 2.5D: один цвет на уровень, общий для схемы карты\n// (`MapRadar`) и отметки чужого танка (`TankRadar`). Длина — `MAX_LEVELS`\n// движка (vimp_engine_core::map::MAX_LEVELS = 8): уровень вне палитры\n// означал бы карту, которую движок и не принял бы.\nexport const LEVEL_COLORS = [\n 0xffffff, // 0 — земля\n 0x8fb7ff, // 1\n 0x9fe0c0, // 2\n 0xf0d78c, // 3\n 0xe0a0d0, // 4\n 0xa0d8f0, // 5\n 0xf0b08c, // 6\n 0xc0c0d8, // 7\n];\n\nexport const levelColor = level => LEVEL_COLORS[level] ?? LEVEL_COLORS[0];\n","import { Container, Graphics } from 'pixi.js';\nimport { levelZ } from '../levelZ.js';\nimport { levelColor } from '../levelColors.js';\n\n// базовый zIndex схемы карты внутри своего уровня\nconst MAP_RADAR_BASE_Z = 2;\n\n// прозрачность уровней, отличных от уровня игрока: на схеме важнее всего,\n// где он сам, а соседние этажи остаются подсказкой, а не мешаниной\nconst OTHER_LEVEL_ALPHA = 0.35;\n\nexport default class MapRadar extends Container {\n constructor(data, _assets, dependencies = {}) {\n super();\n\n // сохраняем необходимые данные для схематичной карты\n this._map = data.map;\n this._level = data.level || 0;\n // solid — препятствия своего уровня (у земли это physicsStatic,\n // у эстакады — её перила)\n this._solid = data.solid || data.physicsStatic || [];\n this._tiles = data.tiles || [];\n this._step = data.step;\n this.zIndex = levelZ(MAP_RADAR_BASE_Z, this._level);\n this.scale = data.scale;\n\n // рисуем стены только тем слоем, который эти стены и показывает:\n // иначе каждый рендер-слой карты дублировал бы одну и ту же графику\n this._draws = this._tiles.some(tile => this._solid.includes(tile));\n\n this.radarGraphics = new Graphics();\n this.addChild(this.radarGraphics);\n\n // уровень игрока: чужие этажи на схеме гаснут (задача 3 мастер-плана).\n // `onRender` — аксессор Container, назначается свойством; слою, который\n // ничего не рисует, колбэк не нужен\n this._levelView = dependencies.levelView || null;\n\n if (this._levelView && this._draws) {\n this.onRender = () => {\n this.alpha =\n this._levelView.level === this._level ? 1 : OTHER_LEVEL_ALPHA;\n };\n }\n\n // создаем схематичную карту\n this.createRadarMap();\n }\n\n createRadarMap() {\n if (!this._draws) {\n return;\n }\n\n const graphics = this.radarGraphics;\n\n graphics.clear();\n\n if (Array.isArray(this._map)) {\n for (let y = 0; y < this._map.length; y += 1) {\n const row = this._map[y];\n\n if (Array.isArray(row)) {\n for (let x = 0; x < row.length; x += 1) {\n const tileType = row[x];\n\n // проверяем, является ли текущий тайл препятствием (physicsStatic)\n if (this._solid.includes(tileType)) {\n graphics.rect(\n x * this._step,\n y * this._step,\n this._step / 2,\n this._step / 2,\n );\n }\n }\n }\n }\n }\n\n graphics.fill(levelColor(this._level));\n }\n\n update() {}\n\n destroy(options) {\n super.destroy({\n children: true,\n texture: false,\n textureSource: false,\n ...options,\n });\n\n this.radarGraphics = null;\n this._map = null;\n this._solid = null;\n this._tiles = null;\n }\n}\n","import { Container, Graphics, Sprite } from 'pixi.js';\nimport { levelZ } from '../levelZ.js';\nimport { levelColor } from '../levelColors.js';\nimport {\n M1_X,\n M1_Y,\n M1_CONDITION,\n M1_TEAM,\n M1_LEVEL,\n} from '../snapshotFields.js';\n\n// базовый zIndex маркера на радаре внутри своего уровня\nconst TANK_RADAR_BASE_Z = 2;\n\n// кольцо уровня вокруг маркера: радиус и толщина в тех же единицах,\n// что текстуры tankRadarTexture (см. bakedAssets в src/config/client.js)\nconst LEVEL_RING_RADIUS = 9;\nconst LEVEL_RING_WIDTH = 1.5;\n\nexport default class TankRadar extends Container {\n constructor(data, assets) {\n super();\n\n // 2.5D: маркер танка с эстакады лежит над маркерами земли, а его уровень\n // читается кольцом в цвете уровня (та же палитра, что у схемы карты)\n this._level = data[M1_LEVEL] || 0;\n this.zIndex = levelZ(TANK_RADAR_BASE_Z, this._level);\n\n this._textures = assets.tankRadarTexture;\n\n this.body = new Sprite();\n this.body.anchor.set(0.5);\n\n this.levelRing = new Graphics();\n\n this.addChild(this.levelRing, this.body);\n this._drawLevelRing();\n\n // параметры с сервера:\n // [x, y, rotation, gunRotation, vX, vY,\n // engineLoad, condition, size, teamId]\n this.x = data[M1_X] || 0;\n this.y = data[M1_Y] || 0;\n this._condition = data[M1_CONDITION];\n this._teamId = data[M1_TEAM];\n\n // масштаб контейнера\n this.scale.set(5, 5);\n\n this.create();\n }\n\n // земля кольца не получает: значок нужен там, где уровень неочевиден\n _drawLevelRing() {\n this.levelRing.clear();\n\n if (this._level > 0) {\n this.levelRing\n .circle(0, 0, LEVEL_RING_RADIUS)\n .stroke({ width: LEVEL_RING_WIDTH, color: levelColor(this._level) });\n }\n }\n\n create() {\n // если танк уничтожен\n if (this._condition === 0) {\n this.body.texture = this._textures.destroyed;\n } else {\n // определение текстуры в зависимости от команды\n if (this._teamId === 1) {\n this.body.texture = this._textures.liveTeamId1;\n } else if (this._teamId === 2) {\n this.body.texture = this._textures.liveTeamId2;\n }\n }\n }\n\n update(data) {\n this.x = data[M1_X];\n this.y = data[M1_Y];\n\n const level = data[M1_LEVEL] || 0;\n\n if (level !== this._level) {\n this._level = level;\n this.zIndex = levelZ(TANK_RADAR_BASE_Z, level);\n this._drawLevelRing();\n }\n\n const newCondition = data[M1_CONDITION];\n const teamId = data[M1_TEAM];\n let needsVisualChange = false;\n\n if (newCondition !== undefined && newCondition !== this._condition) {\n this._condition = newCondition;\n needsVisualChange = true;\n }\n\n if (teamId !== undefined && teamId !== this._teamId) {\n this._teamId = teamId;\n needsVisualChange = true;\n }\n\n // если визуальное представление требуется изменить\n if (needsVisualChange) {\n this.create();\n }\n }\n\n destroy(options) {\n super.destroy({\n children: true,\n texture: false, // текстуры общие\n textureSource: false,\n ...options,\n });\n\n this.body = null;\n this.levelRing = null;\n this._textures = null;\n }\n}\n","// Наклон корпуса в 2.5D: углы квада спрайта, повёрнутого по тангажу и\n// крену. Отдельный модуль, потому что формула нужна трём мешам танка\n// (корпус, пушка, остов) и обязана быть проверяемой без PixiJS.\n//\n// Наклон — поворот лежащего в плоскости карты квада вокруг локальных осей\n// корпуса с последующей ортографической проекцией ТОЙ ЖЕ формулой высоты,\n// что и весь 2.5D (`src/client/parallax.js`): точка, поднявшаяся на высоту\n// `h`, отъезжает от центра на `h * shear`.\n\n/**\n * Углы квада спрайта, наклонённого по тангажу и крену.\n *\n * @param {object} p\n * @param {number} p.width ширина спрайта в экранных единицах\n * @param {number} p.height высота спрайта\n * @param {number} p.anchorX 0..1\n * @param {number} p.anchorY 0..1\n * @param {number} p.rotation собственный поворот спрайта (пушка), рад\n * @param {number} p.heading курс корпуса (поворот контейнера танка),\n * рад: нужен, чтобы `lift` уводил край вверх по ЭКРАНУ, а не по\n * локальной оси танка\n * @param {number} p.pitch продольный наклон, рад (> 0 — нос `+u`\n * поднят, как у `motion::tilt_target`)\n * @param {number} p.roll поперечный наклон, рад (> 0 — поднят борт\n * `+v`, то есть сторона `(−sin, cos)` курса в экранных осях y-вниз:\n * туда смотрит `lat` в `motion::tilt_target`)\n * @param {number} p.shear parallax.shear\n * @param {number} p.lift экранный подъём поднявшегося края, доля\n * его высоты\n * @returns {number[]} [x0,y0, x1,y1, x2,y2, x3,y3] — левый верх, правый\n * верх, правый низ, левый низ: порядок, которого ждёт\n * `PerspectiveMesh.setCorners`\n */\nexport function tiltCorners({\n width,\n height,\n anchorX,\n anchorY,\n rotation,\n heading = 0,\n pitch,\n roll,\n shear,\n lift,\n}) {\n const u0 = -anchorX * width;\n const u1 = (1 - anchorX) * width;\n const v0 = -anchorY * height;\n const v1 = (1 - anchorY) * height;\n\n // обход по часовой: левый верх → правый верх → правый низ → левый низ\n const quad = [\n [u0, v0],\n [u1, v0],\n [u1, v1],\n [u0, v1],\n ];\n\n const cosR = Math.cos(rotation);\n const sinR = Math.sin(rotation);\n const cosP = Math.cos(pitch);\n const sinP = Math.sin(pitch);\n const cosB = Math.cos(roll);\n const sinB = Math.sin(roll);\n\n // экранное «вверх» (0, −1) в локальных осях контейнера, повёрнутого на\n // курс: поворот на −heading даёт (−sin, −cos)\n const upU = -Math.sin(heading);\n const upV = -Math.cos(heading);\n\n const out = [];\n\n for (const [su, sv] of quad) {\n // собственный поворот спрайта (пушка): наклон идёт по осям КОРПУСА,\n // поэтому поворот применяется первым\n const u = su * cosR - sv * sinR;\n const v = su * sinR + sv * cosR;\n\n // тангаж вокруг локальной оси `v` (поперёк корпуса): нос — `+u`, как\n // у ядра, фар и текстуры корпуса (она вытянута по `x`)\n const up = u * cosP;\n const h1 = u * sinP;\n\n // крен вокруг локальной оси `u` (вдоль корпуса): при `roll > 0`\n // поднимается борт `+v`\n const vp = v * cosB;\n const h2 = v * sinB;\n\n // высота точки в ЭКРАННЫХ единицах и её доля в высоте спрайта,\n // переведённая в тот же безразмерный сдвиг, которым живёт весь 2.5D\n const h = h1 + h2;\n const k = height ? (h / height) * shear : 0;\n\n // `lift` — насколько поднявшаяся часть корпуса уезжает вверх по\n // экрану: без него наклон читается только сжатием и выглядит как\n // «сплющивание». Сдвиг — вдоль экранного верха, а не локального `−v`,\n // иначе он поворачивался бы вместе с танком\n const shift = h * lift;\n\n out.push(up * (1 + k) + upU * shift, vp * (1 + k) + upV * shift);\n }\n\n return out;\n}\n\n/**\n * Множитель яркости корпуса по его наклону. Наклон поворачивает нормаль\n * корпуса; её проекция на направление света и есть яркость. Функция\n * чистая и не знает про PixiJS — числа приходят параметрами.\n *\n * Эффект ОДНОСТОРОННИЙ: наклон только затемняет. Подсветки поверх белого\n * тинта в 8 битах нет (см. хвост функции).\n *\n * @param {object} p\n * @param {number} p.angle курс корпуса, рад\n * @param {number} p.pitch продольный наклон, рад\n * @param {number} p.roll поперечный наклон, рад\n * @param {number[]} p.lightDir направление света в экранных осях\n * @param {number} p.shading глубина эффекта (0 — выключено)\n * @returns {number} множитель яркости, 1 - shading .. 1\n */\nexport function tiltShade({ angle, pitch, roll, lightDir, shading }) {\n // без направления света считать нечего, а Math.hypot(undefined) дал бы\n // NaN и молча сломал тинт корпуса в `scaleTint`\n if (!shading || !lightDir) {\n return 1;\n }\n\n // наклон корпуса = поворот его нормали: тангаж (вокруг поперечной оси\n // `v`) даёт составляющую вдоль курса, крен (вокруг `u`) — поперёк\n const cos = Math.cos(angle);\n const sin = Math.sin(angle);\n\n // нормаль в экранных осях (её вертикальная составляющая нам не нужна:\n // свет задан в плоскости экрана)\n // поднятый нос `+u` отклоняет нормаль к корме `−u`, поднятый борт\n // `+v` — к борту `−v` (те же оси, что у `tiltCorners`)\n const nx = -Math.sin(pitch) * cos + Math.sin(roll) * sin;\n const ny = -Math.sin(pitch) * sin - Math.sin(roll) * cos;\n\n const len = Math.hypot(lightDir[0], lightDir[1]) || 1;\n const dot = (nx * lightDir[0] + ny * lightDir[1]) / len;\n\n // dot ∈ [−1, 1] — проекция наклона нормали на свет. Ровный корпус даёт\n // dot = 0 и множитель ровно 1: плоская карта обязана выглядеть как\n // раньше. Наклон ОТ света затемняет; наклон НАВСТРЕЧУ свету не\n // подсвечивает, а только снимает затемнение — подсветки поверх белого\n // тинта в 8 битах всё равно нет: тинт уровня для своего и верхних\n // ярусов — 0xffffff (`src/client/seeThrough.js`), и множитель > 1\n // упирается в потолок канала, то есть пропадает ровно у того танка,\n // ради которого эффект и заведён\n return 1 - Math.max(0, -dot) * shading;\n}\n\n/**\n * Умножает тинт `0xRRGGBB` на скаляр поканально. Нужен, чтобы светотень\n * наклона легла ПОВЕРХ тинта уровня, а не заменила его.\n *\n * @param {number} tint исходный цвет 0xRRGGBB\n * @param {number} factor множитель яркости\n * @returns {number} новый цвет 0xRRGGBB\n */\nexport function scaleTint(tint, factor) {\n const channel = shift => {\n const value = Math.round(((tint >> shift) & 0xff) * factor);\n\n return Math.min(0xff, Math.max(0, value));\n };\n\n return (channel(16) << 16) | (channel(8) << 8) | channel(0);\n}\n","// Детектор касания земли по вертикальной скорости кадра. Общий для\n// `Tank.js` (просадка корпуса) и `Dust.js` (всплеск пыли и звук): оба\n// парта получают ОДНУ И ТУ ЖЕ строку снапшота, поэтому связывать их\n// колбэком не нужно, а держать две копии правила — нельзя: разъедутся\n// молча, и просадка окажется без пыли.\n//\n// `vz` в схеме снапшота объявлен `discrete` (`src/config/snapshot.js`)\n// именно ради этого детектора: сглаженная выборка точного нуля не даёт.\n//\n// Конфиг модуль НЕ импортирует (как и `tilt.js`): пороги приходят\n// параметром, и функция остаётся чистой — тест проверяет формулу, а не\n// настройку.\n\n/**\n * Сила удара при касании, 0 — касания не было.\n *\n * @param {number} prevVz вертикальная скорость прошлого кадра\n * @param {number} vz вертикальная скорость этого кадра\n * @param {object} p\n * @param {number} p.minImpact порог мягкого касания (уровней/с)\n * @param {number} p.fullImpact скорость полной силы удара (уровней/с)\n * @returns {number} 0..1 — доля от `fullImpact`\n */\nexport function landingImpact(prevVz, vz, { minImpact, fullImpact }) {\n // касание: снижение было заметным, а в этом кадре скорость обнулилась.\n // Условие вывернуто (`!(prevVz < …)` вместо `prevVz >= …`) ради NaN в\n // коротком ряду: сравнение с NaN ложно, и детектор молча даёт 0\n if (!(prevVz < -minImpact) || vz !== 0) {\n return 0;\n }\n\n return Math.min(1, -prevVz / fullImpact);\n}\n","// Визуальная отдача после выстрела: только рендер — физика и ядро её не\n// знают. Модуль чистый: время и конфиг приходят параметрами, PixiJS не\n// нужен.\n\n/**\n * Амплитуда отдачи 0..1 через `elapsed` мс после выстрела: быстрый рост\n * до 1 за долю `attack` длительности, затем квадратичный возврат к 0.\n *\n * @param {number} elapsed мс с выстрела\n * @param {number} duration полная длительность, мс\n * @param {number} attack доля длительности на рост (0..1)\n * @returns {number}\n */\nexport function recoilAmount(elapsed, duration, attack) {\n if (!(duration > 0) || elapsed < 0 || elapsed >= duration) {\n return 0;\n }\n\n const t = elapsed / duration;\n\n if (t < attack) {\n return t / attack;\n }\n\n const back = (t - attack) / (1 - attack);\n\n return (1 - back) * (1 - back);\n}\n\n/**\n * Смещения корпуса и башни в осях КОРПУСА. Отдача идёт против ствола:\n * башня и корпус уезжают назад, а сторона ствола приподнимается — знаки\n * те же, что в `tiltCorners` (`pitch > 0` поднимает нос `+u`, `roll > 0` —\n * борт `+v`).\n *\n * @param {object} p\n * @param {number} p.amount `recoilAmount`\n * @param {number} p.gunRotation поворот башни относительно корпуса, рад\n * @param {object} p.config `recoil` из src/config/render.js\n * @returns {{ gun: {x: number, y: number}, body: {x: number, y: number},\n * pitch: number, roll: number }}\n */\nexport function recoilOffsets({ amount, gunRotation, config }) {\n const cos = Math.cos(gunRotation);\n const sin = Math.sin(gunRotation);\n const gun = config.gunKick * amount;\n const body = config.bodyKick * amount;\n const rock = config.rock * amount;\n\n return {\n gun: { x: -cos * gun, y: -sin * gun },\n body: { x: -cos * body, y: -sin * body },\n pitch: rock * cos,\n roll: rock * sin,\n };\n}\n","// Визуальная реакция танка на взрыв: только рендер — физика и ядро её не\n// знают. Модуль чистый: время, конфиг и «случай» приходят параметрами,\n// PixiJS не нужен.\n\nconst TAU = Math.PI * 2;\n\n// частоты встряски, рад/мс (≈14 и ≈18 Гц): несоизмеримы, поэтому сдвиг не\n// ходит по ровной линии\nconst SHAKE_FREQ_X = 0.09;\nconst SHAKE_FREQ_Y = 0.113;\n\n/**\n * Сила взрыва по расстоянию: та же спадающая, что у урона в ядре\n * (`TanksSim::explode`, `1 − d / radius`), вне радиуса — 0.\n *\n * @param {number} distance\n * @param {number} radius\n * @returns {number} 0..1\n */\nexport function blastStrength(distance, radius) {\n if (!(radius > 0) || distance >= radius) {\n return 0;\n }\n\n return 1 - distance / radius;\n}\n\n/**\n * Задел ли взрыв танк и как. null — не задел: другой уровень (плита моста\n * экранирует, как маски ядра) или вне радиуса.\n *\n * @param {object} p\n * @param {number} p.x мировая точка танка\n * @param {number} p.y\n * @param {number} p.level физический уровень танка\n * @param {number} p.heading курс корпуса, рад\n * @param {number} p.hullLength длина корпуса, мировые единицы\n * @param {{x: number, y: number, radius: number, level?: number}} p.blast\n * @param {object} p.config `blastJolt` из src/config/render.js\n * @param {() => number} [p.rng]\n * @returns {null | { strength: number, dirU: number, dirV: number,\n * hop: boolean, phaseX: number, phaseY: number }} `dirU/dirV` —\n * направление на взрыв в осях корпуса: эта сторона и подлетает\n */\nexport function blastKick({\n x,\n y,\n level,\n heading,\n hullLength,\n blast,\n config,\n rng = Math.random,\n}) {\n if ((blast.level || 0) !== (level || 0)) {\n return null;\n }\n\n const dx = blast.x - x;\n const dy = blast.y - y;\n const distance = Math.hypot(dx, dy);\n const strength = blastStrength(distance, blast.radius);\n\n if (!strength) {\n return null;\n }\n\n // под корпусом направление на взрыв вырождено — корпус подбрасывает и\n // кренит в случайную сторону\n const hop = distance < config.underShare * hullLength;\n const angle = hop ? rng() * TAU : Math.atan2(dy, dx) - heading;\n\n return {\n strength,\n dirU: Math.cos(angle),\n dirV: Math.sin(angle),\n hop,\n phaseX: rng() * TAU,\n phaseY: rng() * TAU,\n };\n}\n\n/**\n * Состояние реакции через `elapsed` мс после взрыва.\n *\n * - крен: сторона к взрыву подлетает, затем корпус качается на подвеске —\n * затухающее колебание `e^(−t/decay)·cos(2π·wobbleHz·t)`; знаки как в\n * `tiltCorners` (`pitch > 0` — нос `+u`, `roll > 0` — борт `+v`);\n * - подброс (взрыв под корпусом): видимая высота `hop·s·sin(π·t/hopDuration)`,\n * уровни — прибавляется к высоте в проекции;\n * - встряска: короткий сдвиг корпуса, затухает линейно за `shakeDuration`.\n *\n * @param {object} p\n * @param {number} p.elapsed мс со взрыва\n * @param {object} p.kick `blastKick`\n * @param {object} p.config `blastJolt`\n * @returns {{ pitch: number, roll: number, lift: number, shakeX: number,\n * shakeY: number, envelope: number, done: boolean }}\n */\nexport function blastJoltState({ elapsed, kick, config }) {\n const strength = kick.strength;\n const envelope = config.decay > 0 ? Math.exp(-elapsed / config.decay) : 0;\n const wobble = envelope * Math.cos(TAU * config.wobbleHz * (elapsed / 1000));\n const rock = config.rock * strength * wobble;\n\n const hopping = kick.hop && elapsed < config.hopDuration;\n const lift = hopping\n ? config.hop * strength * Math.sin((Math.PI * elapsed) / config.hopDuration)\n : 0;\n\n const shaking = elapsed < config.shakeDuration;\n const shake = shaking\n ? config.shake * strength * (1 - elapsed / config.shakeDuration)\n : 0;\n\n return {\n pitch: rock * kick.dirU,\n roll: rock * kick.dirV,\n lift,\n shakeX: shake * Math.sin(elapsed * SHAKE_FREQ_X + kick.phaseX),\n shakeY: shake * Math.sin(elapsed * SHAKE_FREQ_Y + kick.phaseY),\n envelope: strength * envelope,\n done: elapsed >= config.duration && !hopping,\n };\n}\n","import { tankModel as tankModelConfig } from '../../config/render.js';\n\n// Низкополигональная модель танка. Оси корпуса: `u` — вперёд (нос, `+x`\n// текстуры), `v` — `+y` текстуры, `w` — вверх; те же, что у `tiltCorners` и\n// света корпуса. Единицы — пиксели рисунка корпуса при базовом size 10\n// (`src/client/bakers/tankTexture.js`): верх модели совпадает с рисунком.\n//\n// Грань — полигон по индексам вершин, обход против часовой стрелки при\n// взгляде СНАРУЖИ (в осях u-v-w — правая тройка): нормаль по правилу\n// правой руки смотрит наружу, а видимость в проекции сверху — знак площади.\n\n// восьмигранник башни из рисунка (`tankTexture.js`), доли размера 10\nconst TURRET = [\n [1.33, -0.42],\n [0.42, -1],\n [-0.42, -1],\n [-1.33, -0.42],\n [-1.33, 0.42],\n [-0.42, 1],\n [0.42, 1],\n [1.33, 0.42],\n].map(([u, v]) => [u * 10, v * 10]);\n\n// порядок частей при равной высоте грани: ниже — раньше\nexport const PART_ORDER = { track: 0, hull: 1, turret: 2, barrel: 3 };\n\n// удвоенная знаковая площадь многоугольника в плоскости u-v\nexport function signedArea(points) {\n let area = 0;\n\n for (let i = 0; i < points.length; i += 1) {\n const [x0, y0] = points[i];\n const [x1, y1] = points[(i + 1) % points.length];\n\n area += x0 * y1 - x1 * y0;\n }\n\n return area;\n}\n\n// нормаль полигона методом Ньюэлла (нормированная)\nexport function polygonNormal(points) {\n let x = 0;\n let y = 0;\n let z = 0;\n\n for (let i = 0; i < points.length; i += 1) {\n const [u0, v0, w0] = points[i];\n const [u1, v1, w1] = points[(i + 1) % points.length];\n\n x += (v0 - v1) * (w0 + w1);\n y += (w0 - w1) * (u0 + u1);\n z += (u0 - u1) * (v0 + v1);\n }\n\n const length = Math.hypot(x, y, z) || 1;\n\n return [x / length, y / length, z / length];\n}\n\nconst rect = (u0, u1, v0, v1) => [\n [u0, v0],\n [u1, v0],\n [u1, v1],\n [u0, v1],\n];\n\nconst scalePolygon = (points, factor) =>\n points.map(([u, v]) => [u * factor, v * factor]);\n\n/**\n * Модель танка: вершины `[u, v, w]` и грани `{ indices, part, material,\n * kind }`, где `kind` — `top | side | bottom`, `material` — имя области\n * атласа (этап 2 плана).\n *\n * @param {object} [config] `tankModel` из src/config/render.js\n * @returns {{ vertices: number[][], faces: object[] }}\n */\nexport function createTankModel(config = tankModelConfig) {\n const vertices = [];\n const faces = [];\n\n // призма: низ `bottom` на высоте `w0`, верх `top` (столько же точек) на\n // `w1`. Обход приводится к положительной площади — бока смотрят наружу\n const prism = ({ bottom, top, w0, w1, part, topMaterial, sideMaterial }) => {\n let lower = bottom;\n let upper = top;\n\n if (signedArea(lower) < 0) {\n lower = [...lower].reverse();\n upper = [...upper].reverse();\n }\n\n const base = vertices.length;\n const count = lower.length;\n\n for (const [u, v] of lower) {\n vertices.push([u, v, w0]);\n }\n\n for (const [u, v] of upper) {\n vertices.push([u, v, w1]);\n }\n\n const low = i => base + i;\n const high = i => base + count + i;\n\n faces.push({\n indices: upper.map((_, i) => high(i)),\n part,\n material: topMaterial,\n kind: 'top',\n });\n\n faces.push({\n indices: lower.map((_, i) => low(count - 1 - i)),\n part,\n material: 'bottom',\n kind: 'bottom',\n });\n\n for (let i = 0; i < count; i += 1) {\n const j = (i + 1) % count;\n\n faces.push({\n indices: [low(i), low(j), high(j), high(i)],\n part,\n material: sideMaterial,\n kind: 'side',\n });\n }\n };\n\n // гусеницы по бортам\n for (const [v0, v1] of [\n [-15, -9],\n [9, 15],\n ]) {\n const plan = rect(-20, 20, v0, v1);\n\n prism({\n bottom: plan,\n top: plan,\n w0: 0,\n w1: config.trackHeight,\n part: 'track',\n topMaterial: 'trackTop',\n sideMaterial: 'trackSide',\n });\n }\n\n // корпус: скаты от низа к палубе — фаски рисунка, спереди — пологий\n // лобовой лист (палуба кончается на u = 14, низ — на u = 18)\n prism({\n bottom: rect(-18, 18, -9, 9),\n top: rect(-16, 14, -7, 7),\n w0: config.hullBase,\n w1: config.hullTop,\n part: 'hull',\n topMaterial: 'hullTop',\n sideMaterial: 'hullSide',\n });\n\n // башня: скаты — кольцо фаски рисунка (верх — тот же восьмигранник × 0.6)\n prism({\n bottom: TURRET,\n top: scalePolygon(TURRET, 0.6),\n w0: config.turretBase,\n w1: config.turretTop,\n part: 'turret',\n topMaterial: 'turretTop',\n sideMaterial: 'turretSide',\n });\n\n // ствол и дульный тормоз — брусы на высоте ствола\n const barrel = rect(2.5, 21, -2.5, 2.5);\n\n prism({\n bottom: barrel,\n top: barrel,\n w0: config.barrelHeight - config.barrelRadius,\n w1: config.barrelHeight + config.barrelRadius,\n part: 'barrel',\n topMaterial: 'barrel',\n sideMaterial: 'barrel',\n });\n\n const brake = rect(21, 24.5, -3.6, 3.6);\n\n prism({\n bottom: brake,\n top: brake,\n w0: config.barrelHeight - config.brakeRadius,\n w1: config.barrelHeight + config.brakeRadius,\n part: 'barrel',\n topMaterial: 'brake',\n sideMaterial: 'brake',\n });\n\n return { vertices, faces };\n}\n","import { polygonNormal } from './model.js';\n\n// Поза модели: поворот башни, откат ствола, тангаж и крен всего танка,\n// встряска. Чистая функция — PixiJS не нужен. Курс сюда не входит: его даёт\n// поворот контейнера `Tank`, а модель живёт в осях корпуса.\n\nconst TURRET_PARTS = new Set(['turret', 'barrel']);\n\n/**\n * @param {object} p\n * @param {{ vertices: number[][], faces: object[] }} p.model\n * @param {number} p.size размер модели танка (как `size` в кадре)\n * @param {number} [p.gunRotation] поворот башни относительно корпуса, рад\n * @param {number} [p.pitch] тангаж, рад (> 0 — нос `+u` вверх)\n * @param {number} [p.roll] крен, рад (> 0 — борт `+v` вверх)\n * @param {{x: number, y: number}} [p.gunKick] откат ствола в осях корпуса,\n * мировые единицы\n * @param {{x: number, y: number}} [p.shift] сдвиг всего танка (отдача\n * корпуса, встряска), мировые единицы\n * @param {number} [p.squash] просадка: доля сжатия по высоте\n * @returns {{ points: number[][], normals: number[][] }} точки в мировых\n * единицах и нормали граней — в осях корпуса после позы\n */\nexport function poseModel({\n model,\n size,\n gunRotation = 0,\n pitch = 0,\n roll = 0,\n gunKick = { x: 0, y: 0 },\n shift = { x: 0, y: 0 },\n squash = 0,\n}) {\n const scale = size / 10;\n const cosG = Math.cos(gunRotation);\n const sinG = Math.sin(gunRotation);\n const cosP = Math.cos(pitch);\n const sinP = Math.sin(pitch);\n const cosR = Math.cos(roll);\n const sinR = Math.sin(roll);\n const heightScale = 1 - squash;\n\n // часть каждой вершины: у башни и ствола свой поворот\n const partOf = new Array(model.vertices.length);\n\n for (const face of model.faces) {\n for (const index of face.indices) {\n partOf[index] = face.part;\n }\n }\n\n // крен вокруг `u`, затем тангаж вокруг `v` — вокруг центра опоры\n const tilt = ([u, v, w]) => {\n const v1 = v * cosR - w * sinR;\n const w1 = v * sinR + w * cosR;\n\n return [u * cosP - w1 * sinP, v1, u * sinP + w1 * cosP];\n };\n\n const turn = ([u, v, w]) => [u * cosG - v * sinG, u * sinG + v * cosG, w];\n\n const points = model.vertices.map(([u, v, w], index) => {\n let point = [u * scale, v * scale, w * scale * heightScale];\n const part = partOf[index];\n\n if (TURRET_PARTS.has(part)) {\n point = turn(point);\n }\n\n // откатывается только ствол, башня стоит\n if (part === 'barrel') {\n point = [point[0] + gunKick.x, point[1] + gunKick.y, point[2]];\n }\n\n point = tilt(point);\n\n return [point[0] + shift.x, point[1] + shift.y, point[2]];\n });\n\n const normals = model.faces.map(face => {\n let normal = faceNormal(model, face, heightScale);\n\n if (TURRET_PARTS.has(face.part)) {\n normal = turn(normal);\n }\n\n return tilt(normal);\n });\n\n return { points, normals };\n}\n\n// нормаль грани модели в её осях (с учётом сжатия по высоте)\nfunction faceNormal(model, face, heightScale) {\n return polygonNormal(\n face.indices.map(i => {\n const [u, v, w] = model.vertices[i];\n\n return [u, v, w * heightScale];\n }),\n );\n}\n","import { signedArea, PART_ORDER } from './model.js';\n\n// Проекция модели в оси контейнера `Tank` (он повёрнут на курс и стоит в\n// проекции своей высоты): почти сверху, с одним на весь танк ограниченным\n// наклоном от центра экрана. Чистые функции — PixiJS не нужен.\n\n/**\n * Наклон на единицу высоты модели в ЛОКАЛЬНЫХ осях контейнера. Та же\n * проекция, что у карты (`(p − camera) · shear` на уровень), ослабленная в\n * `gain` раз и общая для всего танка — модель не «разъезжается», — а\n * верхняя точка плавно упирается в `maxLean` мировых единиц.\n *\n * @param {object} p\n * @param {number} p.x мировая точка танка\n * @param {number} p.y\n * @param {{x: number, y: number}|null} p.camera null — центра камеры нет\n * (сцена без масштаба, парт ещё не на сцене): наклона нет, как у\n * `offsetPoint` нет сдвига (src/client/parallax.js)\n * @param {number} p.heading курс, рад (поворот контейнера)\n * @param {number} p.shear parallax.shear\n * @param {number} p.levelHeight мировых единиц на уровень\n * @param {number} p.maxLean предел смещения верхней точки\n * @param {number} p.topHeight высота верхней точки модели, мир. ед.\n * @param {number} [p.gain] доля наклона карты: 1 — как у зданий\n * @returns {{x: number, y: number}}\n */\nexport function modelLean({\n x,\n y,\n camera,\n heading,\n shear,\n levelHeight,\n maxLean,\n topHeight,\n gain = 1,\n}) {\n // кадр без центра камеры — кадр без проекции: модель обязана обновиться\n // (поза, свет граней, порядок частей), просто без наклона\n if (!camera) {\n return { x: 0, y: 0 };\n }\n\n let lx = ((x - camera.x) * shear * gain) / levelHeight;\n let ly = ((y - camera.y) * shear * gain) / levelHeight;\n const top = Math.hypot(lx, ly) * topHeight;\n\n // плавное насыщение к `maxLean`, а не срез: на жёстком пределе наклон\n // переставал меняться рывком, и при забегающей вперёд камере башня\n // «съезжала» и замирала\n if (top > 0 && maxLean > 0) {\n const scale = (maxLean * Math.tanh(top / maxLean)) / top;\n\n lx *= scale;\n ly *= scale;\n }\n\n // в оси контейнера: поворот на −heading\n const cos = Math.cos(heading);\n const sin = Math.sin(heading);\n\n return { x: lx * cos + ly * sin, y: -lx * sin + ly * cos };\n}\n\n/**\n * 2D-точки модели в осях контейнера: `(u, v) + lean · w`, всё умножено на\n * `zScale` (масштаб высоты уровня, как у корпуса).\n *\n * @param {object} p\n * @param {number[][]} p.points точки позы (`poseModel`)\n * @param {{x: number, y: number}} p.lean\n * @param {number} [p.zScale]\n * @returns {number[][]}\n */\nexport function projectModel({ points, lean, zScale = 1 }) {\n return points.map(([u, v, w]) => [\n (u + lean.x * w) * zScale,\n (v + lean.y * w) * zScale,\n ]);\n}\n\n/**\n * Видимые грани в порядке отрисовки. Видимость — знак площади проекции\n * (грань обращена к наблюдателю), порядок — по части (гусеницы → корпус →\n * башня → ствол), внутри части — по средней высоте грани после позы.\n *\n * @param {object} p\n * @param {{ faces: object[] }} p.model\n * @param {number[][]} p.points точки позы (для высоты)\n * @param {number[][]} p.projected 2D-точки (`projectModel`)\n * @returns {number[]} индексы граней\n */\nexport function visibleFaces({ model, points, projected }) {\n const visible = [];\n\n model.faces.forEach((face, index) => {\n const polygon = face.indices.map(i => projected[i]);\n\n if (signedArea(polygon) > 1e-9) {\n let height = 0;\n\n for (const i of face.indices) {\n height += points[i][2];\n }\n\n visible.push({ index, height: height / face.indices.length, face });\n }\n });\n\n // Сначала по части: башня стоит на корпусе, ствол растёт из башни — и\n // так при любом наклоне. Средняя высота грани для этого не годится: у\n // большой палубы на горке она выше, чем у опущенных задних скатов башни,\n // и палуба закрывала корму башни. Внутри выпуклой части порядок почти не\n // важен (невидимые грани отброшены) — там решает высота\n visible.sort(\n (a, b) =>\n PART_ORDER[a.face.part] - PART_ORDER[b.face.part] ||\n a.height - b.height ||\n a.index - b.index,\n );\n\n return visible.map(item => item.index);\n}\n\n/**\n * Яркость грани по её нормали — та же формула, что у света корпуса\n * (`tankLightFactor`, src/client/tankLight.js): нормаль из осей корпуса\n * поворачивается на курс в оси экрана. Ровный плоский верх даёт 1.\n *\n * @param {number[]} normal нормаль грани после позы, оси корпуса\n * @param {number} heading курс, рад\n * @param {object} light `tankLightUniforms` с нулевыми углами\n * @returns {number}\n */\nexport function faceShade(normal, heading, light) {\n const cos = Math.cos(heading);\n const sin = Math.sin(heading);\n const x = normal[0] * cos - normal[1] * sin;\n const y = normal[0] * sin + normal[1] * cos;\n const dot =\n x * light.uLight[0] + y * light.uLight[1] + normal[2] * light.uLight[2];\n\n return (light.uAmbient + light.uDiffuse * Math.max(dot, 0)) * light.uNorm;\n}\n","// Развёртка граней модели на атлас (`src/client/bakers/tankModelTexture.js`).\n// Чистая функция — PixiJS не нужен. UV считаются по НЕпозированной модели:\n// текстура приклеена к части и едет вместе с ней.\n//\n// - верхи и скаты (видимые сверху) — планарно по (u, v): гусеницы и корпус\n// на рисунок корпуса, башня и ствол — на рисунок башни;\n// - отвесные бока гусениц, ствола и тормоза — на свои полосы: вдоль ребра —\n// его длина в пикселях, по высоте — вся полоса;\n// - низ — одна тёмная точка.\n\nconst PLANAR_BY_PART = {\n track: 'body',\n hull: 'body',\n turret: 'gun',\n barrel: 'gun',\n};\n\nconst STRIP_BY_MATERIAL = {\n trackSide: 'trackSide',\n barrel: 'barrelSide',\n brake: 'brakeSide',\n};\n\n// отвесный ли бок: у квада бока `[low i, low j, high j, high i]` верхние\n// точки стоят над нижними\nfunction isVertical(model, face) {\n const [a, b, c, d] = face.indices.map(i => model.vertices[i]);\n\n return (\n face.indices.length === 4 &&\n a[0] === d[0] &&\n a[1] === d[1] &&\n b[0] === c[0] &&\n b[1] === c[1]\n );\n}\n\n/**\n * @param {{ vertices: number[][], faces: object[] }} model\n * @param {{ width: number, height: number, regions: object }} atlas\n * @returns {number[][][]} на каждую грань — `[x, y]` (0..1) для каждой её\n * вершины, в порядке `face.indices`\n */\nexport function faceUVs(model, atlas) {\n const { width, height, regions } = atlas;\n const norm = ([x, y]) => [x / width, y / height];\n\n return model.faces.map(face => {\n if (face.kind === 'bottom') {\n const { x, y, w, h } = regions.bottom;\n\n return face.indices.map(() => norm([x + w / 2, y + h / 2]));\n }\n\n const strip = STRIP_BY_MATERIAL[face.material];\n\n if (face.kind === 'side' && strip && isVertical(model, face)) {\n const region = regions[strip];\n const [a, b] = face.indices.map(i => model.vertices[i]);\n const length = Math.min(Math.hypot(b[0] - a[0], b[1] - a[1]), region.w);\n const top = region.y;\n const low = region.y + region.h;\n\n // [low i, low j, high j, high i]\n return [\n norm([region.x, low]),\n norm([region.x + length, low]),\n norm([region.x + length, top]),\n norm([region.x, top]),\n ];\n }\n\n const region = regions[PLANAR_BY_PART[face.part]];\n\n return face.indices.map(i => {\n const [u, v] = model.vertices[i];\n\n return norm([region.originX + u, region.originY + v]);\n });\n });\n}\n","import { GlProgram, Shader, UniformGroup } from 'pixi.js';\n\n// Шейдер модели танка: текстура атласа × яркость грани (`aShade`, считает\n// `faceShade` на CPU раз в кадр) × тинт уровня и альфа (`uColor` меша,\n// премультиплицированы — как у меша PixiJS, см. src/client/tankLight.js).\n\nconst VERTEX = `#version 300 es\nin vec2 aPosition;\nin vec2 aUv;\nin float aShade;\n\nout vec2 vUV;\nout float vShade;\nout vec4 vColor;\n\nuniform mat3 uProjectionMatrix;\nuniform mat3 uWorldTransformMatrix;\nuniform vec4 uWorldColorAlpha;\nuniform vec2 uResolution;\n\nuniform mat3 uTransformMatrix;\nuniform vec4 uColor;\nuniform float uRound;\n\nuniform mat3 uTextureMatrix;\n\nvoid main(void) {\n mat3 mvp = uProjectionMatrix * uWorldTransformMatrix * uTransformMatrix;\n\n gl_Position = vec4((mvp * vec3(aPosition, 1.0)).xy, 0.0, 1.0);\n\n if (uRound == 1.0) {\n gl_Position.xy =\n (floor(((gl_Position.xy * 0.5 + 0.5) * uResolution) + 0.5) /\n uResolution) * 2.0 - 1.0;\n }\n\n vUV = (uTextureMatrix * vec3(aUv, 1.0)).xy;\n vShade = aShade;\n vColor = uColor * uWorldColorAlpha;\n}\n`;\n\nconst FRAGMENT = `#version 300 es\nin vec2 vUV;\nin float vShade;\nin vec4 vColor;\n\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\n\nvoid main(void) {\n vec4 color = texture(uTexture, vUV);\n\n finalColor = vec4(color.rgb * vShade, color.a) * vColor;\n}\n`;\n\nlet program = null;\n\n/**\n * @param {import('pixi.js').Texture} texture атлас модели\n * @returns {Shader}\n */\nexport function createTankModelShader(texture) {\n program ||= GlProgram.from({\n vertex: VERTEX,\n fragment: FRAGMENT,\n name: 'tank-model',\n });\n\n return new Shader({\n glProgram: program,\n resources: {\n uTexture: texture.source,\n textureUniforms: new UniformGroup({\n uTextureMatrix: {\n value: texture.textureMatrix.mapCoord,\n type: 'mat3x3<f32>',\n },\n }),\n },\n });\n}\n\n// смена атласа (команда) без пересоздания шейдера\nexport function setTankModelTexture(shader, texture) {\n shader.resources.uTexture = texture.source;\n shader.resources.textureUniforms.uniforms.uTextureMatrix =\n texture.textureMatrix.mapCoord;\n}\n","import { Geometry, Mesh } from 'pixi.js';\nimport { faceUVs } from './uv.js';\nimport { createTankModelShader, setTankModelTexture } from './shader.js';\n\n// Меш модели танка: у каждой грани свои вершины (у соседних граней разные\n// UV и яркость), треугольники — веером. Позиции и яркость пишутся раз в\n// кадр для всех граней, а индексы — только видимых и в порядке отрисовки;\n// хвост индексного буфера заполняется нулями (вырожденные треугольники),\n// поэтому размер буферов постоянный.\n\n/**\n * @param {{ vertices: number[][], faces: object[] }} model\n * @param {{ texture: object, width: number, height: number,\n * regions: object }} atlas\n */\nexport function createTankModelMesh(model, atlas) {\n // раскладка: начало вершин каждой грани в общем буфере\n const offsets = [];\n let vertexCount = 0;\n let triangleCount = 0;\n\n for (const face of model.faces) {\n offsets.push(vertexCount);\n vertexCount += face.indices.length;\n triangleCount += face.indices.length - 2;\n }\n\n const positions = new Float32Array(vertexCount * 2);\n const uvs = new Float32Array(vertexCount * 2);\n const shades = new Float32Array(vertexCount).fill(1);\n const indices = new Uint32Array(triangleCount * 3);\n\n const writeUVs = target => {\n faceUVs(model, target).forEach((corners, face) => {\n corners.forEach(([x, y], k) => {\n uvs[(offsets[face] + k) * 2] = x;\n uvs[(offsets[face] + k) * 2 + 1] = y;\n });\n });\n };\n\n writeUVs(atlas);\n\n const geometry = new Geometry({\n attributes: {\n aPosition: { buffer: positions, format: 'float32x2' },\n aUv: { buffer: uvs, format: 'float32x2' },\n aShade: { buffer: shades, format: 'float32' },\n },\n indexBuffer: indices,\n });\n\n const shader = createTankModelShader(atlas.texture);\n const mesh = new Mesh({ geometry, shader, texture: atlas.texture });\n\n return {\n mesh,\n\n // смена атласа (команда): UV те же по раскладке, но атлас другой\n setAtlas(next) {\n writeUVs(next);\n geometry.getBuffer('aUv').update();\n setTankModelTexture(shader, next.texture);\n mesh.texture = next.texture;\n },\n\n /**\n * @param {number[][]} projected 2D-точки вершин модели\n * @param {number[]} faceShades яркость каждой грани\n * @param {number[]} order видимые грани в порядке отрисовки\n */\n update(projected, faceShades, order) {\n model.faces.forEach((face, f) => {\n const base = offsets[f];\n\n face.indices.forEach((vertex, k) => {\n positions[(base + k) * 2] = projected[vertex][0];\n positions[(base + k) * 2 + 1] = projected[vertex][1];\n shades[base + k] = faceShades[f];\n });\n });\n\n let cursor = 0;\n\n for (const f of order) {\n const base = offsets[f];\n const count = model.faces[f].indices.length;\n\n for (let k = 1; k < count - 1; k += 1) {\n indices[cursor] = base;\n indices[cursor + 1] = base + k;\n indices[cursor + 2] = base + k + 1;\n cursor += 3;\n }\n }\n\n indices.fill(0, cursor);\n\n geometry.getBuffer('aPosition').update();\n geometry.getBuffer('aShade').update();\n geometry.indexBuffer.update();\n },\n\n destroy() {\n mesh.destroy();\n shader.destroy();\n geometry.destroy();\n },\n };\n}\n","// Тень модели по её силуэту: каждая точка позы падает на опору вдоль\n// света, на каждую часть — выпуклая оболочка. Чистые функции — PixiJS не\n// нужен. Свет — тот же, что у граней (`tilt.lightDir` в осях экрана,\n// `tankLight.lightZ` — его высота): точка на высоте `h` падает на\n// `p − h · (Lx, Ly) / Lz` — от света, тем дальше, чем выше точка. Поэтому\n// башня и ствол отбрасывают свою тень, наклон и подброс видны по ней.\n\n// выпуклая оболочка (монотонная цепь), обход против часовой в осях u-v\nexport function convexHull(points) {\n const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n\n if (sorted.length < 3) {\n return sorted;\n }\n\n const cross = (o, a, b) =>\n (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);\n const lower = [];\n const upper = [];\n\n for (const point of sorted) {\n while (\n lower.length >= 2 &&\n cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0\n ) {\n lower.pop();\n }\n\n lower.push(point);\n }\n\n for (let i = sorted.length - 1; i >= 0; i -= 1) {\n const point = sorted[i];\n\n while (\n upper.length >= 2 &&\n cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0\n ) {\n upper.pop();\n }\n\n upper.push(point);\n }\n\n lower.pop();\n upper.pop();\n\n return lower.concat(upper);\n}\n\n/**\n * Сдвиг тени на единицу высоты в ЛОКАЛЬНЫХ осях контейнера (повёрнутого на\n * курс).\n *\n * @param {number[]} [lightDir] направление НА свет в осях экрана\n * @param {number} lightZ высота света относительно длины `lightDir`\n * @param {number} heading курс, рад\n * @returns {{x: number, y: number}}\n */\nexport function shadowDrift(lightDir, lightZ, heading) {\n if (!lightDir || !(lightZ > 0)) {\n return { x: 0, y: 0 };\n }\n\n const len = Math.hypot(lightDir[0], lightDir[1]) || 1;\n // от света: минус направление на свет, на единицу высоты — делённое на\n // высоту света\n const wx = -lightDir[0] / len / lightZ;\n const wy = -lightDir[1] / len / lightZ;\n const cos = Math.cos(heading);\n const sin = Math.sin(heading);\n\n return { x: wx * cos + wy * sin, y: -wx * sin + wy * cos };\n}\n\n/**\n * Полигоны тени — по одному на часть модели.\n *\n * @param {object} p\n * @param {{ faces: object[] }} p.model\n * @param {number[][]} p.points точки позы (`poseModel`), мировые единицы\n * @param {number} [p.lift] подъём корпуса над опорой, мировые единицы\n * @param {{x: number, y: number}} p.drift `shadowDrift`\n * @returns {number[][][]}\n */\nexport function shadowPolygons({ model, points, lift = 0, drift }) {\n const byPart = new Map();\n\n for (const face of model.faces) {\n if (!byPart.has(face.part)) {\n byPart.set(face.part, new Set());\n }\n\n for (const index of face.indices) {\n byPart.get(face.part).add(index);\n }\n }\n\n const polygons = [];\n\n for (const indices of byPart.values()) {\n const shadowed = [...indices].map(i => {\n const [u, v, w] = points[i];\n // точка под опорой (корма на крутом наклоне) тени не удлиняет\n const height = Math.max(0, w + lift);\n\n return [u + drift.x * height, v + drift.y * height];\n });\n\n polygons.push(convexHull(shadowed));\n }\n\n return polygons;\n}\n","import { GlProgram, Shader, UniformGroup } from 'pixi.js';\n\n// Свет корпуса по карте нормалей (`src/client/bakers/tankTexture.js`).\n// Свет один на всю сцену и задан в осях ЭКРАНА (`tilt.lightDir`, как у\n// боковых граней зданий), поэтому нормаль текстуры проходит тот же путь,\n// что и углы квада в `tiltCorners`:\n//\n// оси текстуры → собственный поворот (пушка) → наклон pitch/roll в осях\n// корпуса → курс → оси экрана\n//\n// Множитель яркости нормирован так, что ровный плоский верх даёт ровно 1:\n// ровно стоящий танк по яркости тот же, что без шейдера, а фаски к свету\n// светлеют и от света темнеют. Математика продублирована в JS\n// (`tankLightFactor`) — шейдер не проверить без GPU, а зеркало проверяется\n// тестами; правка формулы меняет ОБЕ стороны.\n\nconst VERTEX = `#version 300 es\nin vec2 aPosition;\nin vec2 aUV;\n\nout vec2 vUV;\nout vec4 vColor;\n\nuniform mat3 uProjectionMatrix;\nuniform mat3 uWorldTransformMatrix;\nuniform vec4 uWorldColorAlpha;\nuniform vec2 uResolution;\n\nuniform mat3 uTransformMatrix;\nuniform vec4 uColor;\nuniform float uRound;\n\nuniform mat3 uTextureMatrix;\n\nvoid main(void) {\n mat3 mvp = uProjectionMatrix * uWorldTransformMatrix * uTransformMatrix;\n\n gl_Position = vec4((mvp * vec3(aPosition, 1.0)).xy, 0.0, 1.0);\n\n if (uRound == 1.0) {\n gl_Position.xy =\n (floor(((gl_Position.xy * 0.5 + 0.5) * uResolution) + 0.5) /\n uResolution) * 2.0 - 1.0;\n }\n\n vUV = (uTextureMatrix * vec3(aUV, 1.0)).xy;\n // тинт уровня и прозрачность (премультиплицированы, как у меша PixiJS)\n vColor = uColor * uWorldColorAlpha;\n}\n`;\n\nconst FRAGMENT = `#version 300 es\nin vec2 vUV;\nin vec4 vColor;\n\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform sampler2D uNormal;\n\nuniform vec2 uSpin;\nuniform vec2 uPitch;\nuniform vec2 uRoll;\nuniform vec2 uHeading;\nuniform vec3 uLight;\nuniform float uAmbient;\nuniform float uDiffuse;\nuniform float uNorm;\n\nvec2 turn(vec2 v, vec2 cs) {\n return vec2(v.x * cs.x - v.y * cs.y, v.x * cs.y + v.y * cs.x);\n}\n\nvoid main(void) {\n vec4 color = texture(uTexture, vUV);\n vec4 encoded = texture(uNormal, vUV);\n vec3 n = vec3(0.0, 0.0, 1.0);\n\n // запечённая текстура премультиплицирована: на кромке антиалиасинга\n // нормаль делится на свою альфу, а пустота считается плоской\n if (encoded.a > 0.01) {\n n = normalize(encoded.rgb / encoded.a * 2.0 - 1.0);\n }\n\n n.xy = turn(n.xy, uSpin);\n // тангаж: нос +u вверх — нормаль к корме\n n = vec3(n.x * uPitch.x - n.z * uPitch.y, n.y, n.x * uPitch.y + n.z * uPitch.x);\n // крен: борт +v вверх — нормаль к борту −v\n n = vec3(n.x, n.y * uRoll.x - n.z * uRoll.y, n.y * uRoll.y + n.z * uRoll.x);\n n.xy = turn(n.xy, uHeading);\n\n float light = (uAmbient + uDiffuse * max(dot(n, uLight), 0.0)) * uNorm;\n\n finalColor = vec4(color.rgb * light, color.a) * vColor;\n}\n`;\n\nconst pair = angle => [Math.cos(angle), Math.sin(angle)];\n\n/**\n * Значения uniforms света для одного меша. Чистая функция: PixiJS не нужен.\n *\n * @param {object} p\n * @param {number} p.heading курс корпуса, рад\n * @param {number} p.rotation собственный поворот меша (пушка), рад\n * @param {number} p.pitch тангаж, рад (> 0 — нос `+u` поднят)\n * @param {number} p.roll крен, рад (> 0 — поднят борт `+v`)\n * @param {number[]} [p.lightDir] направление НА свет в осях экрана\n * @param {number} p.lightZ высота света над плоскостью карты\n * @param {number} p.ambient рассеянная доля\n * @param {number} p.diffuse направленная доля\n * @returns {object} uniforms `uSpin`, `uPitch`, `uRoll`, `uHeading`,\n * `uLight`, `uAmbient`, `uDiffuse`, `uNorm`\n */\nexport function tankLightUniforms({\n heading,\n rotation,\n pitch,\n roll,\n lightDir,\n lightZ,\n ambient,\n diffuse,\n}) {\n // без направления свет падает отвесно: плоский верх остаётся 1, фаски\n // равномерно темнее\n const [lx, ly] = lightDir || [0, 0];\n const len = Math.hypot(lx, ly, lightZ) || 1;\n const light = [lx / len, ly / len, lightZ / len];\n const flat = ambient + diffuse * Math.max(light[2], 0);\n\n return {\n uSpin: pair(rotation),\n uPitch: pair(pitch),\n uRoll: pair(roll),\n uHeading: pair(heading),\n uLight: light,\n uAmbient: ambient,\n uDiffuse: diffuse,\n uNorm: flat ? 1 / flat : 1,\n };\n}\n\nconst turn = ([x, y], [c, s]) => [x * c - y * s, x * s + y * c];\n\n/**\n * JS-зеркало фрагментного шейдера: множитель яркости пикселя с нормалью\n * `normal` (оси текстуры, нормировать не обязательно).\n *\n * @param {number[]} normal [x, y, z]\n * @param {object} u результат `tankLightUniforms`\n * @returns {number}\n */\nexport function tankLightFactor(normal, u) {\n const len = Math.hypot(normal[0], normal[1], normal[2]) || 1;\n let [x, y] = turn([normal[0] / len, normal[1] / len], u.uSpin);\n let z = normal[2] / len;\n\n [x, z] = [\n x * u.uPitch[0] - z * u.uPitch[1],\n x * u.uPitch[1] + z * u.uPitch[0],\n ];\n [y, z] = [y * u.uRoll[0] - z * u.uRoll[1], y * u.uRoll[1] + z * u.uRoll[0]];\n [x, y] = turn([x, y], u.uHeading);\n\n const dot = x * u.uLight[0] + y * u.uLight[1] + z * u.uLight[2];\n\n return (u.uAmbient + u.uDiffuse * Math.max(dot, 0)) * u.uNorm;\n}\n\nlet program = null;\n\n/**\n * Шейдер меша с парой текстур. Свой на каждый меш: uniforms у мешей\n * разные (курс, поворот пушки), а меш со своим шейдером не батчится.\n *\n * @param {import('pixi.js').Texture} texture цветная текстура\n * @param {import('pixi.js').Texture} normal её карта нормалей\n * @returns {Shader}\n */\nexport function createTankLightShader(texture, normal) {\n program ||= GlProgram.from({\n vertex: VERTEX,\n fragment: FRAGMENT,\n name: 'tank-light',\n });\n\n return new Shader({\n glProgram: program,\n resources: {\n uTexture: texture.source,\n uNormal: normal.source,\n textureUniforms: new UniformGroup({\n uTextureMatrix: {\n value: texture.textureMatrix.mapCoord,\n type: 'mat3x3<f32>',\n },\n }),\n tankLight: new UniformGroup({\n uSpin: { value: new Float32Array([1, 0]), type: 'vec2<f32>' },\n uPitch: { value: new Float32Array([1, 0]), type: 'vec2<f32>' },\n uRoll: { value: new Float32Array([1, 0]), type: 'vec2<f32>' },\n uHeading: { value: new Float32Array([1, 0]), type: 'vec2<f32>' },\n uLight: { value: new Float32Array([0, 0, 1]), type: 'vec3<f32>' },\n uAmbient: { value: 1, type: 'f32' },\n uDiffuse: { value: 0, type: 'f32' },\n uNorm: { value: 1, type: 'f32' },\n }),\n },\n });\n}\n\n// смена текстур (команда, остов) без пересоздания шейдера\nexport function setTankLightTextures(shader, texture, normal) {\n shader.resources.uTexture = texture.source;\n shader.resources.uNormal = normal.source;\n shader.resources.textureUniforms.uniforms.uTextureMatrix =\n texture.textureMatrix.mapCoord;\n}\n\n// раскладка значений `tankLightUniforms` в uniforms шейдера\nexport function applyTankLight(shader, values) {\n const target = shader.resources.tankLight.uniforms;\n\n for (const key of ['uSpin', 'uPitch', 'uRoll', 'uHeading', 'uLight']) {\n target[key].set(values[key]);\n }\n\n target.uAmbient = values.uAmbient;\n target.uDiffuse = values.uDiffuse;\n target.uNorm = values.uNorm;\n}\n","import {\n AlphaFilter,\n BlurFilter,\n Container,\n Graphics,\n PerspectiveMesh,\n Sprite,\n Ticker,\n} from 'pixi.js';\nimport { lerp, clamp } from 'vimp-engine/lib/math.js';\nimport { levelZ, renderLevel } from '../levelZ.js';\nimport { cameraCenter } from '../camera.js';\nimport { offsetPoint } from '../parallax.js';\nimport { tiltCorners, tiltShade, scaleTint } from '../tilt.js';\nimport { landingImpact } from '../landing.js';\nimport { recoilAmount, recoilOffsets } from '../recoil.js';\nimport { blastKick, blastJoltState } from '../blastJolt.js';\nimport { createTankModel } from '../tank3d/model.js';\nimport { poseModel } from '../tank3d/transform.js';\nimport {\n modelLean,\n projectModel,\n visibleFaces,\n faceShade,\n} from '../tank3d/project.js';\nimport { createTankModelMesh } from '../tank3d/modelMesh.js';\nimport { shadowDrift, shadowPolygons } from '../tank3d/shadow.js';\nimport {\n createTankLightShader,\n setTankLightTextures,\n applyTankLight,\n tankLightUniforms,\n} from '../tankLight.js';\nimport { lightLevels } from '../lighting/lightMath.js';\nimport {\n parallax as parallaxConfig,\n shadow as shadowConfig,\n tilt as tiltConfig,\n tankLight as tankLightConfig,\n recoil as recoilConfig,\n blastJolt as blastJoltConfig,\n tankModel as tankModelConfig,\n landing as landingConfig,\n lighting as lightingConfig,\n surfaceFx,\n} from '../../config/render.js';\nimport {\n M1_X,\n M1_Y,\n M1_ANGLE,\n M1_GUN_ROTATION,\n M1_VX,\n M1_VY,\n M1_ENGINE_LOAD,\n M1_CONDITION,\n M1_SIZE,\n M1_TEAM,\n M1_Z,\n M1_LEVEL,\n M1_VZ,\n M1_PITCH,\n M1_ROLL,\n} from '../snapshotFields.js';\n\n// светотень наклона только затемняет (см. `tiltShade` в src/client/tilt.js):\n// множитель зажат в [0, 1]\nconst SHADE_MIN = 0;\nconst SHADE_MAX = 1;\n\n// базовый zIndex танка внутри своего уровня (см. plan/stage_6.md)\nconst TANK_BASE_Z = 3;\n\n// геометрия модели одна на все танки: поза и проекция — свои у каждого\nlet sharedModel = null;\n\nconst tankModelGeometry = () => {\n sharedModel ||= createTankModel(tankModelConfig);\n\n return sharedModel;\n};\n\n// свет граней модели — та же формула, что у шейдера карт нормалей, без\n// наклона и курса: их модель учитывает сама (поза, `faceShade`)\nconst modelLight = () =>\n tankLightUniforms({\n heading: 0,\n rotation: 0,\n pitch: 0,\n roll: 0,\n lightDir: tiltConfig.lightDir,\n lightZ: tankLightConfig.lightZ,\n ambient: tankLightConfig.ambient,\n diffuse: tankLightConfig.diffuse,\n });\n\n// дуло — на 0.55 длины корпуса от центра по курсу башни (`Tank::\n// muzzle_position` в core/src/tank.rs)\nconst MUZZLE_REACH = 0.55;\n\n// наибольший перекос башни остова модели, рад\nconst WRECK_SKEW = 0.6;\n\n// потолок уровней карты: движок нумерует их 0..7 (бит 8 занят\n// STATIC_LEVEL_GROUP, vimp-engine core/src/map.rs). Копия, потому что\n// парту WASM-констант не отдают; расходиться с движком ей нельзя.\n// В прыжке `_z` уходит выше уровня отрыва, и слой тени иначе улетел бы за\n// верхнюю плиту\nconst LEVEL_MAX = 7;\n\n// масштаб корпуса на высоте, тень и ракурс на подъёме — числа 2.5D, они\n// живут в src/config/render.js (`parallax`, `shadow`)\n\n// скорость (высота тона) на холостом ходу\nconst MIN_ENGINE_RATE = 1;\n\n// скорость при движении\nconst MAX_ENGINE_RATE = 1.15;\n\n// повышенная скорость при напряжении (газ в стену)\nconst STRAIN_ENGINE_RATE = 1.25;\n\n// множитель громкости на холостом ходу (60% от базовой)\nconst MIN_ENGINE_VOLUME_FACTOR = 0.6;\n\n// множитель на полном ходу (100% от базовой)\nconst MAX_ENGINE_VOLUME_FACTOR = 1.0;\n\n// глубина и частота покачивания высоты тона на холостом ходу\nconst IDLE_WOBBLE_DEPTH = 0.015;\nconst IDLE_WOBBLE_HZ = 2.5;\n\n/**\n * Вычисляет параметры звука двигателя на основе нагрузки на двигатель.\n * @param {number} load - Нагрузка на двигатель (от 0.0 до > 1.0).\n * @param {number} [timeMs=0] - Время для покачивания тона на холостых.\n * @returns {{rate: number, volumeFactor: number}} -\n * Объект со скоростью (pitch) и множителем громкости.\n */\nexport function calculateEngineSoundParams(load, timeMs = 0) {\n // load 0.0 -> холостой ход\n // load 1.0 -> движение на полной скорости\n // load > 1.0 -> напряжение (газ в стену)\n\n // интерполяция скорости (pitch) и громкости,\n // разделение базовой нагрузки (до 1.0)\n // и нагрузки от напряжения (свыше 1.0).\n // нечисловая нагрузка (короткий ряд) не должна доходить до Web Audio:\n // clamp(undefined) даёт NaN, а NaN в rate — нефинитное значение параметра\n const safeLoad = Number.isFinite(load) ? load : 0;\n const baseLoad = clamp(safeLoad, 0, 1);\n const strainLoad = Math.max(0, safeLoad - 1.0);\n\n const rate =\n lerp(MIN_ENGINE_RATE, MAX_ENGINE_RATE, baseLoad) +\n (STRAIN_ENGINE_RATE - MAX_ENGINE_RATE) * strainLoad;\n\n const volumeFactor = lerp(\n MIN_ENGINE_VOLUME_FACTOR,\n MAX_ENGINE_VOLUME_FACTOR,\n baseLoad,\n );\n\n // неизменная высота тона у баса читается ухом как гул, а не как\n // двигатель: на холостых тон слегка покачивается, а с ростом нагрузки\n // покачивание сходит на нет — там за характер отвечает сама нагрузка\n const wobble =\n 1 +\n IDLE_WOBBLE_DEPTH *\n (1 - baseLoad) *\n Math.sin(2 * Math.PI * IDLE_WOBBLE_HZ * (timeMs / 1000));\n\n return { rate: rate * wobble, volumeFactor };\n}\n\n// сдвиг тени от света: `−lightDir` длиной `groundOffset`. Направление\n// света нормируется при чтении (см. `tilt.lightDir`)\nexport function shadowOffset(\n lightDir = tiltConfig.lightDir,\n distance = shadowConfig.groundOffset,\n) {\n const len = lightDir ? Math.hypot(lightDir[0], lightDir[1]) : 0;\n\n if (!len || !distance) {\n return { x: 0, y: 0 };\n }\n\n return {\n x: (-lightDir[0] / len) * distance,\n y: (-lightDir[1] / len) * distance,\n };\n}\n\nexport default class Tank extends Container {\n constructor(data, assets, dependencies, context) {\n super();\n\n // корпус, пушка и остов — PerspectiveMesh, а не Sprite: наклон корпуса\n // (`pitch`/`roll` из кадра) деформирует квад, а спрайт умеет только\n // масштаб и поворот. Якоря и масштаб у меша уходят ВНУТРЬ углов —\n // их считает `src/client/tilt.js`, здесь остаётся только раздать\n // результат в `setCorners` (см. _applyTilt)\n const mesh = () => {\n const view = new PerspectiveMesh({\n verticesX: tiltConfig.vertices,\n verticesY: tiltConfig.vertices,\n });\n\n // батчер задаётся явно, чтобы режим не зависел молча от конфига:\n // при нынешних 6×6 меш батчится и сам, но `tiltConfig.vertices`\n // больше 10 увело бы его за порог `Mesh.batched` (100 вершин) — в\n // общий шейдер `GlMeshAdaptor`, который навсегда ломается на\n // уничтожении текстуры (см. src/client/parts/map/extrusion.js)\n view.geometry.batchMode = 'batch';\n\n return view;\n };\n\n this.body = mesh();\n this.gun = mesh();\n\n // меш для уничтоженного состояния\n this.wreck = mesh();\n\n // якоря: у меша своего якоря нет, он параметр `tiltCorners`\n this._bodyAnchor = { x: 0.5, y: 0.5 };\n this._wreckAnchor = { x: 0.5, y: 0.5 };\n\n this.addChild(this.body, this.gun, this.wreck);\n\n // шейдеры света по карте нормалей: свой на каждый меш (у пушки свой\n // поворот). Заводятся при первой текстуре с картой нормалей\n this._lightShaders = new Map();\n\n this._textures = assets.tankTexture;\n\n // 3D-модель живого танка (src/client/tank3d/, plan/tank-3d/): вместо\n // плоских `body`/`gun`, когда включена и есть атлас. Меш заводится при\n // первом оживлении, остов остаётся плоским\n this._modelAtlases =\n tankModelConfig.enabled && assets.tankModelTexture\n ? assets.tankModelTexture\n : null;\n this._model = null;\n // тень по силуэту модели — сиблинг на сцене, заводится по требованию\n this._modelShadow = null;\n // постоянный перекос башни остова модели (задаётся при гибели)\n this._wreckSkew = 0;\n this._shadowAsset = assets.tankShadowTexture || null;\n this._renderer = dependencies.renderer || null;\n\n // тень живёт СИБЛИНГОМ на сцене, а не ребёнком танка: у неё свой\n // zIndex — слоя, НАД которым танк висит, — а сцена плоская, порядок\n // задаёт только zIndex (тот же приём, что в Tracks._markLayer).\n // Движок про неё не знает, значит убирает её destroy() этого парта\n this._shadow = null;\n\n // параметры с сервера:\n // [x, y, rotation, gunRotation, vX, vY,\n // engineLoad, condition, size, teamId, angvel, z, level]\n // мировая (НЕсмещённая) точка танка: в неё уходят звук и levelView, а\n // `this.position` каждый кадр перезаписывается проекцией высоты\n // (см. _updateView)\n this._worldX = data[M1_X] || 0;\n this._worldY = data[M1_Y] || 0;\n this.x = this._worldX;\n this.y = this._worldY;\n this.rotation = data[M1_ANGLE] || 0;\n // поворот пушки — параметр её квада, а не трансформ меша\n this._gunRotation = data[M1_GUN_ROTATION] || 0;\n this._engineLoad = data[M1_ENGINE_LOAD] || 0;\n this._condition = data[M1_CONDITION];\n this._size = data[M1_SIZE];\n this._teamId = data[M1_TEAM];\n\n // 2.5D: непрерывная высота (рампа/падение) и дискретный уровень\n // ОТРИСОВКИ (о нём — в update)\n this._z = data[M1_Z] || 0;\n\n // ФИЗИЧЕСКИЙ уровень из кадра (не `renderLevel`): в полёте он держит\n // уровень отрыва, и по нему тень знает, над какой плитой висит танк\n this._physLevel = data[M1_LEVEL] || 0;\n\n // вертикальная динамика: наклон корпуса считает ядро (`pitch`/`roll`),\n // `vz` нужен клиенту как детектор касания\n this._vz = data[M1_VZ] || 0;\n this._prevVz = this._vz;\n this._pitch = data[M1_PITCH] || 0;\n this._roll = data[M1_ROLL] || 0;\n\n // просадка корпуса на приземлении: сила удара 0..1 и остаток анимации\n this._landImpact = 0;\n this._landTimer = 0;\n this._squash = 0;\n\n this._level = renderLevel(data[M1_LEVEL], this._z);\n this.zIndex = levelZ(TANK_BASE_Z, this._level);\n\n // свой танк — единственный, кто вправе писать в levelView: по нему\n // плита моста над игроком становится полупрозрачной (Map.onRender).\n //\n // Спрашиваем в момент, когда нужен ответ, а не в конструкторе: парты\n // создаются из FIRST_SHOT_DATA, который приходит ДО первого бинарного\n // кадра, и свой танк строится, пока `localPlayer.id` ещё null — флаг,\n // посчитанный один раз, был бы навсегда false ровно у той сущности,\n // ради которой он и заведён\n this._isLocal = () => dependencies.localPlayer?.is(context?.id) === true;\n\n // отдача после выстрела (src/client/recoil.js): мс с выстрела\n // (Infinity — покой) и её добавка к наклону корпуса. Событие приносит\n // сервис `shots` — его будит эффект выстрела с id стрелка\n this._recoilElapsed = Infinity;\n this._recoilTilt = { pitch: 0, roll: 0 };\n // смещения мешей от отдачи в осях корпуса (мировые единицы, без\n // проекции высоты): собираются с встряской взрыва в `_applyMeshOffsets`\n this._recoilOffsets = { body: { x: 0, y: 0 }, gun: { x: 0, y: 0 } };\n\n // реакция на взрыв (src/client/blastJolt.js): толчок, мс с него и\n // текущие добавки — к наклону, к видимой высоте и сдвиг встряски\n this._blastKick = null;\n this._blastElapsed = 0;\n this._blastTilt = { pitch: 0, roll: 0 };\n this._blastLift = 0;\n this._blastShake = { x: 0, y: 0 };\n this._unsubscribeBlasts = dependencies.blasts\n ? dependencies.blasts.subscribe(blast => this._onBlast(blast))\n : null;\n this._unsubscribeShots =\n dependencies.shots && context?.id !== undefined\n ? dependencies.shots.subscribe(context.id, {\n fired: () => this._onFired(),\n muzzle: () => this._muzzleWorld(),\n })\n : null;\n this._levelView = dependencies.levelView || null;\n // журнал ошибок движка (необязательный сервис): отдаём его levelView —\n // сам сервис игры создаётся до полотна и движковых сервисов не видит\n this._levelView?.setDiagnostics(dependencies.diagnostics ?? null);\n\n // Ночь (сервис игры, src/client/lighting/): два конуса фар и слабый\n // свет под корпусом — источники сессии, переживают смену карты. Блика\n // на самой фаре нет: аддитивный спрайт засвечивал полкорпуса\n this._lighting = dependencies.lighting?.enabled\n ? dependencies.lighting\n : null;\n // { cones: [левая, правая], glow }; null — фары выключены\n this._headlights = null;\n\n // засвет (`lighting.glints`): `{ sprite, mask }` — градиент к источнику,\n // обрезанный силуэтом корпуса. Заводится на первом кадре ночи, днём и\n // без сервиса его нет вовсе\n this._glint = null;\n\n if (this._lighting && assets.headlightConeTexture) {\n this._lighting.registerTextures({ cone: assets.headlightConeTexture });\n }\n\n // видимость уровня и признаки высоты считаются каждый кадр, а не по\n // приходу строки: и камера, и локальный игрок двигаются между кадрами.\n //\n // `onRender` у Container — аксессор: назначаем СВОЙСТВОМ, метод с этим\n // именем на прототипе затенил бы сеттер и колбэк не позвался бы ни разу\n this.onRender = () => this._updateView();\n\n // правильный якорь для пушки в зависимости от команды\n const liveTextures =\n this._teamId === 1\n ? this._textures.liveTeamId1\n : this._textures.liveTeamId2;\n const gunAnchorData = liveTextures ? liveTextures.gunAnchor : null;\n\n this._gunAnchor = gunAnchorData\n ? { x: gunAnchorData.x, y: gunAnchorData.y }\n : { x: 0.5, y: 0.5 }; // запасной вариант\n\n // коэффициент масштабирования, чтобы соответствовать размеру танка\n const BAKER_BASE_SIZE = 10; // размер, использованный в текстурах\n this._scaleFactor = this._size / BAKER_BASE_SIZE;\n\n this._soundManager = dependencies.soundManager;\n this._soundId = null;\n\n const engineConfig = this._soundManager.getSoundConfig('tankEngine');\n\n this._baseEngineVolume = engineConfig?.volume || 0;\n // без конфига registerSound вернёт null: страховка в update() иначе\n // пыталась бы регистрировать звук каждый кадр для каждого танка\n this._hasEngineSound = !!engineConfig;\n\n // плеск под гусеницами: поверхность клетки из ядра (сервис `surfaces`);\n // без сервиса или без звука в каталоге воды не слышно\n this._surfaces = dependencies.surfaces || null;\n this._waterSoundId = null;\n this._speed = Math.hypot(data[M1_VX] || 0, data[M1_VY] || 0);\n\n const waterConfig = this._soundManager.getSoundConfig('tankWater');\n\n this._baseWaterVolume = waterConfig?.volume || 0;\n this._hasWaterSound = !!waterConfig;\n\n // первоначальная установка визуального состояния\n this.create();\n }\n\n // запускает звуки двигателя танка\n _initSounds() {\n if (this._soundId || this._condition === 0 || !this._hasEngineSound) {\n return;\n }\n\n this._soundId = this._soundManager.registerSound(\n 'tankEngine',\n this._getSoundData(),\n );\n }\n\n _getSoundData() {\n const { rate, volumeFactor } = calculateEngineSoundParams(\n this._engineLoad,\n performance.now(),\n );\n\n return {\n position: { x: this._worldX, y: this._worldY },\n rate,\n volume: this._baseEngineVolume * volumeFactor,\n // свой двигатель не принадлежит миру: он всегда по центру и без HRTF.\n // Слушатель — центр камеры, то есть сам этот танк: источник, лежащий\n // ровно на слушателе, HRTF сворачивает в гребенчатую окраску («гул»),\n // а расхождение камеры и танка в пару пикселей кидает звук целиком в\n // одно ухо (азимут в Web Audio зависит от направления, не от\n // расстояния). Флаг считается каждый кадр: `_isLocal()` даёт true\n // только после появления `localPlayer.id`, а обновление уезжает в\n // движок через `updateSoundData`\n spatial: !this._isLocal(),\n };\n }\n\n create() {\n // если танк уничтожен\n if (this._condition === 0) {\n this.body.visible = false;\n this.gun.visible = false;\n\n this._setTexture(\n this.wreck,\n this._textures.destroyed,\n this._textures.destroyedNormal,\n );\n\n // остов — та же модель с обгоревшим атласом и сбитой башней; без\n // модели — прежняя плоская картинка\n const wreckAtlas = this._modelAtlases?.destroyed;\n\n if (wreckAtlas) {\n this._wreckSkew = (Math.random() * 2 - 1) * WRECK_SKEW;\n this._showModel(wreckAtlas);\n this.wreck.visible = false;\n } else {\n if (this._model) {\n this._model.mesh.visible = false;\n }\n\n this.wreck.visible = true;\n }\n\n // поворот башни, так как она теперь часть обломков\n this._gunRotation = 0;\n\n // остов не откатывается\n this._resetRecoil();\n\n // при уничтожении отключение звука\n this.destroySounds();\n\n // у обломка фар нет\n this._removeLights();\n } else {\n // если танк \"ожил\" или создан впервые\n this.wreck.visible = false;\n\n let liveTextures;\n\n // набор текстур в зависимости от команды\n if (this._teamId === 1) {\n liveTextures = this._textures.liveTeamId1;\n } else if (this._teamId === 2) {\n liveTextures = this._textures.liveTeamId2;\n }\n\n this._setTexture(this.body, liveTextures.body, liveTextures.bodyNormal);\n this._setTexture(this.gun, liveTextures.gun, liveTextures.gunNormal);\n\n const atlas = this._modelAtlas();\n\n if (atlas) {\n this._showModel(atlas);\n } else {\n this.body.visible = true;\n this.gun.visible = true;\n }\n\n this._initSounds();\n this._addLights();\n }\n\n // текстура задаёт габариты квада: без пересчёта углов меш остался бы с\n // размерами прежней текстуры до первого кадра\n this._applyTilt();\n }\n\n update(data) {\n this._worldX = data[M1_X];\n this._worldY = data[M1_Y];\n this.rotation = data[M1_ANGLE];\n this._gunRotation = data[M1_GUN_ROTATION];\n // `|| 0`, как в конструкторе: без него короткий ряд даёт undefined,\n // clamp() возвращает NaN, и в `sound.rate` каждый кадр уезжает NaN\n // (сравнение `rate !== activeInstance.rate` для NaN всегда истинно)\n this._engineLoad = data[M1_ENGINE_LOAD] || 0;\n this._speed = Math.hypot(data[M1_VX] || 0, data[M1_VY] || 0);\n\n this._z = data[M1_Z] || 0;\n this._physLevel = data[M1_LEVEL] || 0;\n\n // `|| 0` здесь по той же причине, что у engineLoad: короткий ряд без\n // хвоста иначе уводит углы квада в NaN\n this._vz = data[M1_VZ] || 0;\n this._pitch = data[M1_PITCH] || 0;\n this._roll = data[M1_ROLL] || 0;\n\n // касание: детектор один и для своего танка, и для чужого, и общий с\n // `Dust.js` — оба парта получают один и тот же ряд снапшота, поэтому\n // связывать их колбэком не нужно, и отдельного поля «приземлился» в\n // кадре тоже\n const impact = landingImpact(this._prevVz, this._vz, landingConfig);\n\n if (impact > 0) {\n this._landImpact = impact;\n this._landTimer = landingConfig.duration;\n }\n\n this._prevVz = this._vz;\n\n // уровень ОТРИСОВКИ, а не физический (`renderLevel`): падающий танк\n // иначе рисовался бы слоем, тинтом и прозрачностью эстакады до самого\n // касания\n const level = renderLevel(data[M1_LEVEL], this._z);\n\n if (level !== this._level) {\n this._level = level;\n this.zIndex = levelZ(TANK_BASE_Z, level);\n }\n\n this._applyTilt();\n\n if (this._levelView && this._isLocal()) {\n this._levelView.set(level, this._worldX, this._worldY, this._z);\n }\n\n // обновление звуковой логики; страховка: живой танк без регистрации\n // (её мог снять частичный CLEAR) возвращает звук на следующем кадре\n if (this._condition > 0) {\n if (this._soundId === null) {\n this._initSounds();\n } else {\n this._soundManager.updateSoundData(this._soundId, this._getSoundData());\n }\n }\n\n const newCondition = data[M1_CONDITION];\n const teamId = data[M1_TEAM];\n\n let needsVisualChange = false;\n\n if (newCondition !== undefined && newCondition !== this._condition) {\n this._condition = newCondition;\n needsVisualChange = true;\n }\n\n if (teamId !== undefined && teamId !== this._teamId) {\n this._teamId = teamId;\n needsVisualChange = true;\n }\n\n // если визуальное представление требуется изменить\n if (needsVisualChange) {\n this.create();\n }\n\n // источники света живут в МИРОВЫХ координатах и обновляются по кадру\n // данных, до отрисовки: карта освещённости не отстаёт от корпуса\n this._updateLights();\n this._updateWaterSound();\n }\n\n // петля `tankWater`, пока живой танк стоит или едет по воде (не в\n // полёте). Брызги рисует Dust.js, звук живёт только здесь — иначе\n // дублировался бы\n _updateWaterSound() {\n const inWater =\n this._hasWaterSound &&\n this._condition > 0 &&\n this._vz === 0 &&\n this._surfaces?.kindAt(this._worldX, this._worldY, this._physLevel) ===\n 'water';\n\n if (!inWater) {\n this._removeWaterSound();\n return;\n }\n\n if (this._waterSoundId === null) {\n this._waterSoundId = this._soundManager.registerSound(\n 'tankWater',\n this._getWaterSoundData(),\n );\n } else {\n this._soundManager.updateSoundData(\n this._waterSoundId,\n this._getWaterSoundData(),\n );\n }\n }\n\n _getWaterSoundData() {\n const { minVolume, fullSpeed, rate } = surfaceFx.water.sound;\n const factor = clamp(this._speed / fullSpeed, 0, 1);\n\n return {\n position: { x: this._worldX, y: this._worldY },\n rate: lerp(rate.min, rate.max, factor),\n volume: this._baseWaterVolume * (minVolume + (1 - minVolume) * factor),\n // свой плеск — по центру и без HRTF, как свой двигатель (_getSoundData)\n spatial: !this._isLocal(),\n };\n }\n\n _removeWaterSound() {\n if (this._waterSoundId) {\n this._soundManager.unregisterSound(this._waterSoundId);\n this._waterSoundId = null;\n }\n }\n\n // Две фары на передней кромке корпуса (4 × size вдоль курса, 3 × size в\n // ширину — пропорция `tankTexture`), смещённые на ±offset полуширины\n _headlightPoints() {\n const cos = Math.cos(this.rotation);\n const sin = Math.sin(this.rotation);\n const front = this._size * 2;\n const side = this._size * 1.5 * lightingConfig.headlights.offset;\n const frontX = this._worldX + cos * front;\n const frontY = this._worldY + sin * front;\n\n return [\n { x: frontX + sin * side, y: frontY - cos * side },\n { x: frontX - sin * side, y: frontY + cos * side },\n ];\n }\n\n _addLights() {\n if (!this._lighting || this._headlights) {\n return;\n }\n\n const { headlights, tankGlow } = lightingConfig;\n const cone = () =>\n this._lighting.addLight({\n kind: 'cone',\n radius: headlights.length,\n spread: headlights.spread,\n color: headlights.color,\n intensity: headlights.intensity,\n });\n\n this._headlights = {\n cones: [cone(), cone()],\n glow: this._lighting.addLight({\n kind: 'radial',\n radius: tankGlow.radius,\n color: tankGlow.color,\n intensity: tankGlow.intensity,\n }),\n };\n\n this._updateLights();\n }\n\n _updateLights() {\n if (!this._headlights) {\n return;\n }\n\n const level = this._level;\n const z = this._z;\n // на рампе (`vz` кадра — точный флаг полёта, на рампе он 0) свет идёт в\n // оба соседних уровня: клин затемняет оверлей нижнего, плиту — верхнего\n const levels = lightLevels(this._physLevel, z, this._vz !== 0);\n const points = this._headlightPoints();\n\n for (let i = 0; i < 2; i += 1) {\n this._lighting.updateLight(this._headlights.cones[i], {\n x: points[i].x,\n y: points[i].y,\n z,\n level,\n levels,\n rotation: this.rotation,\n });\n }\n\n this._lighting.updateLight(this._headlights.glow, {\n x: this._worldX,\n y: this._worldY,\n z,\n level,\n levels,\n });\n }\n\n _removeLights() {\n if (this._headlights) {\n this._lighting.removeLight(this._headlights.cones[0]);\n this._lighting.removeLight(this._headlights.cones[1]);\n this._lighting.removeLight(this._headlights.glow);\n this._headlights = null;\n }\n }\n\n // выстрел этого танка: отдача стартует с нуля, а повторный выстрел во\n // время спада возвращает её на пик — при зажатом огне ствол держится\n // откаченным, а не дрожит от перезапусков\n _onFired() {\n if (!recoilConfig.enabled || this._condition === 0) {\n return;\n }\n\n const peak = recoilConfig.attack * recoilConfig.duration;\n\n this._recoilElapsed =\n this._recoilElapsed < recoilConfig.duration\n ? Math.min(this._recoilElapsed, peak)\n : 0;\n }\n\n // мировая (НЕсмещённая проекцией) точка дула: та же формула, что у ядра\n // (`Tank::muzzle_position` — 0.55 длины корпуса по курсу башни). По ней\n // трассер и вспышка держатся у ствола, пока танк едет. У остова дула нет\n _muzzleWorld() {\n if (this._condition === 0 || this.destroyed) {\n return null;\n }\n\n const angle = this.rotation + this._gunRotation;\n const reach = this._size * 4 * MUZZLE_REACH;\n\n return {\n x: this._worldX + Math.cos(angle) * reach,\n y: this._worldY + Math.sin(angle) * reach,\n };\n }\n\n _resetRecoil() {\n this._recoilElapsed = Infinity;\n this._recoilTilt.pitch = 0;\n this._recoilTilt.roll = 0;\n this._recoilOffsets.body.x = 0;\n this._recoilOffsets.body.y = 0;\n this._recoilOffsets.gun.x = 0;\n this._recoilOffsets.gun.y = 0;\n }\n\n // шаг отдачи по ВРЕМЕНИ, как просадка приземления: смещения мешей внутри\n // повёрнутого контейнера — это и есть оси корпуса. Длины — мировые\n // единицы, растут с высотой по той же проекции, что и сам корпус\n _stepRecoil() {\n if (this._recoilElapsed === Infinity) {\n return;\n }\n\n this._recoilElapsed += Ticker.shared.deltaMS;\n\n const amount = recoilAmount(\n this._recoilElapsed,\n recoilConfig.duration,\n recoilConfig.attack,\n );\n\n if (amount === 0 && this._recoilElapsed >= recoilConfig.duration) {\n this._resetRecoil();\n\n return;\n }\n\n const offsets = recoilOffsets({\n amount,\n gunRotation: this._gunRotation,\n config: recoilConfig,\n });\n\n this._recoilOffsets.body = offsets.body;\n this._recoilOffsets.gun = offsets.gun;\n this._recoilTilt.pitch = offsets.pitch;\n this._recoilTilt.roll = offsets.roll;\n }\n\n // взрыв рядом: танк сам решает, задел ли его взрыв. Новый толчок\n // заменяет текущий, только если он сильнее его остатка — слабый дальний\n // взрыв не гасит качку от близкого\n _onBlast(blast) {\n if (!blastJoltConfig.enabled || this.destroyed) {\n return;\n }\n\n const kick = blastKick({\n x: this._worldX,\n y: this._worldY,\n level: this._physLevel,\n heading: this.rotation,\n hullLength: this._size * 4,\n blast,\n config: blastJoltConfig,\n });\n\n if (!kick) {\n return;\n }\n\n const current = this._blastKick\n ? blastJoltState({\n elapsed: this._blastElapsed,\n kick: this._blastKick,\n config: blastJoltConfig,\n }).envelope\n : 0;\n\n if (kick.strength >= current) {\n this._blastKick = kick;\n this._blastElapsed = 0;\n }\n }\n\n // шаг реакции на взрыв по времени; на конце подброса — просадка\n // приземления той же силы\n _stepBlast() {\n if (!this._blastKick) {\n return;\n }\n\n const wasHopping =\n this._blastKick.hop && this._blastElapsed < blastJoltConfig.hopDuration;\n\n this._blastElapsed += Ticker.shared.deltaMS;\n\n const state = blastJoltState({\n elapsed: this._blastElapsed,\n kick: this._blastKick,\n config: blastJoltConfig,\n });\n\n if (\n wasHopping &&\n this._blastElapsed >= blastJoltConfig.hopDuration &&\n landingConfig.duration > 0\n ) {\n this._landImpact = this._blastKick.strength;\n this._landTimer = landingConfig.duration;\n }\n\n if (state.done) {\n this._resetBlast();\n\n return;\n }\n\n this._blastTilt.pitch = state.pitch;\n this._blastTilt.roll = state.roll;\n this._blastLift = state.lift;\n this._blastShake.x = state.shakeX;\n this._blastShake.y = state.shakeY;\n }\n\n _resetBlast() {\n this._blastKick = null;\n this._blastElapsed = 0;\n this._blastTilt.pitch = 0;\n this._blastTilt.roll = 0;\n this._blastLift = 0;\n this._blastShake.x = 0;\n this._blastShake.y = 0;\n }\n\n // видимая высота корпуса: физическая плюс подброс взрыва. По ней идут\n // проекция и масштаб корпуса; тень остаётся на опоре — разъезд с ней и\n // читается подбросом\n _viewZ() {\n return this._z + this._blastLift;\n }\n\n // смещения мешей внутри повёрнутого контейнера (оси корпуса): отдача и\n // встряска взрыва. Длины — мировые единицы, растут с высотой по той же\n // проекции, что и сам корпус\n _applyMeshOffsets() {\n const zScale = 1 + this._viewZ() * parallaxConfig.shear;\n const { body, gun } = this._recoilOffsets;\n const bodyX = body.x + this._blastShake.x;\n const bodyY = body.y + this._blastShake.y;\n\n this.body.position.set(bodyX * zScale, bodyY * zScale);\n this.wreck.position.set(\n this._blastShake.x * zScale,\n this._blastShake.y * zScale,\n );\n // башня едет вместе с корпусом и ещё откатывается сама\n this.gun.position.set((bodyX + gun.x) * zScale, (bodyY + gun.y) * zScale);\n }\n\n // текстура меша и, если есть карта нормалей, шейдер света. Без карты\n // (или при `tankLight.enabled: false`) меш остаётся батченым, как прежде\n _setTexture(mesh, texture, normal) {\n mesh.texture = texture;\n\n const shader = this._lightShaders.get(mesh);\n\n if (!tankLightConfig.enabled || !normal) {\n if (shader) {\n mesh.shader = null;\n shader.destroy();\n this._lightShaders.delete(mesh);\n }\n\n return;\n }\n\n if (shader) {\n setTankLightTextures(shader, texture, normal);\n } else {\n const created = createTankLightShader(texture, normal);\n\n mesh.shader = created;\n this._lightShaders.set(mesh, created);\n }\n }\n\n // uniforms света раз за кадр: курс, наклон и поворот пушки\n _applyLight() {\n for (const [mesh, shader] of this._lightShaders) {\n applyTankLight(\n shader,\n tankLightUniforms({\n heading: this.rotation,\n rotation: mesh === this.gun ? this._gunRotation : 0,\n pitch: tiltConfig.enabled\n ? this._pitch + this._recoilTilt.pitch + this._blastTilt.pitch\n : 0,\n roll: tiltConfig.enabled\n ? this._roll + this._recoilTilt.roll + this._blastTilt.roll\n : 0,\n lightDir: tiltConfig.lightDir,\n lightZ: tankLightConfig.lightZ,\n ambient: tankLightConfig.ambient,\n diffuse: tankLightConfig.diffuse,\n }),\n );\n }\n }\n\n // углы квада одного меша: масштаб высоты, просадка приземления и наклон\n // корпуса — один трансформ, потому что у PerspectiveMesh нет ни якоря, ни\n // осмысленного `scale` вокруг него\n _setCorners(mesh, anchor, rotation, size, squashY) {\n const { texture } = mesh;\n\n mesh.setCorners(\n ...tiltCorners({\n width: texture.width * size,\n height: texture.height * size * squashY,\n anchorX: anchor.x,\n anchorY: anchor.y,\n rotation,\n // курс нужен `lift`: поднявшийся край уезжает вверх по экрану\n heading: this.rotation,\n // выключенный наклон вырождает квад в обычный прямоугольник —\n // отдельной «плоской» ветки держать не нужно\n pitch: tiltConfig.enabled\n ? this._pitch + this._recoilTilt.pitch + this._blastTilt.pitch\n : 0,\n roll: tiltConfig.enabled\n ? this._roll + this._recoilTilt.roll + this._blastTilt.roll\n : 0,\n shear: parallaxConfig.shear,\n lift: tiltConfig.lift,\n }),\n );\n }\n\n // Высота ПОВЕРХНОСТИ под танком в уровнях. На плите и на рампе это сама\n // высота корпуса (танк лежит на опоре), в полёте — уровень отрыва: его\n // ядро держит в `level` всю дугу (`core/src/level.rs`, `step_airborne`).\n // По ней рисуется тень: разъезд корпуса с тенью обязан показывать\n // подъём НАД ОПОРОЙ, а не высоту над нулём карты — иначе стоящий на\n // верхнем ярусе танк уезжает от своей тени тем дальше, чем дальше он от\n // центра экрана.\n // `vz` в кадре — точный флаг: ядро специально не даёт ненулевой скорости\n // округлиться в ноль (`snapshot_row`, core/src/tank.rs)\n _groundZ() {\n return this._vz !== 0 ? this._physLevel : this._z;\n }\n\n // высота читается масштабом корпуса: танк на эстакаде крупнее наземного\n // ровно настолько же, насколько крупнее сама плита под ним — это та же\n // проекция, что и сдвиг (`src/client/parallax.js`)\n _applyTilt() {\n const zScale = 1 + this._viewZ() * parallaxConfig.shear;\n const size = this._scaleFactor * zScale;\n const squashY = 1 - this._squash;\n\n this._setCorners(this.body, this._bodyAnchor, 0, size, squashY);\n this._setCorners(\n this.gun,\n this._gunAnchor,\n this._gunRotation,\n size,\n squashY,\n );\n this._setCorners(this.wreck, this._wreckAnchor, 0, size, squashY);\n }\n\n // признаки уровня и высоты: прозрачность над игроком, затемнение под ним\n // и тень. Зовётся из `onRender` каждый кадр\n _updateView() {\n // проекция 2.5D: смещается КОРПУС — на свою высоту и тем сильнее, чем\n // дальше он от центра экрана (src/client/parallax.js). Тем же числом\n // смещается плита уровня, поэтому танк с неё не съезжает.\n // Раньше сдвигалась тень, а корпус стоял в мировой точке — проекция\n // была вывернута наизнанку, и тень выглядела выше танка\n // центр камеры добывает сервис — один раз на кадр и для всех партов\n // сразу (src/client/levelView.js): владельцем его был локальный танк, и\n // без него (наблюдатель, промежуток до респауна) камеры не было вовсе\n if (this._levelView) {\n this._levelView.attachStage(this.parent, this._renderer);\n }\n\n const camera = this._levelView\n ? this._levelView.camera()\n : cameraCenter(this.parent, this._renderer);\n\n // карты освещённости: реальная работа — раз на трансформ сцены\n if (this._lighting) {\n this._lighting.attachStage(this.parent, this._renderer);\n this._lighting.render();\n }\n\n // тинт УРОВНЯ — база светотени наклона: она множитель поверх него, и\n // считать её от `this.tint` нельзя — без сервиса уровней тот не\n // сбрасывается и корпус темнел бы кадр за кадром\n let baseTint = 0xffffff;\n\n if (this._levelView) {\n this.alpha = this._levelView.alphaFor(\n this._level,\n this._worldX,\n this._worldY,\n this._z,\n );\n baseTint = this._levelView.tintFor(this._level);\n this.tint = baseTint;\n }\n\n // реакция на взрыв — до проекции: подброс поднимает видимую высоту\n this._stepBlast();\n\n const view = offsetPoint(\n this._worldX,\n this._worldY,\n camera,\n this._viewZ() * parallaxConfig.shear,\n );\n\n this.position.set(view.x, view.y);\n\n // светотень наклона поверх тинта уровня: множитель, а не замена —\n // затемнение нижних ярусов обязано остаться. Со светом по карте\n // нормалей наклон уже учтён в шейдере, у 3D-модели — в свете граней\n // (`faceShade`), и второй раз его не кладём\n if (\n tiltConfig.enabled &&\n tiltConfig.shading &&\n this._lightShaders.size === 0 &&\n !this._model?.mesh.visible\n ) {\n const shade = clamp(\n tiltShade({\n angle: this.rotation,\n pitch: this._pitch,\n roll: this._roll,\n lightDir: tiltConfig.lightDir,\n shading: tiltConfig.shading,\n }),\n SHADE_MIN,\n SHADE_MAX,\n );\n\n this.tint = scaleTint(baseTint, shade);\n }\n\n // просадка идёт по ВРЕМЕНИ, а не по кадрам сети, поэтому живёт здесь.\n // `sin` даёт горб без кривых и библиотек: быстрый удар, мягкий возврат\n if (this._landTimer > 0) {\n this._landTimer = Math.max(0, this._landTimer - Ticker.shared.deltaMS);\n\n const t = this._landTimer / landingConfig.duration;\n\n this._squash =\n Math.sin(t * Math.PI) * landingConfig.squash * this._landImpact;\n } else {\n this._squash = 0;\n }\n\n this._stepRecoil();\n this._applyMeshOffsets();\n this._applyTilt();\n this._applyLight();\n this._applyModel(camera);\n\n this._updateShadow(camera);\n this._updateGlint();\n this._updateCaster();\n }\n\n // Засвет: блик на стороне корпуса, обращённой к сильнейшему чужому\n // источнику (фонарь, фара другого танка, вспышка). Градиент\n // `glintTexture` режется маской — спрайтом с текстурой корпуса (или\n // остова) в тех же габаритах; блик — последний ребёнок, поверх модели\n _updateGlint() {\n const glints = lightingConfig.glints;\n const asset = this._lighting?.texture('glint');\n const zScale = 1 + this._viewZ() * parallaxConfig.shear;\n const reach = this._size * 2 * zScale;\n const hit =\n glints?.enabled &&\n asset &&\n this._lighting.isNight() &&\n this._lighting.onScreen(this._worldX, this._worldY, this._viewZ(), reach)\n ? this._lighting.lightsAt(\n this._worldX,\n this._worldY,\n this._level,\n 1,\n this._headlights\n ? [...this._headlights.cones, this._headlights.glow]\n : null,\n )[0]\n : null;\n\n if (!hit) {\n if (this._glint) {\n this._glint.sprite.visible = false;\n }\n\n return;\n }\n\n if (!this._glint) {\n const sprite = new Sprite(asset.texture);\n const mask = new Sprite();\n\n sprite.anchor.set(0.5);\n sprite.blendMode = 'add';\n sprite.mask = mask;\n this.addChild(mask, sprite);\n this._glint = { sprite, mask };\n }\n\n const { sprite, mask } = this._glint;\n const dead = this._condition === 0;\n const source = dead ? this.wreck : this.body;\n const anchor = dead ? this._wreckAnchor : this._bodyAnchor;\n const size = this._scaleFactor * zScale;\n\n // модель могла лечь поверх после `_showModel`\n if (this.children[this.children.length - 1] !== sprite) {\n this.addChild(sprite);\n }\n\n mask.texture = source.texture;\n mask.anchor.set(anchor.x, anchor.y);\n mask.position.copyFrom(source.position);\n mask.scale.set(size, size * (1 - this._squash));\n\n sprite.texture = asset.texture;\n sprite.visible = true;\n sprite.position.copyFrom(source.position);\n sprite.rotation = hit.angle - this.rotation;\n sprite.scale.set(\n (Math.max(source.texture.width, source.texture.height) *\n size *\n glints.size) /\n asset.contentSize,\n );\n sprite.tint = hit.color;\n sprite.alpha = Math.min(1, glints.intensity * hit.strength);\n }\n\n // тень корпуса в лучах фонарей (`lighting.shafts.shadows`): танк —\n // круг с полудиагональю корпуса\n _updateCaster() {\n if (!this._lighting) {\n return;\n }\n\n this._lighting.setCaster(this, {\n x: this._worldX,\n y: this._worldY,\n z: this._z,\n level: this._level,\n radius: this._size * 2,\n });\n }\n\n // атлас модели своей команды; null — модель выключена или атласа нет\n _modelAtlas() {\n if (!this._modelAtlases) {\n return null;\n }\n\n return this._teamId === 2\n ? this._modelAtlases.liveTeamId2\n : this._modelAtlases.liveTeamId1;\n }\n\n // живой танк рисуется моделью: плоские корпус и башня прячутся\n _showModel(atlas) {\n if (!this._model) {\n this._model = createTankModelMesh(tankModelGeometry(), atlas);\n this.addChild(this._model.mesh);\n } else {\n this._model.setAtlas(atlas);\n }\n\n this._model.mesh.visible = true;\n this.body.visible = false;\n this.gun.visible = false;\n }\n\n // поза, проекция и свет модели раз за кадр. Курс — поворот контейнера,\n // всё остальное — в позе: тангаж и крен (с добавками отдачи и взрыва),\n // поворот башни, откат ствола, сдвиг корпуса (отдача и встряска),\n // просадка — сжатием по высоте. Наклон от центра экрана один на весь танк\n _applyModel(camera) {\n if (!this._model || !this._model.mesh.visible) {\n return;\n }\n\n const model = tankModelGeometry();\n const posed = poseModel({\n model,\n size: this._size,\n gunRotation: this._condition === 0 ? this._wreckSkew : this._gunRotation,\n pitch: tiltConfig.enabled\n ? this._pitch + this._recoilTilt.pitch + this._blastTilt.pitch\n : 0,\n roll: tiltConfig.enabled\n ? this._roll + this._recoilTilt.roll + this._blastTilt.roll\n : 0,\n gunKick: this._recoilOffsets.gun,\n shift: {\n x: this._recoilOffsets.body.x + this._blastShake.x,\n y: this._recoilOffsets.body.y + this._blastShake.y,\n },\n squash: this._squash,\n });\n\n const lean = modelLean({\n x: this._worldX,\n y: this._worldY,\n camera,\n heading: this.rotation,\n shear: parallaxConfig.shear,\n levelHeight: tankModelConfig.levelHeight,\n maxLean: tankModelConfig.maxLean,\n gain: tankModelConfig.leanGain,\n topHeight: (tankModelConfig.turretTop * this._size) / 10,\n });\n\n const projected = projectModel({\n points: posed.points,\n lean,\n zScale: 1 + this._viewZ() * parallaxConfig.shear,\n });\n\n // поза нужна и тени по силуэту (`_updateModelShadow`)\n this._modelPose = posed;\n\n const order = visibleFaces({ model, points: posed.points, projected });\n const light = modelLight();\n const shades = posed.normals.map(normal =>\n faceShade(normal, this.rotation, light),\n );\n\n this._model.update(projected, shades, order);\n }\n\n // тень по силуэту модели: живой танк с моделью (остов тени не\n // отбрасывает, как и прежде)\n _modelShadowActive() {\n return (\n this._condition !== 0 &&\n this._model !== null &&\n this._model.mesh.visible &&\n this._modelPose !== undefined &&\n this.parent !== null\n );\n }\n\n // Тень модели — `Graphics` сиблингом на сцене (как спрайт тени): точки\n // позы падают на опору вдоль света, по полигону на часть. Прозрачность —\n // фильтром на всю тень, иначе перекрытия частей темнели бы дважды.\n // Лежит на опоре в её проекции, повёрнута на курс\n _updateModelShadow(camera) {\n if (!this._modelShadow) {\n const shadow = new Graphics();\n\n this._modelShadowAlpha = new AlphaFilter({ alpha: 1 });\n shadow.filters = tankModelConfig.shadowBlur\n ? [\n new BlurFilter({\n strength: tankModelConfig.shadowBlur,\n quality: 2,\n }),\n this._modelShadowAlpha,\n ]\n : [this._modelShadowAlpha];\n this.parent.addChild(shadow);\n this._modelShadow = shadow;\n }\n\n const shadow = this._modelShadow;\n const groundZ = this._groundZ();\n const view = offsetPoint(\n this._worldX,\n this._worldY,\n camera,\n groundZ * parallaxConfig.shear,\n );\n // подъём над опорой (прыжок, подброс взрыва), уровни → мировые единицы\n const liftLevels = Math.max(0, this._viewZ() - groundZ);\n\n shadow.visible = true;\n shadow.position.set(view.x, view.y);\n shadow.rotation = this.rotation;\n shadow.scale.set(1 + groundZ * parallaxConfig.shear);\n shadow.zIndex = levelZ(\n TANK_BASE_Z - 1,\n Math.min(LEVEL_MAX, Math.floor(groundZ)),\n );\n\n const polygons = shadowPolygons({\n model: tankModelGeometry(),\n points: this._modelPose.points,\n lift: liftLevels * tankModelConfig.levelHeight,\n drift: shadowDrift(\n tiltConfig.lightDir,\n tankLightConfig.lightZ,\n this.rotation,\n ),\n });\n\n shadow.clear();\n\n for (const polygon of polygons) {\n shadow.poly(polygon.flat()).fill(0x000000);\n }\n\n const airborne = this._vz !== 0 || this._blastLift > 0;\n const base = airborne\n ? Math.max(\n 0,\n shadowConfig.baseAlpha - liftLevels * shadowConfig.alphaFalloff,\n )\n : shadowConfig.groundAlpha;\n\n this._modelShadowAlpha.alpha = base * this.alpha;\n }\n\n // Тень показывает две вещи: объём корпуса на земле и отрыв от опоры в\n // полёте. Она сдвинута ОТ света (`shadow.groundOffset`) — без сдвига\n // лежала бы ровно под корпусом и читалась серым ореолом, поэтому при\n // нулевом сдвиге она есть только в полёте. Признак полёта тот же, что у\n // `_groundZ`: ненулевая вертикальная скорость в кадре\n _updateShadow(camera) {\n // у модели своя тень — по силуэту\n if (this._modelShadowActive()) {\n if (this._shadow) {\n this._shadow.visible = false;\n }\n\n this._updateModelShadow(camera);\n\n return;\n }\n\n if (this._modelShadow) {\n this._modelShadow.visible = false;\n }\n\n if (!this._shadowAsset || !this.parent) {\n return;\n }\n\n const airborne = this._vz !== 0;\n const visible =\n this._condition !== 0 && (airborne || shadowConfig.groundOffset > 0);\n\n if (!visible) {\n // спрайта может не быть вовсе: танк, который ни разу не взлетал, его\n // и не заводит\n if (this._shadow) {\n this._shadow.visible = false;\n }\n\n return;\n }\n\n if (!this._shadow) {\n const { texture, contentSize } = this._shadowAsset;\n\n this._shadow = new Sprite(texture);\n this._shadow.anchor.set(0.5);\n // текстура тени уже в пропорции корпуса, поэтому масштаб один на обе\n // оси и нормируется по КОРПУСУ (4 × size вдоль курса), а не по size\n this._shadowScale =\n (this._size * 4 * shadowConfig.sizeFactor) / contentSize;\n this.parent.addChild(this._shadow);\n }\n\n const shadow = this._shadow;\n\n shadow.visible = true;\n\n const groundZ = this._groundZ();\n\n // тень лежит НА ОПОРЕ и живёт в той же проекции, что корпус и плита\n // под ним: разъезд с корпусом и есть высота отрыва. Падение с обрыва\n // оставляет её на покинутой плите — опорой ядро держит уровень отрыва\n const view = offsetPoint(\n this._worldX,\n this._worldY,\n camera,\n groundZ * parallaxConfig.shear,\n );\n\n // сдвиг — в осях ЭКРАНА, а не корпуса: свет один на всю сцену и с\n // танком не поворачивается\n const offset = shadowOffset();\n\n shadow.x = view.x + offset.x;\n shadow.y = view.y + offset.y;\n shadow.rotation = this.rotation;\n\n // масштаб и прозрачность читают ПОДЪЁМ над опорой, а не высоту над\n // нулём карты: тень на верхнем ярусе обязана быть такой же, как на\n // земле, — разной её делает только высота прыжка\n const lift = Math.max(0, this._z - groundZ);\n\n shadow.scale.set(this._shadowScale * (1 + lift * shadowConfig.scaleGain));\n const baseAlpha = airborne\n ? Math.max(0, shadowConfig.baseAlpha - lift * shadowConfig.alphaFalloff)\n : shadowConfig.groundAlpha;\n\n shadow.alpha = baseAlpha * this.alpha;\n\n // тень лежит на слое, НАД которым висит танк: на рампе это ещё нижний\n // уровень, и именно поэтому по ней видно, что танк уже поднялся\n // в прыжке `_z` уходит выше любой плиты карты, а слоёв всего LEVEL_MAX\n shadow.zIndex = levelZ(\n TANK_BASE_Z - 1,\n Math.min(LEVEL_MAX, Math.floor(groundZ)),\n );\n }\n\n // останавливает и сбрасывает все звуки, связанные с танком\n destroySounds() {\n if (this._soundId) {\n this._soundManager.unregisterSound(this._soundId);\n this._soundId = null;\n }\n\n this._removeWaterSound();\n }\n\n destroy(options) {\n this.destroySounds();\n\n // источники света и тень в лучах — состояние сессии сервиса: снимает\n // их владелец\n this._removeLights();\n this._lighting?.setCaster(this, null);\n\n // маска и блик — дети танка, их снимет `super.destroy`\n this._glint = null;\n\n // тень движок не создавал и не уберёт: она сиблинг на сцене\n if (this._shadow) {\n this._shadow.destroy({ texture: false, textureSource: false });\n this._shadow = null;\n }\n\n if (this._modelShadow) {\n this._modelShadow.destroy();\n this._modelShadow = null;\n }\n\n if (this._unsubscribeShots) {\n this._unsubscribeShots();\n this._unsubscribeShots = null;\n }\n\n if (this._unsubscribeBlasts) {\n this._unsubscribeBlasts();\n this._unsubscribeBlasts = null;\n }\n\n // свой шейдер меш не уничтожает; текстуры общие и остаются\n if (this._model) {\n this._model.destroy();\n this._model = null;\n }\n\n for (const shader of this._lightShaders.values()) {\n shader.destroy();\n }\n\n this._lightShaders.clear();\n\n super.destroy({\n children: true,\n texture: false, // текстуры общие, не должны уничтожаться\n textureSource: false,\n ...options,\n });\n }\n}\n","import { Text, Ticker, Container, Sprite } from 'pixi.js';\nimport { levelZ } from '../levelZ.js';\nimport { cameraCenter } from '../camera.js';\nimport { offsetPoint } from '../parallax.js';\nimport { parallax as parallaxConfig } from '../../config/render.js';\nimport {\n W2_X,\n W2_Y,\n W2_ANGLE,\n W2_SIZE,\n W2_TIME,\n W2_LEVEL,\n} from '../snapshotFields.js';\n\n// базовый zIndex бомбы внутри своего уровня\nconst BOMB_BASE_Z = 2;\n\nexport default class Bomb extends Container {\n constructor(params, assets, dependencies) {\n super();\n\n // 2.5D: уровень, на котором лежит бомба (строка w2)\n this._level = params[W2_LEVEL] || 0;\n this.zIndex = levelZ(BOMB_BASE_Z, this._level);\n\n // бомба на мосту гаснет вместе с плитой, бомба под мостом темнеет:\n // единая формула прозрачности живёт в сервисе levelView\n this._levelView = dependencies.levelView || null;\n\n // `onRender` — аксессор Container: назначаем свойством, иначе сеттер\n // не отработает и колбэк не позовётся ни разу\n this._renderer = dependencies.renderer || null;\n\n if (this._levelView || this._renderer) {\n this.onRender = () => {\n if (this._levelView) {\n this.alpha = this._levelView.alphaFor(\n this._level,\n this._worldX,\n this._worldY,\n );\n this.tint = this._levelView.tintFor(this._level);\n }\n\n this._applyHeight();\n };\n }\n\n this.body = new Sprite(assets.bombTexture);\n this.body.anchor.set(0.5);\n\n // мировая (НЕсмещённая) точка: `this.position` каждый кадр\n // перезаписывается проекцией высоты (см. _applyHeight)\n this._worldX = params[W2_X];\n this._worldY = params[W2_Y];\n this.x = this._worldX;\n this.y = this._worldY;\n\n this.rotation = params[W2_ANGLE];\n this._size = params[W2_SIZE]; // соотношение сторон 1:1\n this._totalDurationMs = params[W2_TIME];\n\n this._soundManager = dependencies.soundManager;\n this._soundId = null;\n\n // спрайт под нужный размер\n // текстура квадратная\n const textureSize = assets.bombTexture.width;\n const scale = this._size / textureSize;\n this.body.scale.set(scale);\n\n // фиксированный размер шрифта для рендера текстуры,\n // чтобы текст был четким (векторное качество)\n const renderFontSize = 64;\n\n // масштаб под размер бомбы\n const fontScale = this._size / 1.2 / renderFontSize;\n\n this.text = new Text({\n style: {\n fontFamily: 'Arial',\n fontSize: renderFontSize,\n fill: 0xffffff,\n align: 'center',\n },\n });\n\n this.text.anchor.set(0.5);\n this.text.scale.set(fontScale);\n this.text.x = 0;\n this.text.y = 0;\n\n // накопленное время с момента создания\n this._accumulatedTimeMs = 0;\n this._secondsLeft = '';\n\n this.addChild(this.body, this.text);\n this._updateTimerDisplay(this._totalDurationMs);\n\n this._tickListener = ticker => this._updateTimer(ticker.deltaMS);\n Ticker.shared.add(this._tickListener);\n\n this._soundId = this._soundManager.registerSound('bombHasBeenPlanted', {\n position: {\n x: this.x,\n y: this.y,\n },\n });\n }\n\n // обновление таймера и звука\n _updateTimer(deltaMs) {\n this._accumulatedTimeMs += deltaMs;\n\n const remainingMs = Math.max(\n 0,\n this._totalDurationMs - this._accumulatedTimeMs,\n );\n\n this._updateTimerDisplay(remainingMs);\n\n if (remainingMs <= 0) {\n this._stopTimer();\n this._updateTimerDisplay(0);\n }\n }\n\n _updateTimerDisplay(remainingMs) {\n const time = Math.round(remainingMs / 1000);\n\n if (this._secondsLeft !== time) {\n this._secondsLeft = time;\n this.text.text = `${this._secondsLeft}`;\n }\n }\n\n // авторитетная строка приходит один раз — подтверждением локально\n // предсказанной бомбы: переносим сущность в авторитетную точку\n update(params) {\n this._worldX = params[W2_X];\n this._worldY = params[W2_Y];\n this.x = this._worldX;\n this.y = this._worldY;\n this.rotation = params[W2_ANGLE];\n\n if (this._soundId) {\n const alive = this._soundManager.updateSoundData(this._soundId, {\n position: { x: this._worldX, y: this._worldY },\n });\n\n // регистрацию мог снять reset(); перерегистрировать нечего —\n // сэмпл постановки одноразовый\n if (!alive) {\n this._soundId = null;\n }\n }\n }\n\n // Проекция высоты: бомба на мосту рисуется смещённой и увеличенной ровно\n // так же, как сама плита под ней. Позиция лежит на самом контейнере,\n // поэтому applyParallax тут не применим — сдвиг считается от мировой\n // точки, а масштаб ставится отдельно. Высоты в строке `w2` нет: бомба\n // лежит на плите своего уровня\n _applyHeight() {\n const k = this._level * parallaxConfig.shear;\n const camera = cameraCenter(this.parent, this._renderer);\n const view = offsetPoint(this._worldX, this._worldY, camera, k);\n\n this.position.set(view.x, view.y);\n this.scale.set(1 + k);\n }\n\n _stopTimer() {\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n }\n\n destroy(options) {\n this._stopTimer();\n\n if (this._soundId) {\n // одноразовый сэмпл постановки живёт дольше самой бомбы (её убирает\n // детонация через weapon.time) — отпускаем, а не обрываем\n this._soundManager.releaseSound(this._soundId);\n this._soundId = null;\n }\n\n super.destroy({\n children: true,\n texture: false,\n textureSource: false,\n ...options,\n });\n\n this.body = null;\n this.text = null;\n }\n}\n","import { Sprite } from 'pixi.js';\nimport BaseEffect from '../BaseEffect.js';\n\nexport default class ExplosionEffect extends BaseEffect {\n constructor(x, y, radius, onComplete, assets) {\n super(onComplete); // конструктор BaseEffect\n\n // ассет взрыва: текстура + диаметр нарисованного круга\n const { texture, contentSize } = assets.explosionTexture;\n\n this.x = x;\n this.y = y;\n this._radius = radius;\n\n this._durationMs = 1500;\n this._elapsedMs = 0;\n\n // масштаб нормируется по нарисованному кругу, а не по холсту:\n // запас под размытие не должен влиять на видимый размер\n this._baseScale = (this._radius * 2) / contentSize;\n\n // спрайты\n // основное тело взрыва (голубое)\n this._mainBody = new Sprite(texture);\n this._mainBody.anchor.set(0.5);\n this._mainBody.tint = 0xadd8e6; // светло-голубой\n this._mainBody.scale.set(this._baseScale);\n\n // коллапсирующее ядро (серое)\n this._core = new Sprite(texture);\n this._core.anchor.set(0.5);\n this._core.tint = 0xd3d3d3; // светло-серый\n this._core.scale.set(this._baseScale); // начальный размер тот же\n\n this.addChild(this._mainBody, this._core);\n }\n\n // обновление анимации\n _update(deltaMs) {\n if (this.isComplete) {\n // проверка из BaseEffect\n return;\n }\n\n this._elapsedMs += deltaMs;\n\n if (this._elapsedMs <= this._durationMs) {\n const progress = this._elapsedMs / this._durationMs;\n this._draw(progress);\n } else {\n // метод из BaseEffect\n this._completeEffect();\n }\n }\n\n // отрисовка\n _draw(progress) {\n const easeInQuad = t => t * t;\n\n // анимация основного тела (затухание)\n this._mainBody.alpha = 0.6 * (1 - easeInQuad(progress));\n\n // анимация ядра (затухание и сжатие)\n const coreProgress = Math.min(1, progress * 1.5);\n this._core.alpha = 0.7 - coreProgress;\n this._core.scale.set(this._baseScale * (1 - coreProgress));\n }\n\n // уничтожение объекта\n destroy(options) {\n // вызов destroy из BaseEffect\n super.destroy(options);\n }\n}\n","import { Particle } from 'pixi.js';\n\nconst pool = [];\n\nexport default {\n get(texture) {\n if (pool.length > 0) {\n const particle = pool.pop();\n particle.texture = texture;\n // Сброс базовых параметров, чтобы не наследовать состояние с прошлой жизни\n particle.alpha = 1;\n particle.scaleX = 1;\n particle.scaleY = 1;\n particle.rotation = 0;\n particle.tint = 0xffffff;\n particle.anchorX = 0.5;\n particle.anchorY = 0.5;\n return particle;\n }\n return new Particle({ texture, anchorX: 0.5, anchorY: 0.5 });\n },\n\n // вызывающий обязан сам удалить частицу из ParticleContainer\n // (container.removeParticle) перед возвратом в пул, если она ещё жива в сцене\n release(particle) {\n if (!particle) {\n return;\n }\n\n pool.push(particle);\n },\n};\n","import { ParticleContainer, Rectangle } from 'pixi.js';\nimport ParticlePool from '../../ParticlePool.js';\nimport BaseEffect from '../BaseEffect.js';\n\n// радиус взрыва, под который подобраны размеры дыма (бомба w2):\n// та же эталонная бомба стоит за геометрией воронки в FunnelEffect\n// у оружия с другим радиусом геометрия султана масштабируется пропорционально,\n// а времена жизни и прозрачность остаются - они задают характер, не габарит\nexport const REFERENCE_BLAST_RADIUS = 50;\n\n// размеры области разлёта частиц дыма эталонного взрыва вокруг локального\n// центра эффекта (0, 0); масштабируются вместе с султаном, иначе\n// ParticleContainer отсечёт частицы крупного взрыва\nconst BOUNDS_WIDTH = 400;\nconst BOUNDS_HEIGHT = 800;\n\nexport default class SmokeEffect extends BaseEffect {\n constructor(assets, radius = REFERENCE_BLAST_RADIUS) {\n super();\n\n const blastScale = radius / REFERENCE_BLAST_RADIUS;\n\n this._blastScale = blastScale;\n\n const { texture, contentSize } = assets.explosionTexture;\n\n this.explosionTexture = texture;\n\n // размеры частиц задаются в юнитах мира,\n // масштаб нормируется по нарисованному кругу (не по холсту с размытием)\n this._unitScale = 1 / contentSize;\n\n const boundsWidth = BOUNDS_WIDTH * blastScale;\n const boundsHeight = BOUNDS_HEIGHT * blastScale;\n\n this._particleContainer = new ParticleContainer({\n texture: this.explosionTexture,\n // создаётся per-instance, а не как общая константа,\n // чтобы не делить один мутируемый Rectangle между эффектами\n boundsArea: new Rectangle(\n -boundsWidth / 2,\n -boundsHeight / 2,\n boundsWidth,\n boundsHeight,\n ),\n dynamicProperties: {\n position: true,\n vertex: true,\n rotation: true,\n color: true,\n },\n });\n this.addChild(this._particleContainer);\n\n // симуляция каждой частицы хранится отдельно от Particle\n // (у Particle нет customData)\n this._particles = [];\n this._isSpawning = true;\n\n this._particleSpawnRateMs = 50;\n this._particleMaxLifeMs = 2000;\n\n // Размеры (в юнитах мира)\n this._minStartSize = 2.1 * blastScale;\n this._maxStartSize = 5.2 * blastScale;\n this._minTargetSize = 8.3 * blastScale;\n this._maxTargetSize = 16.6 * blastScale;\n\n this._startAlpha = 0.1;\n this._initialOffsetX = 15 * blastScale;\n this._initialOffsetY = 15 * blastScale;\n\n this._lastSpawnTime = 0;\n\n // стартовый залп\n for (let i = 0; i < 30; i += 1) {\n this._createParticle();\n }\n }\n\n stopSpawning() {\n this._isSpawning = false;\n }\n\n _createParticle() {\n // получение из пула вместо new Particle\n const view = ParticlePool.get(this.explosionTexture);\n\n // вариация цвета\n const grayLevel = 0.2 + Math.random() * 0.4;\n const colorVal = Math.floor(grayLevel * 255);\n\n view.tint = (colorVal << 16) | (colorVal << 8) | colorVal;\n\n // размер\n const startScale =\n (this._minStartSize +\n Math.random() * (this._maxStartSize - this._minStartSize)) *\n this._unitScale;\n\n // искажение пропорций\n const aspectX = 0.6 + Math.random() * 0.8;\n const aspectY = 0.6 + Math.random() * 0.8;\n\n view.scaleX = startScale * aspectX;\n view.scaleY = startScale * aspectY;\n\n view.alpha = this._startAlpha + Math.random() * 0.1;\n view.rotation = Math.random() * Math.PI * 2;\n\n // начальная позиция\n view.x = (Math.random() - 0.5) * this._initialOffsetX;\n view.y = (Math.random() - 0.5) * this._initialOffsetY;\n\n const particle = {\n view,\n life: 0,\n maxLife: this._particleMaxLifeMs * (0.7 + Math.random() * 0.6),\n aspectRatioX: aspectX,\n aspectRatioY: aspectY,\n\n // движение (скорости - тоже геометрия: иначе крупный султан\n // поднимался бы непропорционально медленно)\n vx: (Math.random() - 0.5) * 0.2 * this._blastScale,\n vy: (-0.3 - Math.random() * 0.4) * this._blastScale,\n\n // рыскание (Sway): амплитуда - смещение, частота - характер\n swaySpeed: 0.002 + Math.random() * 0.003,\n swayAmp: (0.025 + Math.random() * 0.05) * this._blastScale,\n swayOffset: Math.random() * 100,\n rotationSpeed: (Math.random() - 0.5) * 0.05,\n targetScale:\n (this._minTargetSize +\n Math.random() * (this._maxTargetSize - this._minTargetSize)) *\n this._unitScale,\n startScale,\n };\n\n this._particleContainer.addParticle(view);\n this._particles.push(particle);\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n // ограничение в 100мс,\n // чтобы предотвратит спавн тысячи частиц\n deltaMs = Math.min(deltaMs, 100);\n\n if (this._isSpawning) {\n this._lastSpawnTime += deltaMs;\n\n while (this._lastSpawnTime > this._particleSpawnRateMs) {\n this._createParticle();\n this._lastSpawnTime -= this._particleSpawnRateMs;\n }\n }\n\n for (let i = this._particles.length - 1; i >= 0; i -= 1) {\n const particle = this._particles[i];\n const view = particle.view;\n\n particle.life += deltaMs;\n\n if (particle.life >= particle.maxLife) {\n // возвращение в пул\n this._particleContainer.removeParticle(view);\n ParticlePool.release(view);\n this._particles.splice(i, 1);\n } else {\n const progress = particle.life / particle.maxLife;\n\n // физика\n particle.vy *= 0.98;\n particle.vx *= 0.95;\n\n const sway =\n Math.sin(particle.life * particle.swaySpeed + particle.swayOffset) *\n particle.swayAmp;\n\n view.x += (particle.vx + sway) * (deltaMs / 16);\n view.y += particle.vy * (deltaMs / 16);\n view.rotation += particle.rotationSpeed * (deltaMs / 16);\n\n // масштаб\n const ease = 1 - Math.pow(1 - progress, 3);\n const currentBaseScale =\n particle.startScale +\n (particle.targetScale - particle.startScale) * ease;\n\n view.scaleX = currentBaseScale * particle.aspectRatioX;\n view.scaleY = currentBaseScale * particle.aspectRatioY;\n\n // альфа\n if (progress < 0.1) {\n view.alpha = (progress / 0.1) * this._startAlpha;\n } else if (progress > 0.4) {\n const fadeP = (progress - 0.4) / 0.6;\n view.alpha = this._startAlpha * (1 - fadeP);\n } else {\n view.alpha = this._startAlpha;\n }\n }\n }\n\n if (!this._isSpawning && this._particles.length === 0 && this._isStarted) {\n this._completeEffect();\n\n // destroy, если эффект завершен, чтобы отписаться от тикера\n if (!this.destroyed) {\n this.destroy();\n }\n }\n }\n\n destroy(options) {\n // при уничтожении эффекта возвращение живых частиц в пул\n for (let i = 0; i < this._particles.length; i += 1) {\n ParticlePool.release(this._particles[i].view);\n }\n\n this._particles = [];\n\n // super.destroy уничтожит ParticleContainer как обычного ребёнка\n // (children: true из BaseEffect); частицы, уже возвращённые в пул,\n // повторно не трогаются\n super.destroy(options);\n }\n}\n","import { Sprite } from 'pixi.js';\nimport { clamp, randomRange } from 'vimp-engine/lib/math.js';\nimport SmokeEffect from './SmokeEffect.js';\nimport BaseEffect from '../BaseEffect.js';\n\n// константы для _createFunnelSprite\n// диаметр видимой воронки как доля от радиуса взрыва:\n// след масштабируется вместе с мощностью оружия\n// доля подобрана под эталонную бомбу REFERENCE_BLAST_RADIUS\nconst FUNNEL_TO_BLAST_RATIO = 0.168;\n// базовая прозрачность: след едва читается и не спорит с игровыми объектами\nconst FUNNEL_BASE_ALPHA = 0.225;\n// разброс размера воронок относительно базового\nconst FUNNEL_SIZE_JITTER = 0.15;\n\n// времена жизни следа и его фаз\nconst FUNNEL_DURATION_MS = 20000;\n// проявление: воронка поднимается вместе со вспышкой и под ней же,\n// поэтому фаза короткая - она снимает щелчок, а не читается как анимация\n// 0 - проявление мгновенное\nconst FUNNEL_FADE_IN_DURATION_MS = 50;\nconst FUNNEL_FADE_DURATION_MS = 4000; // затухание\nconst SMOKE_DURATION_MS = 4000; // время дыма\n\nexport default class FunnelEffect extends BaseEffect {\n constructor(x, y, radius, onComplete, assets) {\n super(onComplete);\n\n this._assets = assets;\n this._radius = radius;\n this.x = x;\n this.y = y;\n this._funnelDurationMs = FUNNEL_DURATION_MS;\n this._funnelFadeInDurationMs = FUNNEL_FADE_IN_DURATION_MS;\n this._funnelFadeDurationMs = FUNNEL_FADE_DURATION_MS;\n this._elapsedMs = 0;\n this._smokeDurationMs = SMOKE_DURATION_MS;\n this._smokeSpawningStopped = false;\n\n this._funnel = this._createFunnelSprite();\n this._smoke = new SmokeEffect(this._assets, this._radius);\n\n this.addChild(this._funnel, this._smoke);\n }\n\n _createFunnelSprite() {\n // ассет - набор силуэтов, воронка берёт случайный\n const { textures, contentSize } = this._assets.funnelTexture;\n const texture = textures[Math.floor(Math.random() * textures.length)];\n\n // текстура уже двухтоновая, tint не применяется\n const funnelSprite = new Sprite(texture);\n funnelSprite.anchor.set(0.5);\n funnelSprite.alpha = 0; // проявляется в _update\n\n // поворот и разброс размера, чтобы воронки не повторялись\n funnelSprite.rotation = Math.random() * Math.PI * 2;\n\n const size =\n this._radius *\n FUNNEL_TO_BLAST_RATIO *\n randomRange(1 - FUNNEL_SIZE_JITTER, 1 + FUNNEL_SIZE_JITTER);\n\n // масштаб нормируется по силуэту, а не по холсту:\n // запас под размытие не влияет на видимый размер воронки\n funnelSprite.scale.set(size / contentSize);\n\n return funnelSprite;\n }\n\n _onEffectStart() {\n // переопределение хука из BaseEffect\n // на случай, если в базовом классе появится логика\n super._onEffectStart();\n if (this._smoke) {\n this._smoke.run();\n }\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n this._elapsedMs += deltaMs;\n\n // если время дыма закончилось\n if (\n !this._smokeSpawningStopped &&\n this._elapsedMs >= this._smokeDurationMs\n ) {\n this._smoke.stopSpawning();\n this._smokeSpawningStopped = true;\n }\n\n const timeLeft = this._funnelDurationMs - this._elapsedMs;\n\n // воронка проступает со вспышкой и затухает к концу жизни\n // нулевое проявление - деления нет, воронка видна сразу\n const fadeIn =\n this._funnelFadeInDurationMs > 0\n ? this._elapsedMs / this._funnelFadeInDurationMs\n : 1;\n const fadeOut = timeLeft / this._funnelFadeDurationMs;\n\n this._funnel.alpha =\n FUNNEL_BASE_ALPHA * clamp(Math.min(fadeIn, fadeOut), 0, 1);\n\n if (timeLeft <= 0) {\n this._completeEffect(); // метод из BaseEffect\n }\n }\n\n destroy(options) {\n // уничтожение эффекта перед super.destroy()\n if (this._smoke) {\n this._smoke.destroy();\n this._smoke = null;\n }\n\n this._funnel = null;\n\n super.destroy(options);\n }\n}\n","import { Container } from 'pixi.js';\nimport ExplosionEffect from './ExplosionEffect.js';\nimport FunnelEffect from './FunnelEffect.js';\nimport { REFERENCE_BLAST_RADIUS } from './SmokeEffect.js';\nimport { levelZ } from '../../../levelZ.js';\nimport { cameraCenter } from '../../../camera.js';\nimport { offsetPoint } from '../../../parallax.js';\nimport {\n parallax as parallaxConfig,\n lighting as lightingConfig,\n} from '../../../../config/render.js';\nimport {\n W2E_X,\n W2E_Y,\n W2E_RADIUS,\n W2E_LEVEL,\n} from '../../../snapshotFields.js';\n\n// базовые zIndex вспышки и воронки внутри своего уровня\nconst EXPLOSION_BASE_Z = 4;\nconst FUNNEL_BASE_Z = 2;\n\nexport default class ExplosionEffectController extends Container {\n constructor(data, assets, dependencies) {\n super();\n\n this.originX = data[W2E_X];\n this.originY = data[W2E_Y];\n // радиус - единственный источник масштаба для вспышки, воронки и дыма:\n // гард здесь избавляет всех троих от NaN, если сервер его не прислал\n this.radius = data[W2E_RADIUS] ?? REFERENCE_BLAST_RADIUS;\n // 2.5D: уровень взрыва — плита моста экранирует его и вверх, и вниз,\n // значит и рисуется он только на своём слое\n this._level = data[W2E_LEVEL] || 0;\n\n this._assets = assets;\n this._soundManager = dependencies.soundManager;\n this._levelView = dependencies.levelView || null;\n this._renderer = dependencies.renderer || null;\n // ночь: вспышка кратко подсвечивает окрестность (no-op днём)\n this._lighting = dependencies.lighting || null;\n\n // танки в радиусе играют визуальную реакцию (src/client/blastJolt.js).\n // Эффект создаётся раз на взрыв, радиус — физический, из ядра\n dependencies.blasts?.exploded({\n x: this.originX,\n y: this.originY,\n radius: this.radius,\n level: this._level,\n });\n\n // вспышка и воронка живут СИБЛИНГАМИ на сцене (у них свои zIndex),\n // поэтому прозрачность ставится им, а не контроллеру.\n // `onRender` — аксессор Container, назначается свойством\n if (this._levelView || this._renderer) {\n this.onRender = () => {\n if (this._levelView) {\n this._updateSeeThrough();\n }\n\n this._applyHeight();\n };\n }\n\n this.x = this.originX;\n this.y = this.originY;\n\n this.explosion = null;\n this.funnel = null;\n this._isStarted = false;\n this._isDestroyed = false;\n\n this._soundId = this._soundManager.registerSound('explosion', {\n position: {\n x: this.originX,\n y: this.originY,\n },\n });\n }\n\n _updateSeeThrough() {\n const alpha = this._levelView.alphaFor(\n this._level,\n this.originX,\n this.originY,\n );\n const tint = this._levelView.tintFor(this._level);\n\n if (this.explosion) {\n this.explosion.alpha = alpha;\n this.explosion.tint = tint;\n }\n\n if (this.funnel) {\n this.funnel.alpha = alpha;\n this.funnel.tint = tint;\n }\n }\n\n // Проекция высоты: взрыв на мосту стоит на мосту, а не на земле под ним —\n // тот же сдвиг и тот же масштаб, что у плиты (`src/client/parallax.js`).\n // Вспышка и воронка живут сиблингами на сцене, поэтому проекция ставится\n // каждой из них, а не контроллеру\n _applyHeight() {\n const k = this._level * parallaxConfig.shear;\n const camera = cameraCenter(this.parent, this._renderer);\n const view = offsetPoint(this.originX, this.originY, camera, k);\n\n for (const target of [this.explosion, this.funnel]) {\n if (target) {\n target.position.set(view.x, view.y);\n target.scale.set(1 + k);\n }\n }\n }\n\n // вспышка и воронка с дымом поднимаются вместе: воронка живёт много дольше\n // вспышки, и именно её завершение уничтожает контроллер\n run() {\n // повторный run поднял бы вторую пару эффектов поверх первой,\n // потеряв ссылки на неё: старая пара осталась бы на сцене и в тикере\n if (this._isDestroyed || this._isStarted) {\n return;\n }\n\n this._isStarted = true;\n\n // сцену очистили до старта - поднимать эффекты некуда, а ждать нечего:\n // уничтожает контроллер только завершение воронки, которой не будет\n if (!this.parent) {\n this.destroy();\n return;\n }\n\n // свет вспышки — на уровне взрыва: плита моста его экранирует\n this._lighting?.flash({\n ...lightingConfig.flash.explosion,\n level: this._level,\n x: this.originX,\n y: this.originY,\n z: this._level,\n });\n\n this.explosion = new ExplosionEffect(\n this.originX,\n this.originY,\n this.radius,\n this._onExplosionComplete.bind(this),\n this._assets,\n );\n\n this.explosion.zIndex = levelZ(EXPLOSION_BASE_Z, this._level);\n\n this.funnel = new FunnelEffect(\n this.originX,\n this.originY,\n this.radius,\n this._onFunnelComplete.bind(this),\n this._assets,\n );\n\n this.funnel.zIndex = levelZ(FUNNEL_BASE_Z, this._level);\n\n // эффекты добавляются вне GameView.add - порядок слоёв пересчитывается тут,\n // одной сортировкой на оба\n this.parent.addChild(this.explosion, this.funnel);\n this.parent.sortChildren();\n\n this.explosion.run();\n this.funnel.run();\n }\n\n _onExplosionComplete() {\n if (this._isDestroyed) {\n return;\n }\n\n if (this.explosion) {\n this.explosion.destroy();\n this.explosion = null;\n }\n }\n\n _onFunnelComplete() {\n if (this._isDestroyed) {\n return;\n }\n\n // когда исчезает воронка (она длится дольше взрыва),\n // уничтожение всего контроллера\n this.destroy();\n }\n\n destroy() {\n if (this._isDestroyed) {\n return;\n }\n\n this._isDestroyed = true;\n\n if (this._soundId) {\n this._soundManager.unregisterSound(this._soundId);\n this._soundId = null;\n }\n\n if (this.explosion) {\n this.explosion.destroy();\n this.explosion = null;\n }\n\n if (this.funnel) {\n this.funnel.destroy();\n this.funnel = null;\n }\n\n if (this.parent) {\n this.parent.removeChild(this);\n }\n\n super.destroy({\n children: true,\n });\n }\n}\n","import { Container, ParticleContainer, Rectangle, Ticker } from 'pixi.js';\nimport ParticlePool from './ParticlePool.js';\nimport { lerp, randomRange } from 'vimp-engine/lib/math.js';\nimport { levelZ } from '../levelZ.js';\nimport { cameraCenter } from '../camera.js';\nimport { applyParallax } from '../parallax.js';\nimport { parallax as parallaxConfig } from '../../config/render.js';\nimport {\n M1_X,\n M1_Y,\n M1_ANGLE,\n M1_VX,\n M1_VY,\n M1_ENGINE_LOAD,\n M1_CONDITION,\n M1_SIZE,\n M1_Z,\n M1_LEVEL,\n} from '../snapshotFields.js';\n\n// базовый zIndex дыма внутри своего уровня\nconst SMOKE_BASE_Z = 4;\n\n// щедрый отступ вокруг эмиттера для boundsArea:\n// покрывает разлёт частиц с учётом скорости танка и порыва при смене состояния\nconst BOUNDS_PADDING = 400;\n\n// глобальные переменные для ветра\nconst globalWindX = 0;\nconst globalWindY = 0;\n\n// базовый видимый размер частицы дыма в юнитах мира:\n// нормировка по нарисованному кругу текстуры делает вид частиц независимым\n// от запаса под размытие в запечённом ассете\nconst SMOKE_PARTICLE_BASE_SIZE = 8;\n\nconst SMOKE_CONFIG = {\n // размеры и жизнь частиц\n particleLifetime: { min: 800, max: 1800 },\n\n // конфигурация размеров:\n // 1 (сильный урон) - самый крупный дым\n // 2 (средний урон) - средний дым\n // 0 (уничтожен) - маленький дым (тление)\n particleStartSizeFactor: { 1: 2.0, 2: 1.0, 0: 0.25 },\n particleEndSizeFactor: { 1: 3.0, 2: 2.0, 0: 1.0 },\n\n // прозрачность\n particleStartAlpha: 0.08,\n particleEndAlpha: 0.0,\n\n particleColor: 0x333333,\n\n // конфигурация спавна (частиц в секунду):\n // 1 - густой поток\n // 2 - умеренный поток\n // 0 - редкий дым\n particleSpawnRate: { 1: 60, 2: 50, 0: 30 },\n\n // направление дыма\n particleInitialVelocity: {\n away: { min: -10, max: 100 },\n side: { min: -30, max: 30 },\n },\n\n // сопротивление воздуха\n airResistance: 0.92,\n\n particleVelocityVariance: 10,\n\n // сдвиг труб друг относительно друга\n streamOffsetFactor: 0.1,\n\n // количество частиц при \"взрыве\" (смене состояния)\n // эффект резкого облака дыма при получении урона\n burstParticleCount: 3,\n\n // выхлоп по газу: канал, независимый от дыма повреждений\n exhaust: {\n // частиц в секунду при полном газе\n spawnRate: 45,\n // частиц в секунду при холостом ходе (лёгкое дрожание над трубой)\n idleSpawnRate: 4,\n // ниже этой нагрузки выхлопа нет вовсе\n minLoad: 0.05,\n // размер относительно дыма повреждений\n startSizeFactor: 0.6,\n endSizeFactor: 1.6,\n startAlpha: 0.05,\n color: 0x555555,\n lifetime: { min: 350, max: 750 },\n },\n};\n\nexport default class Smoke extends Container {\n constructor(data, assets, dependencies = {}) {\n super();\n\n this._level = data[M1_LEVEL] || 0;\n this.zIndex = levelZ(SMOKE_BASE_Z, this._level);\n\n const { texture, contentSize } = assets.smokeTexture;\n\n this._smokeTexture = texture;\n this._textureScale = SMOKE_PARTICLE_BASE_SIZE / contentSize;\n\n this._emitterX = data[M1_X];\n this._emitterY = data[M1_Y];\n this._emitterRotation = data[M1_ANGLE];\n this._emitterVX = data[M1_VX];\n this._emitterVY = data[M1_VY];\n this._engineLoad = data[M1_ENGINE_LOAD];\n this._condition = data[M1_CONDITION];\n this._z = data[M1_Z] || 0;\n\n this._size = data[M1_SIZE];\n this._width = this._size * 4;\n this._height = this._size * 3;\n\n this._particleScaleMultiplier = Math.max(\n 0.5,\n Math.sqrt(this._width * this._height * 0.001),\n );\n\n this._particles = [];\n\n // прозрачность над игроком и затемнение под ним — общая формула\n // levelView. `onRender` — аксессор Container, назначается свойством\n this._levelView = dependencies.levelView || null;\n this._renderer = dependencies.renderer || null;\n\n if (this._levelView || this._renderer) {\n this.onRender = () => {\n if (this._levelView) {\n this.alpha = this._levelView.alphaFor(\n this._level,\n this._emitterX,\n this._emitterY,\n this._z,\n );\n this.tint = this._levelView.tintFor(this._level);\n }\n\n // дым на мосту стоит на мосту, а не на земле под ним: та же\n // проекция высоты, что у плиты и у танка. Частицы живут в мировых\n // координатах, контейнер единичный — трансформ контейнера даёт им\n // ровно offsetPoint.\n //\n // Известное упрощение: облако смещается по ТЕКУЩЕЙ высоте танка,\n // поэтому уже отпущенные частицы едут вместе с ним. Для дыма это\n // незаметно — жизнь частицы меньше двух секунд\n applyParallax(\n this,\n cameraCenter(this.parent, this._renderer),\n this._z * parallaxConfig.shear,\n 1,\n );\n };\n }\n\n this._particleContainer = new ParticleContainer({\n texture: this._smokeTexture,\n boundsArea: new Rectangle(\n this._emitterX - BOUNDS_PADDING,\n this._emitterY - BOUNDS_PADDING,\n BOUNDS_PADDING * 2,\n BOUNDS_PADDING * 2,\n ),\n dynamicProperties: {\n position: true,\n vertex: true,\n rotation: true,\n color: true,\n },\n });\n this.addChild(this._particleContainer);\n\n this._timeSinceLastSpawn = 0;\n this._timeSinceExhaust = 0;\n\n this._tickListener = ticker => this._updateParticles(ticker.deltaMS);\n Ticker.shared.add(this._tickListener);\n }\n\n update(data) {\n const prevCondition = this._condition;\n const level = data[M1_LEVEL] || 0;\n\n // танк переехал на эстакаду — дым едет за ним на её слой\n if (level !== this._level) {\n this._level = level;\n this.zIndex = levelZ(SMOKE_BASE_Z, level);\n }\n\n this._emitterX = data[M1_X];\n this._emitterY = data[M1_Y];\n this._emitterRotation = data[M1_ANGLE];\n this._emitterVX = data[M1_VX];\n this._emitterVY = data[M1_VY];\n this._engineLoad = data[M1_ENGINE_LOAD];\n this._condition = data[M1_CONDITION];\n this._z = data[M1_Z] || 0;\n\n this._particleContainer.boundsArea.x = this._emitterX - BOUNDS_PADDING;\n this._particleContainer.boundsArea.y = this._emitterY - BOUNDS_PADDING;\n\n // если состояние изменилось и\n // новое состояние подразумевает наличие дыма (не 3)\n if (this._condition !== prevCondition && this._condition !== 3) {\n this._triggerSmokeBurst();\n }\n }\n\n // метод для создания мгновенного облака частиц\n _triggerSmokeBurst() {\n let numStreams = 0;\n\n if (this._condition === 1) {\n numStreams = 2;\n } else if (this._condition === 2) {\n numStreams = 1;\n } else if (this._condition === 0) {\n numStreams = 1;\n }\n\n if (numStreams > 0) {\n const count = SMOKE_CONFIG.burstParticleCount;\n const particlesPerStream = Math.ceil(count / numStreams);\n\n for (let i = 0; i < particlesPerStream; i += 1) {\n for (let s = 0; s < numStreams; s += 1) {\n this.spawnParticle(s, numStreams, 'burst');\n }\n }\n }\n }\n\n _updateParticles(deltaMs) {\n if (deltaMs <= 0) {\n return;\n }\n\n const deltaTime = deltaMs / 1000.0;\n this._timeSinceLastSpawn += deltaMs;\n this._timeSinceExhaust += deltaMs;\n\n let numStreams = 0;\n\n // норма: дыма нет\n if (this._condition === 3) {\n numStreams = 0;\n\n // значительные повреждения: сильный дым (из двух труб или просто широкий)\n } else if (this._condition === 1) {\n numStreams = 2;\n\n // незначительные повреждения: средний дым (один поток)\n } else if (this._condition === 2) {\n numStreams = 1;\n\n // уничтожен: небольшой остаточный дым\n } else if (this._condition === 0) {\n numStreams = 1;\n }\n\n // частота спавна или 0, если состояние не определено\n const spawnRate = SMOKE_CONFIG.particleSpawnRate[this._condition] || 0;\n\n // если spawnRate 0 (или бесконечность),\n // интервал будет Infinity, цикл не запустится\n const spawnInterval = spawnRate > 0 ? 1000.0 / spawnRate : Infinity;\n\n if (numStreams > 0 && spawnRate > 0) {\n while (this._timeSinceLastSpawn >= spawnInterval) {\n for (let i = 0; i < numStreams; i += 1) {\n this.spawnParticle(i, numStreams);\n }\n this._timeSinceLastSpawn -= spawnInterval;\n }\n } else {\n // сброс таймера, если дыма нет\n this._timeSinceLastSpawn = 0;\n }\n\n // канал выхлопа: интенсивность — это газ, а не повреждения. `min(load, 1)`\n // отсекает «напряжение» (> 1): упёршийся в стену танк дымит не сильнее,\n // чем едущий на полном газу, — за упор отвечает пыль из-под гусениц.\n // Уничтоженный танк (condition 0) выхлопа не даёт: газа нет\n const gas = Math.min(this._engineLoad, 1);\n const exhaustRate =\n this._condition > 0 && gas >= SMOKE_CONFIG.exhaust.minLoad\n ? lerp(\n SMOKE_CONFIG.exhaust.idleSpawnRate,\n SMOKE_CONFIG.exhaust.spawnRate,\n gas,\n )\n : 0;\n\n if (exhaustRate > 0) {\n const exhaustInterval = 1000.0 / exhaustRate;\n\n while (this._timeSinceExhaust >= exhaustInterval) {\n this.spawnParticle(0, 1, 'exhaust');\n this._timeSinceExhaust -= exhaustInterval;\n }\n } else {\n this._timeSinceExhaust = 0;\n }\n\n // предварительный расчет трения\n const frictionFactor = Math.pow(SMOKE_CONFIG.airResistance, deltaMs / 16.0);\n const oneMinusFriction = 1 - frictionFactor;\n\n // обратный цикл для безопасного удаления\n for (let i = this._particles.length - 1; i >= 0; i -= 1) {\n const particle = this._particles[i];\n particle.age += deltaMs;\n\n if (particle.age >= particle.lifetime) {\n this._particleContainer.removeParticle(particle.view);\n ParticlePool.release(particle.view);\n this._particles.splice(i, 1);\n continue;\n }\n\n // ветер и трение\n particle.vx =\n particle.vx + (globalWindX - particle.vx) * oneMinusFriction;\n particle.vy =\n particle.vy + (globalWindY - particle.vy) * oneMinusFriction;\n\n particle.x += particle.vx * deltaTime;\n particle.y += particle.vy * deltaTime;\n\n // интерполяция параметров\n const lifeProgress = particle.age / particle.lifetime;\n // простое квадратичное затухание\n const ease = 1 - (1 - lifeProgress) * (1 - lifeProgress);\n\n const currentSizeFactor = lerp(\n particle.startSizeFactor,\n particle.endSizeFactor,\n ease,\n );\n\n const currentAlpha = lerp(\n particle.startAlpha,\n particle.endAlpha,\n lifeProgress,\n );\n\n const currentScale =\n currentSizeFactor * this._particleScaleMultiplier * this._textureScale;\n\n const view = particle.view;\n view.x = particle.x;\n view.y = particle.y;\n view.scaleX = currentScale;\n view.scaleY = currentScale;\n view.alpha = currentAlpha;\n view.rotation += particle.rotSpeed * deltaTime;\n }\n }\n\n // kind: 'damage' — дым повреждений, 'burst' — облако при смене состояния,\n // 'exhaust' — выхлоп по газу. Точка спавна и начальные скорости у всех\n // трёх общие: труба одна\n spawnParticle(streamIndex, numStreams, kind = 'damage') {\n const isBurst = kind === 'burst';\n const isExhaust = kind === 'exhaust';\n const exhaust = SMOKE_CONFIG.exhaust;\n\n const lifetime = isExhaust\n ? randomRange(exhaust.lifetime.min, exhaust.lifetime.max)\n : randomRange(\n SMOKE_CONFIG.particleLifetime.min,\n SMOKE_CONFIG.particleLifetime.max,\n );\n\n // параметры из конфига или дефолтные маленькие значения\n let startSizeFactor = isExhaust\n ? exhaust.startSizeFactor\n : SMOKE_CONFIG.particleStartSizeFactor[this._condition] || 0.3;\n let endSizeFactor = isExhaust\n ? exhaust.endSizeFactor\n : SMOKE_CONFIG.particleEndSizeFactor[this._condition] || 1.0;\n\n // параметры по умолчанию\n let startAlpha = isExhaust\n ? exhaust.startAlpha\n : SMOKE_CONFIG.particleStartAlpha;\n let velocityMultiplier = 1.0;\n\n // если эффект взрыва\n if (isBurst) {\n // огромный размер\n startSizeFactor *= 2.0;\n endSizeFactor *= 2.5;\n\n // густота (дым непрозрачный)\n startAlpha = 0.8;\n\n // дальность полета (мощный импульс скорости)\n velocityMultiplier = 2.0;\n }\n\n const angle = this._emitterRotation;\n\n // векторы направления\n const backAngle = angle + Math.PI;\n const cosBack = Math.cos(backAngle);\n const sinBack = Math.sin(backAngle);\n\n const rightAngle = angle + Math.PI / 2;\n const cosRight = Math.cos(rightAngle);\n const sinRight = Math.sin(rightAngle);\n\n // смещение вбок\n let offsetSide = 0;\n const streamDisplacement =\n (this._width / 2) * SMOKE_CONFIG.streamOffsetFactor;\n\n if (numStreams === 2) {\n offsetSide = streamIndex === 0 ? -streamDisplacement : streamDisplacement;\n } else if (numStreams === 3) {\n offsetSide = (streamIndex - 1) * streamDisplacement;\n }\n\n // смещение назад\n const offsetBack = this._height * 0.22;\n\n // точка спавна\n const spawnX =\n this._emitterX + cosBack * offsetBack + cosRight * offsetSide;\n const spawnY =\n this._emitterY + sinBack * offsetBack + sinRight * offsetSide;\n\n // скорость\n let ejectionSpeed = randomRange(\n SMOKE_CONFIG.particleInitialVelocity.away.min,\n SMOKE_CONFIG.particleInitialVelocity.away.max,\n );\n let sideSpeed = randomRange(\n SMOKE_CONFIG.particleInitialVelocity.side.min,\n SMOKE_CONFIG.particleInitialVelocity.side.max,\n );\n\n // ускорение для взрыва\n ejectionSpeed *= velocityMultiplier;\n sideSpeed *= velocityMultiplier;\n\n let vx = cosBack * ejectionSpeed + cosRight * sideSpeed;\n let vy = sinBack * ejectionSpeed + sinRight * sideSpeed;\n\n const variance = SMOKE_CONFIG.particleVelocityVariance;\n\n vx += randomRange(-variance, variance);\n vy += randomRange(-variance, variance);\n\n // дым не наследует скорость танка (0), чтобы оставаться \"в воздухе\"\n vx += this._emitterVX;\n vy += this._emitterVY;\n\n const view = ParticlePool.get(this._smokeTexture);\n view.tint = isExhaust ? exhaust.color : SMOKE_CONFIG.particleColor;\n view.x = spawnX;\n view.y = spawnY;\n view.alpha = startAlpha;\n const startScale =\n startSizeFactor * this._particleScaleMultiplier * this._textureScale;\n view.scaleX = startScale;\n view.scaleY = startScale;\n view.rotation = randomRange(0, Math.PI * 2);\n\n const particle = {\n view,\n x: spawnX,\n y: spawnY,\n vx,\n vy,\n age: 0,\n lifetime,\n startSizeFactor,\n endSizeFactor,\n startAlpha,\n endAlpha: SMOKE_CONFIG.particleEndAlpha,\n rotSpeed: randomRange(-1, 1),\n };\n\n this._particleContainer.addParticle(view);\n this._particles.push(particle);\n }\n\n _stopTimer() {\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n }\n\n destroy(options) {\n this._stopTimer();\n\n // все активные частицы в пул\n for (let i = 0; i < this._particles.length; i += 1) {\n ParticlePool.release(this._particles[i].view);\n }\n\n this._particles = [];\n\n // children: true идёт после ...options и не переопределяется извне:\n // отключение children оставило бы уже возвращённые в пул частицы\n // висеть в живом _particleContainer (двойное использование)\n super.destroy({\n texture: false,\n textureSource: false,\n ...options,\n children: true,\n });\n }\n}\n","import { Sprite } from 'pixi.js';\n\nexport default class TrackMark extends Sprite {\n // lifetimeFactor — множитель времени жизни (поверхность: масло держит след\n // дольше)\n constructor(\n x,\n y,\n rotation,\n width,\n length,\n initialAlpha,\n texture,\n lifetimeFactor = 1,\n ) {\n super(texture);\n\n this.anchor.set(0.5);\n\n this.x = x;\n this.y = y;\n this.rotation = rotation;\n this.width = length;\n this.height = width;\n this.alpha = initialAlpha; // начальная прозрачность\n this._initialAlpha = initialAlpha; // сохранение для расчетов\n\n this._elapsedTime = 0;\n\n // время в ms, за которое сегмент следа полностью исчезнет\n // рандомность, чтобы следы исчезали не все одновременно\n this._fadeDuration = (1800 + Math.random() * 700) * lifetimeFactor;\n }\n\n // обновляет состояние следа\n // вызывается извне классом Tracks\n // deltaMs - время, прошедшее с последнего кадра\n // возвращает true, если жизненный цикл следа завершен\n update(deltaMs) {\n this._elapsedTime += deltaMs;\n const progress = Math.min(this._elapsedTime / this._fadeDuration, 1);\n\n // альфа изменяется от сохраненной начальной альфы до 0\n this.alpha = (1 - progress) * this._initialAlpha;\n\n return progress >= 1;\n }\n}\n","import { Container, Ticker } from 'pixi.js';\nimport TrackMark from './TrackMark.js';\nimport { normalizeAngle } from 'vimp-engine/lib/math.js';\nimport { levelZ } from '../../levelZ.js';\nimport { cameraCenter } from '../../camera.js';\nimport { applyParallax } from '../../parallax.js';\nimport {\n parallax as parallaxConfig,\n surfaceFx,\n} from '../../../config/render.js';\nimport {\n M1_X,\n M1_Y,\n M1_ANGLE,\n M1_ENGINE_LOAD,\n M1_CONDITION,\n M1_SIZE,\n M1_LEVEL,\n} from '../../snapshotFields.js';\n\n// базовый zIndex следов внутри своего уровня\nconst TRACKS_BASE_Z = 1;\n\nexport default class Tracks extends Container {\n constructor(data, assets, dependencies = {}) {\n super();\n\n // 2.5D: следы на плите НАД игроком обязаны гаснуть вместе с ней, а сами\n // отметки — висеть на высоте своего уровня. Дым, ящики, танки и бомбы\n // это уже делают; следы оставались единственным непрозрачным пятном\n this._levelView = dependencies.levelView || null;\n this._renderer = dependencies.renderer || null;\n // поверхность под танком из ядра: масло держит тёмный след дольше, грязь\n // темнит его, вода не оставляет вовсе. Без сервиса — прежние следы\n this._surfaces = dependencies.surfaces || null;\n\n // 2.5D: след принадлежит тому уровню, на котором он оставлен, и остаётся\n // на нём, даже когда танк уже уехал по рампе. Поэтому отметки живут не в\n // самом парте, а в контейнере СВОЕГО уровня: контейнер парта один, а\n // слоёв два, и разъехаться по zIndex они могут только сиблингами на сцене\n this._level = data[M1_LEVEL] || 0;\n this._markLayers = new Map();\n this.zIndex = levelZ(TRACKS_BASE_Z, this._level);\n\n this._currentX = data[M1_X] || 0;\n this._currentY = data[M1_Y] || 0;\n this._currentRotation = data[M1_ANGLE] || 0;\n this._engineLoad = data[M1_ENGINE_LOAD] || 0;\n this._condition = data[M1_CONDITION];\n this._size = data[M1_SIZE];\n\n // состояние для расчета дельт и ускорений\n this._prevX = this._currentX;\n this._prevY = this._currentY;\n this._prevRotation = this._currentRotation;\n this._prevSpeed = 0; // предыдущая линейная скорость\n this._prevAngularSpeed = 0; // предыдущая угловая скорость\n\n this._assets = assets;\n\n // минимальное время (ms), которое должно пройти между созданием\n // двух последовательных \"пачек\" следов\n // чем меньше значение, тем больше кол-во следов\n // (создаст больше объектов TrackMark, может повлиять на производительность)\n this._trackMarkCooldown = 2;\n\n // задержка в ms между созданием последовательных \"пачек\" следов\n this._lastTrackMarkTime = 0;\n\n // минимальное изменение линейной скорости, чтобы оставить след\n // скорость измеряется в пикселях за время deltaMs последнего тика\n // при уменьшении значения, следы будут появляться чаще\n // при малейшем маневрировании скоростью\n // (легкий разгон, небольшое торможение)\n this._minAbsAccelerationForMark = 4;\n\n // минимальная текущая угловая скорость для следов при повороте\n // рекомендуемые значения от 0.02 до 0.06\n // при уменьшении: даже медленные или плавные повороты\n // (при условии достаточной линейной скорости)\n // будут оставлять следы\n this._minAngularSpeedForMark = 0.02;\n\n // минимальное изменение угловой скорости (угловое ускорение)\n // рекомендуемые значения: 0.01 - 1\n // при уменьшении: танк будет оставлять следы даже\n // при небольших изменениях в скорости поворота\n // при увеличении: следы будут появляться только\n // при очень резком начале или очень резком прекращении вращения\n this._minRotationAccelerationForMark = 0.4;\n\n // минимальная линейная скорость, чтобы поворот оставлял след\n // рекомендемые значения: min: 0, max: 2\n // при уменьшении: танк будет оставлять следы от поворотов даже\n // при вращении на месте\n // при увеличении: танк должен заметно двигаться вперед или назад,\n // чтобы его повороты оставляли следы\n this._minSpeedForRotationMarks = 1.4;\n\n // визуальные параметры следов\n\n // ширина одного сегмента следа\n this._trackWidth = this._size * 0.4;\n\n // длина одного сегмента следа\n this._trackLength = this._size * 0.5;\n\n const tankHeight = this._size * 3;\n\n // расстояние от центральной линии танка до центра каждого из двух следов\n this._trackOffset = (tankHeight / 2) * 0.7;\n\n // смещение назад точки появления следов от центра танка\n this._backwardOffset = tankHeight * 0.4;\n\n // начальная прозрачность следа (от 0 до 1), когда он только появляется\n this._trackInitialAlpha = 0.4;\n\n // шлейф поверхности с `trail` (масло): после съезда отметки ещё\n // `trail` секунд её цвета со спадающей альфой. Таймер свой: остаток\n // ядра есть только у своего танка, у удалённых его не видно\n this._trailKind = null;\n this._trailLeft = 0;\n\n this._tickListener = ticker => this._internalUpdate(ticker.deltaMS);\n Ticker.shared.add(this._tickListener);\n\n // `onRender` у Container — аксессор: назначается СВОЙСТВОМ, иначе сеттер\n // не отработает и PixiJS не позовёт колбэк ни разу. Контейнер парта\n // пустой (отметки живут сиблингами), но RenderGroup зовёт `_onRender`\n // по своему списку, а не по содержимому — колбэк приходит каждый кадр\n if (this._levelView) {\n this.onRender = () => this._updateLayers();\n }\n }\n\n update(data) {\n this._currentX = data[M1_X];\n this._currentY = data[M1_Y];\n this._currentRotation = data[M1_ANGLE];\n this._engineLoad = data[M1_ENGINE_LOAD];\n this._condition = data[M1_CONDITION];\n\n const level = data[M1_LEVEL] || 0;\n\n if (level !== this._level) {\n this._level = level;\n this.zIndex = levelZ(TRACKS_BASE_Z, level);\n }\n }\n\n // контейнер отметок уровня: сиблинг парта на сцене (у эффектов взрыва тот\n // же приём). Пока парт не добавлен на сцену, отметки складываются в него\n // самого — кадр без родителя рисовать всё равно некуда\n _markLayer(level) {\n let layer = this._markLayers.get(level);\n\n if (layer) {\n return layer;\n }\n\n layer = new Container();\n layer.zIndex = levelZ(TRACKS_BASE_Z, level);\n\n this._markLayers.set(level, layer);\n\n const host = this.parent || this;\n\n host.addChild(layer);\n host.sortChildren();\n\n return layer;\n }\n\n _internalUpdate(deltaMs) {\n if (deltaMs <= 0) {\n return;\n }\n\n // обновление всех существующих следов в одном цикле\n // итерация в обратном порядке,\n // чтобы безопасно удалять элементы из массива children\n for (const layer of this._markLayers.values()) {\n for (let i = layer.children.length - 1; i >= 0; i -= 1) {\n const mark = layer.children[i];\n // если возвращается true, значит время жизни вышло\n if (mark.update(deltaMs)) {\n // уничтожение спрайта\n // метод destroy() автоматически удаляет объект\n // из родительского контейнера\n mark.destroy();\n }\n }\n }\n\n this._stepTrail(deltaMs / 1000);\n\n // текущие скорости\n const deltaX = this._currentX - this._prevX;\n const deltaY = this._currentY - this._prevY;\n\n // текущая линейная скорость (пикселей за время deltaMs)\n const currentSpeed = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n const rotationDiff = normalizeAngle(\n this._currentRotation - this._prevRotation,\n );\n\n // текущая угловая скорость (радианы за время deltaMs)\n const currentAngularSpeed = Math.abs(rotationDiff);\n\n // линейное ускорение/замедление\n // положительное - ускорение, отрицательное - торможение\n const acceleration = currentSpeed - this._prevSpeed;\n const absAcceleration = Math.abs(acceleration);\n\n // угловое ускорение/замедление\n const angularAcceleration = currentAngularSpeed - this._prevAngularSpeed;\n const absAngularAcceleration = Math.abs(angularAcceleration);\n\n const currentTime = performance.now();\n let shouldLeaveMark = false;\n\n // логика принятия решения об оставлении следа\n if (\n this._condition !== 0 &&\n currentTime - this._lastTrackMarkTime > this._trackMarkCooldown\n ) {\n // если резкое изменение линейной скорости (ускорение или торможение)\n if (absAcceleration > this._minAbsAccelerationForMark) {\n shouldLeaveMark = true;\n }\n\n // если резкий поворот (высокая текущая угловая скорость\n // ИЛИ высокое угловое ускорение)\n // и при этом есть минимальное движение вперед,\n // чтобы не рисовать следы при вращении на месте.\n if (currentSpeed > this._minSpeedForRotationMarks) {\n if (\n currentAngularSpeed > this._minAngularSpeedForMark ||\n absAngularAcceleration > this._minRotationAccelerationForMark\n ) {\n shouldLeaveMark = true;\n }\n }\n }\n\n // если танк газует, будучи застрявшим (состояние напряжения)\n // engineLoad > 1.0 означает напряжение (strain)\n if (this._engineLoad > 1.0) {\n shouldLeaveMark = true;\n }\n\n if (shouldLeaveMark) {\n this.createTrackMarksAtPreviousPosition();\n this._lastTrackMarkTime = currentTime;\n }\n\n // сохранение текущих значений как предыдущих для следующего вызова\n this._prevX = this._currentX;\n this._prevY = this._currentY;\n this._prevRotation = this._currentRotation;\n this._prevSpeed = currentSpeed;\n this._prevAngularSpeed = currentAngularSpeed;\n }\n\n // таймер шлейфа: на клетке с `trail` взводится полностью, вне её спадает.\n // Клетка смотрится каждый тик, а не только при отметке: по прямой танк\n // следов не оставляет, и проезд по маслу иначе не взводил бы шлейф\n _stepTrail(dt) {\n if (!this._surfaces) {\n return;\n }\n\n const kind = this._surfaces.kindAt(\n this._currentX,\n this._currentY,\n this._level,\n );\n const trail = kind && surfaceFx.tracks[kind]?.trail;\n\n if (trail) {\n this._trailKind = kind;\n this._trailLeft = trail;\n } else if (this._trailLeft > 0) {\n this._trailLeft = Math.max(0, this._trailLeft - dt);\n }\n }\n\n createTrackMarksAtPreviousPosition() {\n const kind = this._surfaces\n ? this._surfaces.kindAt(this._prevX, this._prevY, this._level)\n : null;\n\n if (kind && surfaceFx.tracks.noMarks.includes(kind)) {\n return;\n }\n\n let style = (kind && surfaceFx.tracks[kind]) || null;\n // доля шлейфа: 1 — на самой поверхности или без шлейфа\n let fade = 1;\n\n if (!style && this._trailLeft > 0) {\n style = surfaceFx.tracks[this._trailKind];\n fade = this._trailLeft / style.trail;\n }\n\n const layer = this._markLayer(this._level);\n\n for (let i = -1; i <= 1; i += 2) {\n const sideOffsetX =\n Math.cos(this._prevRotation + Math.PI / 2) * this._trackOffset * i;\n const sideOffsetY =\n Math.sin(this._prevRotation + Math.PI / 2) * this._trackOffset * i;\n\n const backwardComponentX =\n -Math.cos(this._prevRotation) * this._backwardOffset;\n const backwardComponentY =\n -Math.sin(this._prevRotation) * this._backwardOffset;\n\n const markX = this._prevX + sideOffsetX + backwardComponentX;\n const markY = this._prevY + sideOffsetY + backwardComponentY;\n\n const mark = new TrackMark(\n markX,\n markY,\n this._prevRotation,\n this._trackWidth,\n this._trackLength,\n Math.min(1, this._trackInitialAlpha * (style ? style.alpha : 1)) *\n fade,\n this._assets.trackMarkTexture,\n style ? style.lifetime : 1,\n );\n\n if (style) {\n mark.tint = style.tint;\n }\n\n layer.addChild(mark);\n }\n }\n\n // видимость и высота контейнеров отметок: alpha и tint — по уровню слоя\n // (та же формула levelView, что у ящиков и танков), сдвиг — тот же\n // параллакс, что у плиты, на которой след оставлен. Точка для «дыры»\n // берётся у своего танка: след — его собственный, и держится рядом\n _updateLayers() {\n if (!this._markLayers.size) {\n return;\n }\n\n const camera = cameraCenter(this.parent, this._renderer);\n\n for (const [level, layer] of this._markLayers) {\n layer.alpha = this._levelView.alphaFor(\n level,\n this._currentX,\n this._currentY,\n );\n layer.tint = this._levelView.tintFor(level);\n\n if (level) {\n applyParallax(layer, camera, level * parallaxConfig.shear);\n }\n }\n }\n\n _stopTimer() {\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n }\n\n destroy(options) {\n this._stopTimer();\n\n // контейнеры отметок — сиблинги парта: движок их не знает и сам не снимет\n for (const layer of this._markLayers.values()) {\n if (!layer.destroyed) {\n layer.destroy({ children: true });\n }\n }\n\n this._markLayers.clear();\n\n super.destroy({\n children: true,\n texture: false,\n textureSource: false,\n ...options,\n });\n }\n}\n","import { Container, ParticleContainer, Rectangle, Ticker } from 'pixi.js';\nimport ParticlePool from './ParticlePool.js';\nimport { randomRange } from 'vimp-engine/lib/math.js';\nimport { levelZ } from '../levelZ.js';\nimport { cameraCenter } from '../camera.js';\nimport { applyParallax } from '../parallax.js';\nimport {\n parallax as parallaxConfig,\n landing as landingConfig,\n surfaceFx,\n} from '../../config/render.js';\nimport { landingImpact } from '../landing.js';\nimport {\n M1_X,\n M1_Y,\n M1_ANGLE,\n M1_VX,\n M1_VY,\n M1_ENGINE_LOAD,\n M1_CONDITION,\n M1_SIZE,\n M1_Z,\n M1_LEVEL,\n M1_VZ,\n} from '../snapshotFields.js';\n\n// базовый zIndex пыли внутри своего уровня: над следами гусениц (1), под\n// танком (3) — пыль живёт НА земле, в отличие от дыма (4), который над танком\nconst DUST_BASE_Z = 2;\n\n// отступ вокруг эмиттера для boundsArea: покрывает разлёт частиц\nconst BOUNDS_PADDING = 300;\n\n// базовый видимый размер частицы в юнитах мира: нормировка по нарисованному\n// кругу текстуры делает вид частиц независимым от запаса под размытие\nconst DUST_PARTICLE_BASE_SIZE = 6;\n\nconst DUST_CONFIG = {\n // пыль оседает: живёт меньше дыма и почти не летит\n lifetime: { min: 300, max: 700 },\n\n startSizeFactor: 0.7,\n endSizeFactor: 2.0,\n\n startAlpha: 0.35,\n endAlpha: 0.0,\n\n // белая текстура, крашенная в цвет грунта\n color: 0x9c8f77,\n\n // сопротивление воздуха (та же форма, что у дыма)\n airResistance: 0.86,\n\n // буксование: частиц в секунду на каждую точку контакта при полном упоре\n spawnRate: 30,\n\n // выше этой скорости танк едет, а не буксует (мировых единиц в секунду)\n maxSpinSpeed: 8,\n\n // выброс грунта назад вдоль корпуса плюс разброс\n spinVelocity: {\n back: { min: 20, max: 70 },\n side: { min: -25, max: 25 },\n },\n\n // приземление: частиц на каждую точку контакта при полном ударе. Число\n // считается от силы удара (`landingBurst × impact`), а мягкое касание\n // (impact = 0) не даёт ни одной — порог живёт в `landing.minImpact`\n landingBurst: 14,\n\n // радиальная скорость разлёта при приземлении\n landingVelocity: { min: 40, max: 110 },\n\n // во сколько раз крупнее частицы всплеска\n landingSizeFactor: 1.6,\n};\n\n// вид обычной пыли (буксование, приземление); поверхности задают свой\nconst DUST_STYLE = {\n color: DUST_CONFIG.color,\n alpha: DUST_CONFIG.startAlpha,\n lifetime: DUST_CONFIG.lifetime,\n};\n\n// поверхности с непрерывной эмиссией на ходу (параметры — render.js)\nconst SURFACE_EMITTERS = ['sand', 'mud', 'water'];\n\n// разброс хвоста бустера вокруг направления против стрелки, рад\nconst BOOST_TAIL_SPREAD = 0.35;\n\n// та же плита бустера: клетка прошлого кадра — бустер с той же стрелкой\nconst isSameBoost = (cell, dir) =>\n cell.kind === 'boost' &&\n cell.dir !== null &&\n cell.dir[0] === dir[0] &&\n cell.dir[1] === dir[1];\n\nexport default class Dust extends Container {\n constructor(data, assets, dependencies = {}) {\n super();\n\n this._level = data[M1_LEVEL] || 0;\n this.zIndex = levelZ(DUST_BASE_Z, this._level);\n\n const { texture, contentSize } = assets.dustTexture;\n\n this._dustTexture = texture;\n this._textureScale = DUST_PARTICLE_BASE_SIZE / contentSize;\n\n this._x = data[M1_X];\n this._y = data[M1_Y];\n this._rotation = data[M1_ANGLE];\n this._vx = data[M1_VX] || 0;\n this._vy = data[M1_VY] || 0;\n this._engineLoad = data[M1_ENGINE_LOAD] || 0;\n this._condition = data[M1_CONDITION];\n this._z = data[M1_Z] || 0;\n this._vz = data[M1_VZ] || 0;\n this._prevVz = this._vz;\n\n this._size = data[M1_SIZE];\n\n const tankHeight = this._size * 3;\n\n // геометрия точек контакта гусениц — та же, что у следов\n // (`Tracks.createTrackMarksAtPreviousPosition`): пыль поднимается\n // оттуда же, откуда штампуется след\n this._trackOffset = (tankHeight / 2) * 0.7;\n this._backwardOffset = tankHeight * 0.4;\n\n this._particleScaleMultiplier = Math.max(\n 0.5,\n Math.sqrt(this._size * 4 * tankHeight * 0.001),\n );\n\n this._particles = [];\n this._timeSinceSpawn = 0;\n\n this._levelView = dependencies.levelView || null;\n this._renderer = dependencies.renderer || null;\n this._soundManager = dependencies.soundManager || null;\n\n // поверхность под танком из ядра (сервис `surfaces`); без сервиса —\n // прежняя пыль. `_surfaceCell` — клетка прошлого кадра: null, пока часть\n // не видела ни одного кадра, по которому клетки можно сравнить\n this._surfaces = dependencies.surfaces || null;\n this._surfaceKind = null;\n this._surfaceCell = null;\n this._timeSinceSurfaceSpawn = 0;\n\n const landingSound = this._soundManager?.getSoundConfig('tankLanding');\n\n // ассета может не быть: без конфига звук просто не проигрывается\n this._landingVolume = landingSound?.volume || 0;\n this._hasLandingSound = !!landingSound;\n\n // прозрачность над игроком и затемнение под ним — общая формула\n // levelView. `onRender` — аксессор Container, назначается свойством\n if (this._levelView || this._renderer) {\n this.onRender = () => {\n if (this._levelView) {\n this.alpha = this._levelView.alphaFor(\n this._level,\n this._x,\n this._y,\n this._z,\n );\n this.tint = this._levelView.tintFor(this._level);\n }\n\n // пыль лежит на той же плите, что и танк: та же проекция высоты,\n // что у дыма, следов и корпуса\n applyParallax(\n this,\n cameraCenter(this.parent, this._renderer),\n this._z * parallaxConfig.shear,\n 1,\n );\n };\n }\n\n this._particleContainer = new ParticleContainer({\n texture: this._dustTexture,\n boundsArea: new Rectangle(\n this._x - BOUNDS_PADDING,\n this._y - BOUNDS_PADDING,\n BOUNDS_PADDING * 2,\n BOUNDS_PADDING * 2,\n ),\n dynamicProperties: {\n position: true,\n vertex: true,\n rotation: true,\n color: true,\n },\n });\n this.addChild(this._particleContainer);\n\n this._tickListener = ticker => this._updateParticles(ticker.deltaMS);\n Ticker.shared.add(this._tickListener);\n }\n\n update(data) {\n const prevX = this._x;\n const prevY = this._y;\n const wasAlive = this._condition > 0;\n const level = data[M1_LEVEL] || 0;\n\n if (level !== this._level) {\n this._level = level;\n this.zIndex = levelZ(DUST_BASE_Z, level);\n }\n\n this._x = data[M1_X];\n this._y = data[M1_Y];\n this._rotation = data[M1_ANGLE];\n this._vx = data[M1_VX] || 0;\n this._vy = data[M1_VY] || 0;\n this._engineLoad = data[M1_ENGINE_LOAD] || 0;\n this._condition = data[M1_CONDITION];\n this._z = data[M1_Z] || 0;\n this._vz = data[M1_VZ] || 0;\n\n this._particleContainer.boundsArea.x = this._x - BOUNDS_PADDING;\n this._particleContainer.boundsArea.y = this._y - BOUNDS_PADDING;\n\n // касание: тот же детектор, что в `Tank.js` — общая функция. Обе части\n // получают один и тот же ряд снапшота, поэтому связывать их колбэком\n // не нужно\n const impact = landingImpact(this._prevVz, this._vz, landingConfig);\n\n if (impact > 0) {\n this._triggerLandingBurst(impact);\n this._playLandingSound(impact);\n }\n\n this._prevVz = this._vz;\n\n this._updateSurface(prevX, prevY, wasAlive);\n }\n\n // поверхность под центром корпуса: тип для непрерывной эмиссии и разовые\n // события въезда (всплеск воды, вспышка бустера). Правило въезда — то же,\n // что у импульса в ядре (`surface::boost_dv`). Сравнивать клетки можно\n // только с кадром, который часть уже видела у этого танка: первое\n // появление, респаун (condition 0 → жив) и скачок позиции (телепорт) лишь\n // запоминают текущую клетку\n _updateSurface(prevX, prevY, wasAlive) {\n if (!this._surfaces) {\n return;\n }\n\n const kind = this._surfaces.kindAt(this._x, this._y, this._level);\n const dir =\n kind === 'boost'\n ? this._surfaces.dirAt(this._x, this._y, this._level)\n : null;\n const prev = this._surfaceCell;\n const jumped =\n Math.hypot(this._x - prevX, this._y - prevY) >\n surfaceFx.boost.resetDistance;\n const respawned = !wasAlive && this._condition > 0;\n\n this._surfaceKind = kind;\n this._surfaceCell = { kind, dir };\n\n // в полёте поверхности нет — как в ядре\n if (\n prev === null ||\n respawned ||\n jumped ||\n this._condition <= 0 ||\n this._vz !== 0\n ) {\n return;\n }\n\n if (kind === 'water' && prev.kind !== 'water') {\n this._triggerWaterEntry();\n }\n\n if (dir && !isSameBoost(prev, dir)) {\n const along = this._vx * dir[0] + this._vy * dir[1];\n\n if (along >= surfaceFx.boost.boostMinSpeed) {\n this._triggerBoostFlash(dir);\n }\n }\n }\n\n // разовый всплеск при въезде в воду со скоростью\n _triggerWaterEntry() {\n const config = surfaceFx.water;\n\n if (Math.hypot(this._vx, this._vy) < config.entryMinSpeed) {\n return;\n }\n\n for (let side = -1; side <= 1; side += 2) {\n const point = this._contactPoint(side);\n\n for (let i = 0; i < config.entryBurst; i += 1) {\n const [vx, vy] = this._sideSprayVelocity(side, config);\n\n this._spawnParticle(\n point.x,\n point.y,\n vx,\n vy,\n config.sizeFactor * 1.5,\n config,\n );\n }\n }\n }\n\n // вспышка-хвост бустера: из центра корпуса против стрелки плиты\n _triggerBoostFlash(dir) {\n const config = surfaceFx.boost;\n const backAngle = Math.atan2(-dir[1], -dir[0]);\n\n for (let i = 0; i < config.burst; i += 1) {\n const angle =\n backAngle + randomRange(-BOOST_TAIL_SPREAD, BOOST_TAIL_SPREAD);\n const speed = randomRange(config.tailSpeed.min, config.tailSpeed.max);\n\n this._spawnParticle(\n this._x,\n this._y,\n Math.cos(angle) * speed,\n Math.sin(angle) * speed,\n config.sizeFactor,\n config,\n );\n }\n }\n\n // брызги в сторону гусеницы `side` с небольшим сносом назад\n _sideSprayVelocity(side, config) {\n const sideAngle = this._rotation + Math.PI / 2;\n const backAngle = this._rotation + Math.PI;\n const lateral =\n randomRange(config.sideSpeed.min, config.sideSpeed.max) * side;\n const back = randomRange(0, config.sideSpeed.min);\n\n return [\n Math.cos(sideAngle) * lateral + Math.cos(backAngle) * back,\n Math.sin(sideAngle) * lateral + Math.sin(backAngle) * back,\n ];\n }\n\n // непрерывная эмиссия поверхности на ходу: по частице из-под каждой\n // гусеницы — пыль и комья назад, брызги в стороны\n _spawnSurfaceDust(kind, config) {\n for (let side = -1; side <= 1; side += 2) {\n const point = this._contactPoint(side);\n let vx;\n let vy;\n\n if (kind === 'water') {\n [vx, vy] = this._sideSprayVelocity(side, config);\n } else {\n const backAngle = this._rotation + Math.PI;\n const sideAngle = this._rotation + Math.PI / 2;\n const back = randomRange(\n DUST_CONFIG.spinVelocity.back.min,\n DUST_CONFIG.spinVelocity.back.max,\n );\n const lateral = randomRange(\n DUST_CONFIG.spinVelocity.side.min,\n DUST_CONFIG.spinVelocity.side.max,\n );\n\n vx = Math.cos(backAngle) * back + Math.cos(sideAngle) * lateral;\n vy = Math.sin(backAngle) * back + Math.sin(sideAngle) * lateral;\n }\n\n this._spawnParticle(point.x, point.y, vx, vy, config.sizeFactor, config);\n }\n }\n\n _playLandingSound(impact) {\n if (!this._hasLandingSound) {\n return;\n }\n\n this._soundManager.registerSound('tankLanding', {\n position: { x: this._x, y: this._y },\n volume: this._landingVolume * impact,\n });\n }\n\n // точки контакта гусениц: сторона `side` — -1 (левая) или 1 (правая)\n _contactPoint(side) {\n const sideAngle = this._rotation + Math.PI / 2;\n\n return {\n x:\n this._x +\n Math.cos(sideAngle) * this._trackOffset * side -\n Math.cos(this._rotation) * this._backwardOffset,\n y:\n this._y +\n Math.sin(sideAngle) * this._trackOffset * side -\n Math.sin(this._rotation) * this._backwardOffset,\n };\n }\n\n // всплеск пыли из-под обеих гусениц: радиально наружу, размером и числом\n // по силе удара\n _triggerLandingBurst(impact) {\n const count = Math.round(DUST_CONFIG.landingBurst * impact);\n\n if (count <= 0) {\n return;\n }\n\n for (let side = -1; side <= 1; side += 2) {\n const point = this._contactPoint(side);\n\n for (let i = 0; i < count; i += 1) {\n const angle = randomRange(0, Math.PI * 2);\n const speed =\n randomRange(\n DUST_CONFIG.landingVelocity.min,\n DUST_CONFIG.landingVelocity.max,\n ) * impact;\n\n this._spawnParticle(\n point.x,\n point.y,\n Math.cos(angle) * speed,\n Math.sin(angle) * speed,\n DUST_CONFIG.landingSizeFactor * impact,\n );\n }\n }\n }\n\n // буксование: гусеница выбрасывает грунт НАЗАД, против направления тяги\n _spawnSpinDust() {\n const backAngle = this._rotation + Math.PI;\n const sideAngle = this._rotation + Math.PI / 2;\n\n for (let side = -1; side <= 1; side += 2) {\n const point = this._contactPoint(side);\n\n const back = randomRange(\n DUST_CONFIG.spinVelocity.back.min,\n DUST_CONFIG.spinVelocity.back.max,\n );\n const lateral = randomRange(\n DUST_CONFIG.spinVelocity.side.min,\n DUST_CONFIG.spinVelocity.side.max,\n );\n\n this._spawnParticle(\n point.x,\n point.y,\n Math.cos(backAngle) * back + Math.cos(sideAngle) * lateral,\n Math.sin(backAngle) * back + Math.sin(sideAngle) * lateral,\n 1,\n );\n }\n }\n\n // style — цвет, начальная альфа и время жизни частицы\n _spawnParticle(x, y, vx, vy, sizeMultiplier, style = DUST_STYLE) {\n const startSizeFactor = DUST_CONFIG.startSizeFactor * sizeMultiplier;\n const endSizeFactor = DUST_CONFIG.endSizeFactor * sizeMultiplier;\n\n const view = ParticlePool.get(this._dustTexture);\n const startScale =\n startSizeFactor * this._particleScaleMultiplier * this._textureScale;\n\n view.tint = style.color;\n view.x = x;\n view.y = y;\n view.alpha = style.alpha;\n view.scaleX = startScale;\n view.scaleY = startScale;\n view.rotation = randomRange(0, Math.PI * 2);\n\n this._particles.push({\n view,\n x,\n y,\n vx,\n vy,\n age: 0,\n lifetime: randomRange(style.lifetime.min, style.lifetime.max),\n startAlpha: style.alpha,\n startSizeFactor,\n endSizeFactor,\n rotSpeed: randomRange(-1, 1),\n });\n\n this._particleContainer.addParticle(view);\n }\n\n _updateParticles(deltaMs) {\n if (deltaMs <= 0) {\n return;\n }\n\n const deltaTime = deltaMs / 1000.0;\n\n this._timeSinceSpawn += deltaMs;\n\n // упор в стену: газ есть, а танк не едет. `engineLoad > 1` — тот же\n // порог «напряжения», по которому Tracks.js штампует следы, а Tank.js\n // поднимает высоту тона двигателя\n const strain = Math.max(0, this._engineLoad - 1);\n const speed = Math.hypot(this._vx, this._vy);\n // масло не пылит вовсе: ни на ходу, ни при буксовании\n const spinning =\n this._condition > 0 &&\n strain > 0 &&\n speed < DUST_CONFIG.maxSpinSpeed &&\n this._surfaceKind !== 'oil';\n const rate = spinning ? DUST_CONFIG.spawnRate * Math.min(strain, 1) : 0;\n\n if (rate > 0) {\n const interval = 1000.0 / rate;\n\n while (this._timeSinceSpawn >= interval) {\n this._spawnSpinDust();\n this._timeSinceSpawn -= interval;\n }\n } else {\n this._timeSinceSpawn = 0;\n }\n\n this._emitSurface(deltaMs, speed);\n\n const frictionFactor = Math.pow(DUST_CONFIG.airResistance, deltaMs / 16.0);\n\n // обратный цикл для безопасного удаления\n for (let i = this._particles.length - 1; i >= 0; i -= 1) {\n const particle = this._particles[i];\n\n particle.age += deltaMs;\n\n if (particle.age >= particle.lifetime) {\n this._particleContainer.removeParticle(particle.view);\n ParticlePool.release(particle.view);\n this._particles.splice(i, 1);\n continue;\n }\n\n particle.vx *= frictionFactor;\n particle.vy *= frictionFactor;\n\n particle.x += particle.vx * deltaTime;\n particle.y += particle.vy * deltaTime;\n\n const lifeProgress = particle.age / particle.lifetime;\n const ease = 1 - (1 - lifeProgress) * (1 - lifeProgress);\n\n const currentScale =\n (particle.startSizeFactor +\n (particle.endSizeFactor - particle.startSizeFactor) * ease) *\n this._particleScaleMultiplier *\n this._textureScale;\n\n const view = particle.view;\n\n view.x = particle.x;\n view.y = particle.y;\n view.scaleX = currentScale;\n view.scaleY = currentScale;\n view.alpha =\n particle.startAlpha +\n (DUST_CONFIG.endAlpha - particle.startAlpha) * lifeProgress;\n view.rotation += particle.rotSpeed * deltaTime;\n }\n }\n\n // эмиссия поверхности: частота пропорциональна скорости, в полёте и у\n // мёртвого танка — ничего\n _emitSurface(deltaMs, speed) {\n const kind = this._surfaceKind;\n const config = SURFACE_EMITTERS.includes(kind) ? surfaceFx[kind] : null;\n\n if (\n !config ||\n this._condition <= 0 ||\n this._vz !== 0 ||\n speed < config.minSpeed\n ) {\n this._timeSinceSurfaceSpawn = 0;\n\n return;\n }\n\n const interval =\n 1000.0 / (config.rate * Math.min(speed / config.fullSpeed, 1));\n\n this._timeSinceSurfaceSpawn += deltaMs;\n\n while (this._timeSinceSurfaceSpawn >= interval) {\n this._spawnSurfaceDust(kind, config);\n this._timeSinceSurfaceSpawn -= interval;\n }\n }\n\n _stopTimer() {\n if (this._tickListener) {\n Ticker.shared.remove(this._tickListener);\n this._tickListener = null;\n }\n }\n\n destroy(options) {\n this._stopTimer();\n\n // все активные частицы в пул\n for (let i = 0; i < this._particles.length; i += 1) {\n ParticlePool.release(this._particles[i].view);\n }\n\n this._particles = [];\n\n // children: true идёт после ...options и не переопределяется извне:\n // отключение children оставило бы уже возвращённые в пул частицы\n // висеть в живом _particleContainer (двойное использование)\n super.destroy({\n texture: false,\n textureSource: false,\n ...options,\n children: true,\n });\n }\n}\n","import { Graphics } from 'pixi.js';\nimport BaseEffect from '../BaseEffect.js';\nimport { lerp, clamp } from 'vimp-engine/lib/math.js';\nimport { tracer as tracerConfig } from '../../../../config/render.js';\n\n/**\n * Видимый отрезок трассера: расстояния хвоста и головы от дула. Хвост\n * растёт прямо от дула — отступа нет, линия выходит из ствола, — и не\n * длиннее `max(trailLength, totalDist · trailShare)`; к концу пути он\n * стягивается к цели. Доля от длины луча держит у дальнего выстрела линию\n * от ствола в первых кадрах: голова там проходит сотни единиц за кадр.\n *\n * @param {object} p\n * @param {number} p.progress 0..1 — доля пройденного пути\n * @param {number} p.totalDist длина луча\n * @param {number} p.trailLength наименьшая наибольшая длина хвоста\n * @param {number} [p.trailShare] доля луча, которой хвост может достигать\n * @returns {{ tail: number, head: number }}\n */\nexport function tracerSpan({\n progress,\n totalDist,\n trailLength,\n trailShare = 0,\n}) {\n const head = totalDist * clamp(progress, 0, 1);\n const longest = Math.max(trailLength, totalDist * trailShare);\n const maxTrail = lerp(longest, 0, clamp(progress, 0, 1));\n\n return { tail: Math.max(0, head - maxTrail), head };\n}\n\nexport default class TracerEffect extends BaseEffect {\n // `options.pieces` — куски луча по уровням `[{ from, to, level }]`\n // (`tracerPieces`), `options.layerFor(level)` — контейнер, в котором\n // рисуется кусок уровня (своя проекция и zIndex). Без них — один кусок\n // во весь луч внутри самого эффекта\n constructor(\n startX,\n startY,\n endX,\n endY,\n onComplete,\n config = tracerConfig,\n options = {},\n ) {\n super(onComplete);\n\n this.startPositionX = startX;\n this.startPositionY = startY;\n this.endPositionX = endX;\n this.endPositionY = endY;\n this.config = config;\n\n // свечение складывается со сценой: трассер — это свет, а не краска.\n // По графике на уровень: в чужом контейнере она не ребёнок эффекта и\n // уничтожается им самим\n this._layerFor = options.layerFor || null;\n this.pieces = options.pieces?.length\n ? options.pieces\n : [{ from: 0, to: Infinity, level: null }];\n this._graphics = new Map();\n\n for (const { level } of this.pieces) {\n if (!this._graphics.has(level)) {\n const graphics = new Graphics();\n\n graphics.blendMode = 'add';\n (this._layerFor?.(level) || this).addChild(graphics);\n this._graphics.set(level, graphics);\n }\n }\n\n this.graphics = this._graphics.values().next().value;\n\n this.elapsedTime = 0;\n this.progress = 0;\n this._aim();\n\n this.animationDuration = clamp(\n (this.totalDist / config.speed) * 1000,\n config.minDuration,\n config.maxDuration,\n );\n }\n\n // направление и длина луча от текущего дула к неподвижной цели\n _aim() {\n const dx = this.endPositionX - this.startPositionX;\n const dy = this.endPositionY - this.startPositionY;\n\n this.totalDist = Math.hypot(dx, dy);\n\n if (this.totalDist > 0.001) {\n this.nx = dx / this.totalDist;\n this.ny = dy / this.totalDist;\n } else {\n this.nx = 0;\n this.ny = 0;\n this.totalDist = 0;\n }\n }\n\n /**\n * Дуло сдвинулось (танк едет): луч переносится ЦЕЛИКОМ — начало в новое\n * дуло, конец на тот же сдвиг, направление и длина прежние. Перестраивать\n * луч к неподвижной цели нельзя: у танка вплотную к стене дуло уже в\n * стене, луч почти нулевой, и при езде вдоль стены он растягивался бы\n * назад к старой точке. Прогресс и длительность пролёта не меняются\n */\n shiftTo(x, y) {\n if (this.isComplete) {\n return;\n }\n\n this.endPositionX += x - this.startPositionX;\n this.endPositionY += y - this.startPositionY;\n this.startPositionX = x;\n this.startPositionY = y;\n this._draw();\n }\n\n _clear() {\n for (const graphics of this._graphics.values()) {\n graphics.clear();\n }\n }\n\n _draw() {\n this._clear();\n\n const { tail, head } = tracerSpan({\n progress: this.progress,\n totalDist: this.totalDist,\n trailLength: this.config.trailLength,\n trailShare: this.config.trailShare,\n });\n\n if (head - tail > 0.001) {\n this._drawTrail(\n tail,\n head,\n lerp(this.config.alphaStart, this.config.alphaEnd, this.progress),\n );\n }\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n this.elapsedTime += deltaMs;\n this.progress = Math.min(this.elapsedTime / this.animationDuration, 1);\n this._draw();\n\n if (this.elapsedTime >= this.animationDuration) {\n this._clear();\n this._completeEffect();\n }\n }\n\n // хвост гаснет к дулу: подотрезки с растущей к голове альфой (по квадрату). Сначала\n // все слои свечения, потом ядро — иначе свечение следующего подотрезка\n // легло бы поверх ядра предыдущего. Подотрезок, пересекающий границу\n // уровней, режется по ней: каждая часть — в графику своего уровня\n _drawTrail(tail, head, alpha) {\n const { color, coreColor, coreWidth, glowWidth, glowAlpha } = this.config;\n const steps = Math.max(1, this.config.fadeSteps);\n const pieces = [];\n\n for (let i = 0; i < steps; i += 1) {\n const from = lerp(tail, head, i / steps);\n const to = lerp(tail, head, (i + 1) / steps);\n\n // квадрат: яркость собрана у головы, хвост быстро сходит на нет —\n // так выглядит смазанная в движении точка, а не ровная полоса\n const share = (i + 1) / steps;\n\n pieces.push({ from, to, fade: share * share });\n }\n\n const parts = this._split(pieces);\n\n for (const { graphics, from, to, fade } of parts) {\n graphics\n .moveTo(from[0], from[1])\n .lineTo(to[0], to[1])\n .stroke({\n width: glowWidth,\n color,\n alpha: alpha * glowAlpha * fade,\n cap: 'round',\n });\n }\n\n for (const { graphics, from, to, fade } of parts) {\n graphics\n .moveTo(from[0], from[1])\n .lineTo(to[0], to[1])\n .stroke({\n width: coreWidth,\n color: coreColor,\n alpha: alpha * fade,\n cap: 'round',\n });\n }\n }\n\n // подотрезки `[{ from, to, fade }]` (дистанции от дула) → части по кускам\n // уровней `{ graphics, from: [x, y], to: [x, y], fade }`\n _split(segments) {\n const point = distance => [\n this.startPositionX + this.nx * distance,\n this.startPositionY + this.ny * distance,\n ];\n const parts = [];\n\n for (const { from, to, fade } of segments) {\n for (const piece of this.pieces) {\n const a = Math.max(from, piece.from);\n const b = Math.min(to, piece.to);\n\n if (b - a > 1e-6) {\n parts.push({\n graphics: this._graphics.get(piece.level),\n from: point(a),\n to: point(b),\n fade,\n });\n }\n }\n }\n\n return parts;\n }\n\n destroy(options) {\n for (const graphics of this._graphics.values()) {\n if (!graphics.destroyed) {\n graphics.clear();\n\n // графика в чужом контейнере — не ребёнок эффекта\n if (graphics.parent !== this) {\n graphics.destroy();\n }\n }\n }\n\n this._graphics.clear();\n super.destroy(options);\n }\n}\n","// Куски трассера по уровням: сегменты луча из ядра (`shots.path`) →\n// непересекающиеся отрезки `[{ from, to, level }]` от 0 до `total`.\n//\n// Правила сегментов (`core/src/shot_levels.rs`): вниз луч падает в первой\n// клетке без плиты — сегменты идут встык; вверх у кромки плиты верхний\n// уровень получает окно на клетку ПОВЕРХ продолжающегося нижнего — сегменты\n// перекрываются. Рисовать окно на верхнем уровне нельзя: наземный трассер,\n// прошедший под кромкой моста, прыгнул бы на плиту. Поэтому в перекрытии\n// выигрывает сегмент, начатый РАНЬШЕ (луч продолжает свой уровень).\n//\n// Уровень конца — из строки трассера (`W1_END_LEVEL`): попадание судит хост,\n// и осколки лежат там же. Если конец достался окну верхнего уровня\n// (попадание в цель на кромке), последний кусок — от начала этого окна.\n// Без сегментов — один кусок на уровне конца, как было.\nexport function tracerPieces(segments, total, endLevel) {\n const end = endLevel || 0;\n\n if (!(total > 0)) {\n return [{ from: 0, to: 0, level: end }];\n }\n\n const list = (segments || []).filter(\n segment => segment.t1 > segment.t0 && segment.t0 < total,\n );\n\n if (list.length === 0) {\n return [{ from: 0, to: total, level: end }];\n }\n\n const clip = t => Math.min(total, Math.max(0, t));\n const points = [\n ...new Set([0, total, ...list.flatMap(s => [clip(s.t0), clip(s.t1)])]),\n ].sort((a, b) => a - b);\n const pieces = [];\n\n for (let i = 0; i + 1 < points.length; i += 1) {\n const from = points[i];\n const to = points[i + 1];\n\n if (to - from < 1e-6) {\n continue;\n }\n\n const mid = (from + to) / 2;\n let owner = null;\n\n for (const segment of list) {\n if (\n segment.t0 <= mid &&\n mid < segment.t1 &&\n (owner === null || segment.t0 < owner.t0)\n ) {\n owner = segment;\n }\n }\n\n // дыра между сегментами — продолжение прежнего уровня\n const level = owner ? owner.level : (pieces.at(-1)?.level ?? end);\n\n appendPiece(pieces, from, to, level);\n }\n\n if (pieces.at(-1).level === end) {\n return pieces;\n }\n\n // конец луча — на уровне окна: последний кусок начинается там, где\n // начался сегмент уровня конца, накрывающий конец\n const window = list\n .filter(s => s.level === end && s.t0 < total && s.t1 >= total - 1e-3)\n .sort((a, b) => b.t0 - a.t0)[0];\n const cut = window ? clip(window.t0) : pieces.at(-1).from;\n const head = [];\n\n for (const piece of pieces) {\n if (piece.from < cut) {\n appendPiece(head, piece.from, Math.min(piece.to, cut), piece.level);\n }\n }\n\n appendPiece(head, cut, total, end);\n\n return head;\n}\n\n// добавляет кусок, склеивая его с предыдущим того же уровня\nfunction appendPiece(pieces, from, to, level) {\n if (to - from < 1e-6) {\n return;\n }\n\n const last = pieces.at(-1);\n\n if (last && last.level === level && Math.abs(last.to - from) < 1e-6) {\n last.to = to;\n } else {\n pieces.push({ from, to, level });\n }\n}\n","import { Sprite } from 'pixi.js';\nimport BaseEffect from '../BaseEffect.js';\nimport { lerp, randomRange } from 'vimp-engine/lib/math.js';\n\nexport default class ImpactEffect extends BaseEffect {\n // Осколки живут в мировых координатах и остаются лежать там, где пуля\n // встретила препятствие, — даже если задетый ящик поедет дальше. Задача\n // «попасть в правильную точку» решается на стороне ShotEffectController:\n // он пересчитывает точку удара по актуальному трансформу ящика в момент\n // запуска эффекта (см. якорь трассера в core/src/client/shot.rs).\n constructor(x, y, impactDirectionX, impactDirectionY, onComplete, assets) {\n super(onComplete);\n\n this.x = x; // координата X центра эффекта\n this.y = y; // координата Y центра эффекта\n\n this._assets = assets;\n\n const { texture, contentSize } = this._assets.impactParticleTexture;\n\n this._particleTexture = texture;\n\n // размер осколка задан в юнитах мира и нормируется\n // по нарисованному кругу текстуры, а не по холсту с запасом под размытие\n this._textureContentSize = contentSize;\n\n this.config = {\n particleCount: randomRange(2, 4), // количество осколков\n particleMinSize: 0.8, // минимальный размер осколка\n particleMaxSize: 2.4, // максимальный размер осколка\n\n // начальная скорость разлета\n minInitialSpeed: 10, // пикселей в секунду\n maxInitialSpeed: 300, // пикселей в секунду\n\n // жизненный цикл осколков\n minLifetime: 8000, // мс\n maxLifetime: 15000, // мс\n\n color: [0x333333, 0x222222, 0x444444], // массив цветов для осколков\n\n fadeOutStart: 0.8, // начинать угасание, когда прошло 80% времени жизни\n\n // управление направлением разлета\n impactDirectionX, // компонента X базового направления отлета\n impactDirectionY, // компонента Y\n spreadAngle: 60, // угол разброса в градусах\n\n // параметры для управления движением и остановкой\n // коэффициент сопротивления (чем выше, тем быстрее остановка)\n dragCoefficient: 13,\n // порог скорости, ниже которого частица считается \"остановившейся\"\n minSpeedThreshold: 1.0,\n // сколько времени частица лежит неподвижно перед угасанием (мс)\n lingerDuration: 6000,\n };\n\n // обработка случая, когда impactDirection (0,0) - например, выстрел в точку\n // в этом случае частицы разлетятся во все стороны\n if (\n this.config.impactDirectionX === 0 &&\n this.config.impactDirectionY === 0\n ) {\n this.useOmnidirectionalSpread = true; // флаг для разлета во все стороны\n } else {\n this.useOmnidirectionalSpread = false;\n }\n\n this.particlesData = []; // хранение данные для управления логикой\n this.elapsedTime = 0;\n\n this._createParticles();\n }\n\n _createParticles() {\n let baseAngleRad;\n\n if (!this.useOmnidirectionalSpread) {\n baseAngleRad = Math.atan2(\n this.config.impactDirectionY,\n this.config.impactDirectionX,\n );\n }\n\n const spreadAngleRad = this.config.spreadAngle * (Math.PI / 180);\n const halfSpreadRad = spreadAngleRad / 2;\n\n for (let i = 0, len = this.config.particleCount; i < len; i += 1) {\n let particleAngle;\n\n if (this.useOmnidirectionalSpread) {\n particleAngle = Math.random() * Math.PI * 2;\n } else {\n particleAngle =\n baseAngleRad + randomRange(-halfSpreadRad, halfSpreadRad);\n }\n\n const initialSpeed = randomRange(\n this.config.minInitialSpeed,\n this.config.maxInitialSpeed,\n );\n\n // спрайт для частицы\n const sprite = new Sprite(this._particleTexture);\n sprite.anchor.set(0.5);\n\n const particleData = {\n sprite, // ссылка на спрайт\n x: 0,\n y: 0,\n vx: Math.cos(particleAngle) * initialSpeed,\n vy: Math.sin(particleAngle) * initialSpeed,\n size: randomRange(\n this.config.particleMinSize,\n this.config.particleMaxSize,\n ),\n color: Array.isArray(this.config.color)\n ? this.config.color[\n Math.floor(Math.random() * this.config.color.length)\n ]\n : this.config.color,\n lifetime: randomRange(this.config.minLifetime, this.config.maxLifetime),\n age: 0,\n alpha: 1.0,\n active: true,\n isMoving: true,\n timeSinceStopped: 0,\n };\n\n // начальный цвет\n sprite.tint = particleData.color;\n\n this.particlesData.push(particleData);\n this.addChild(sprite);\n }\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n this.elapsedTime += deltaMs;\n const deltaSeconds = deltaMs / 1000;\n let activeParticlesCount = 0;\n\n for (let i = 0, len = this.particlesData.length; i < len; i += 1) {\n const pData = this.particlesData[i];\n\n if (!pData.active) {\n continue;\n }\n\n activeParticlesCount += 1;\n pData.age += deltaMs;\n\n if (pData.isMoving) {\n // сопротивление среды (drag)\n const speed = Math.hypot(pData.vx, pData.vy);\n\n if (speed > 0) {\n const dragForceMagnitude =\n speed * this.config.dragCoefficient * deltaSeconds;\n\n const speedReductionFactor = Math.max(\n 0,\n 1 - dragForceMagnitude / speed,\n );\n\n pData.vx *= speedReductionFactor;\n pData.vy *= speedReductionFactor;\n }\n\n pData.x += pData.vx * deltaSeconds;\n pData.y += pData.vy * deltaSeconds;\n\n const currentSpeed = Math.hypot(pData.vx, pData.vy);\n\n // если частица остановилась\n if (currentSpeed < this.config.minSpeedThreshold) {\n pData.isMoving = false;\n pData.vx = 0;\n pData.vy = 0;\n }\n // частица остановилась\n } else {\n pData.timeSinceStopped += deltaMs;\n }\n\n // логика угасания и завершения жизни\n const currentLifetimeProgress = pData.age / pData.lifetime;\n\n if (\n !pData.isMoving &&\n pData.timeSinceStopped >= this.config.lingerDuration\n ) {\n // ускоренное угасание после остановки и задержки\n const timeIntoFade =\n pData.timeSinceStopped - this.config.lingerDuration;\n // угасание за короткое время, например, 1 секунда после lingerDuration\n const quickFadeDuration = Math.min(\n 500,\n pData.lifetime * (1 - this.config.fadeOutStart),\n );\n pData.alpha = lerp(\n 1.0,\n 0.0,\n Math.min(timeIntoFade / quickFadeDuration, 1.0),\n );\n } else if (currentLifetimeProgress >= this.config.fadeOutStart) {\n // стандартное угасание по lifetime\n const timeIntoFade =\n pData.age - pData.lifetime * this.config.fadeOutStart;\n const fadeDuration = pData.lifetime * (1 - this.config.fadeOutStart);\n pData.alpha =\n fadeDuration > 0\n ? lerp(1.0, 0.0, Math.min(timeIntoFade / fadeDuration, 1.0))\n : 0.0;\n }\n\n if (pData.age >= pData.lifetime || pData.alpha < 0.01) {\n pData.active = false; // частица неактивна\n pData.alpha = 0; // полностью прозрачна\n }\n\n // обновление спрайта\n const pSprite = pData.sprite;\n\n pSprite.position.set(pData.x, pData.y);\n pSprite.alpha = pData.alpha;\n pSprite.visible = pData.active;\n\n const scale = pData.size / this._textureContentSize;\n\n pSprite.scale.set(scale);\n }\n\n if (activeParticlesCount === 0 && this._isStarted) {\n this._completeEffect();\n }\n }\n\n destroy(options) {\n this.particlesData = [];\n super.destroy(options);\n }\n}\n","import { Graphics } from 'pixi.js';\nimport BaseEffect from '../BaseEffect.js';\nimport { clamp } from 'vimp-engine/lib/math.js';\nimport { muzzleFlash as muzzleFlashConfig } from '../../../../config/render.js';\n\n/**\n * Языки пламени одного выстрела: случайные длина и угол каждого в пределах\n * конфига. Считаются раз при выстреле — вспышка не мерцает формой.\n *\n * @param {object} config `muzzleFlash` из src/config/render.js\n * @param {() => number} rng 0..1\n * @returns {{ forward: {angle: number, length: number}[],\n * sides: number[] }} углы — относительно оси выстрела, длины — доли\n */\nexport function rollMuzzleFlash(config, rng = Math.random) {\n const forward = [];\n const count = Math.max(1, config.spikes);\n\n for (let i = 0; i < count; i += 1) {\n // языки раскладываются веером по всему разбросу, со случайным сдвигом\n // внутри своей доли — без комков и без ровного частокола\n const slot = count === 1 ? 0.5 : (i + rng()) / count;\n\n forward.push({\n angle: (slot * 2 - 1) * config.spread,\n length: 1 - rng() * config.jitter,\n });\n }\n\n // боковые выбросы тоже неровные\n const sides = [1 - rng() * config.jitter, 1 - rng() * config.jitter];\n\n return { forward, sides };\n}\n\n/**\n * Полигоны вспышки в её собственных осях: дуло в (0, 0), выстрел вдоль\n * `(dirX, dirY)`. Каждый язык — узкий треугольник; каждый слой (`layers`)\n * рисует все языки в своём масштабе и со своей яркостью, так край выходит\n * мягким без текстур и фильтров. С `t` от 0 до 1 вспышка быстро гаснет\n * (яркость по квадрату) и немного сжимается.\n *\n * @param {object} p\n * @param {number} p.dirX\n * @param {number} p.dirY\n * @param {number} p.t 0..1 — доля прожитой длительности\n * @param {object} p.roll `rollMuzzleFlash`\n * @param {object} p.config `muzzleFlash` из src/config/render.js\n * @returns {{ points: number[], color: number, alpha: number }[]}\n */\nexport function muzzleFlashShape({ dirX, dirY, t, roll, config }) {\n const life = 1 - clamp(t, 0, 1);\n const glow = life * life;\n const size = 1 - config.shrink * (1 - life);\n const baseAngle = Math.atan2(dirY, dirX);\n const polygons = [];\n\n // язык: треугольник от дула вдоль угла `angle`\n const spike = (angle, length, width) => {\n const ax = Math.cos(angle);\n const ay = Math.sin(angle);\n const half = width / 2;\n\n return [\n -ay * half,\n ax * half,\n ax * length,\n ay * length,\n ay * half,\n -ax * half,\n ];\n };\n\n for (const layer of config.layers) {\n const scale = layer.scale * size;\n const color = layer.core ? config.coreColor : config.color;\n const alpha = layer.alpha * glow;\n\n if (alpha <= 0) {\n continue;\n }\n\n for (const { angle, length } of roll.forward) {\n polygons.push({\n points: spike(\n baseAngle + angle,\n config.length * length * scale,\n config.width * scale,\n ),\n color,\n alpha,\n });\n }\n\n roll.sides.forEach((length, i) => {\n polygons.push({\n points: spike(\n baseAngle + (i === 0 ? Math.PI / 2 : -Math.PI / 2),\n config.sideLength * length * scale,\n config.sideWidth * scale,\n ),\n color,\n alpha,\n });\n });\n }\n\n return polygons;\n}\n\nexport default class MuzzleFlashEffect extends BaseEffect {\n constructor(\n x,\n y,\n dirX,\n dirY,\n onComplete,\n config = muzzleFlashConfig,\n rng = Math.random,\n ) {\n super(onComplete);\n\n // вспышка рисуется вокруг своего начала: контроллер двигает и\n // масштабирует её целиком, когда уровень дула не совпадает с уровнем\n // конца луча\n this.position.set(x, y);\n\n this.dirX = dirX;\n this.dirY = dirY;\n this.config = config;\n this.roll = rollMuzzleFlash(config, rng);\n this.elapsedTime = 0;\n\n this.graphics = new Graphics();\n this.graphics.blendMode = 'add';\n this.addChild(this.graphics);\n }\n\n _update(deltaMs) {\n if (this.isComplete) {\n return;\n }\n\n this.elapsedTime += deltaMs;\n this.graphics.clear();\n\n const t = this.config.duration\n ? this.elapsedTime / this.config.duration\n : 1;\n\n if (t < 1) {\n const polygons = muzzleFlashShape({\n dirX: this.dirX,\n dirY: this.dirY,\n t,\n roll: this.roll,\n config: this.config,\n });\n\n for (const { points, color, alpha } of polygons) {\n this.graphics.poly(points).fill({ color, alpha });\n }\n } else {\n this._completeEffect();\n }\n }\n\n destroy(options) {\n if (this.graphics) {\n this.graphics.clear();\n }\n\n super.destroy(options);\n }\n}\n","import { Container } from 'pixi.js';\nimport TracerEffect from './TracerEffect.js';\nimport { tracerPieces } from './tracerPieces.js';\nimport ImpactEffect from './ImpactEffect.js';\nimport MuzzleFlashEffect from './MuzzleFlashEffect.js';\nimport { levelZ } from '../../../levelZ.js';\nimport { cameraCenter } from '../../../camera.js';\nimport { applyParallax } from '../../../parallax.js';\nimport { EMISSIVE_BASE_Z } from '../../../lighting/lightMath.js';\nimport {\n parallax as parallaxConfig,\n lighting as lightingConfig,\n} from '../../../../config/render.js';\nimport {\n W1_START_X,\n W1_START_Y,\n W1_START_LEVEL,\n W1_END_X,\n W1_END_Y,\n W1_BODY_X,\n W1_BODY_Y,\n W1_WAS_HIT,\n W1_SHOOTER_ID,\n W1_END_LEVEL,\n W1_ANCHOR,\n} from '../../../snapshotFields.js';\n\n// базовый zIndex трассера и осколков внутри своего уровня\nconst SHOT_BASE_Z = 2;\n\nexport default class ShotEffectController extends Container {\n constructor(data, assets, dependencies) {\n super();\n\n this.startPositionX = data[W1_START_X];\n this.startPositionY = data[W1_START_Y];\n this.endPositionX = data[W1_END_X];\n this.endPositionY = data[W1_END_Y];\n this.soundPositionX = data[W1_BODY_X];\n this.soundPositionY = data[W1_BODY_Y];\n this.hit = data[W1_WAS_HIT];\n\n // 2.5D: осколки и вспышка — на уровне КОНЦА луча, иначе осколки\n // провалятся под мост. Трассер режется по сегментам уровней ядра и\n // рисуется кусками на своих уровнях (`_layerFor`): луч с моста идёт над\n // плитой и падает за кромкой. Уровень начала (`W1_START_LEVEL`) нужен\n // сегментам и вспышке выстрела на стволе\n this.endLevel = data[W1_END_LEVEL] || 0;\n this.startLevel = data[W1_START_LEVEL] || 0;\n this.zIndex = levelZ(SHOT_BASE_Z, this.endLevel);\n\n // якорь попадания в динамику карты — одиннадцатый элемент строки, только\n // у своего локально предсказанного трассера (см. build_tracer в\n // core/src/client/shot.rs); авторитетные трассеры (длина 10) его не\n // несут — data[W1_ANCHOR] === undefined\n this.anchorKey = null;\n this.anchorLocalX = 0;\n this.anchorLocalY = 0;\n\n if (Array.isArray(data[W1_ANCHOR])) {\n [this.anchorKey, this.anchorLocalX, this.anchorLocalY] = data[W1_ANCHOR];\n }\n\n this._assets = assets;\n this._soundManager = dependencies.soundManager;\n\n // свой ли это выстрел: звук своего выстрела берёт позицию корпуса\n // стрелка на момент выстрела, а слушатель — центр камеры, то есть\n // предсказанный свой танк. Предсказанный локальный выстрел и его\n // авторитетное эхо не совпадают по позиции — один и тот же выстрел\n // звучал то по центру, то целиком в одно ухо. Свой выстрел принадлежит\n // игроку, а не миру, поэтому не панорамируется\n this._isLocalShot =\n dependencies.localPlayer?.is(data[W1_SHOOTER_ID]) === true;\n\n // отдача: танк стрелка откатывается (src/client/recoil.js). Эффект\n // создаётся раз на выстрел — свой предсказанный, а авторитетный дубль\n // ядро отфильтровывает\n dependencies.shots?.fired(data[W1_SHOOTER_ID]);\n this._mapDynamics = dependencies.mapDynamics || null;\n // дуло стрелка: пока идут трассер и вспышка, они держатся у ствола\n // едущего танка (сервис `shots`, src/client/shotEvents.js)\n this._shots = dependencies.shots || null;\n this._shooterId = data[W1_SHOOTER_ID];\n this._levelView = dependencies.levelView || null;\n // ночь: вспышка выстрела на стволе (no-op днём)\n this._lighting = dependencies.lighting || null;\n\n // трассер и осколки уступают видимость игроку под плитой ровно так же,\n // как всё остальное на верхнем уровне (единая формула — в levelView).\n // `onRender` — аксессор Container, назначается свойством\n this._renderer = dependencies.renderer || null;\n\n if (this._levelView || this._renderer) {\n this.onRender = () => {\n if (this._levelView) {\n this.alpha = this._levelView.alphaFor(\n this.endLevel,\n this.endPositionX,\n this.endPositionY,\n );\n this.tint = this._levelView.tintFor(this.endLevel);\n }\n\n // проекция высоты: трассер и осколки на мосту стоят на мосту.\n // Дети контроллера авторятся в мировых координатах, сам он\n // единичный — трансформ контейнера даёт им ровно offsetPoint\n const camera = cameraCenter(this.parent, this._renderer);\n\n applyParallax(this, camera, this.endLevel * parallaxConfig.shear, 1);\n\n for (const [level, layer] of this.layers) {\n if (this._levelView) {\n layer.alpha = this._levelView.alphaFor(level, layer.refX, layer.refY);\n layer.tint = this._levelView.tintFor(level);\n }\n\n applyParallax(layer, camera, level * parallaxConfig.shear, 1);\n }\n\n this._followMuzzle();\n this._placeFlash(camera);\n };\n }\n\n this.tracer = null;\n // контейнеры кусков трассера по уровням (см. _layerFor): level -> Container\n this.layers = new Map();\n this.impact = null;\n // вспышка у дула; пока её нет, ждать нечего\n this.flash = null;\n this._flashComplete = true;\n this._isDestroyed = false;\n\n // флаги для управления жизненным циклом\n this._visualsComplete = false;\n this._soundComplete = false;\n\n // звук выстрела в момент старта эффекта\n this._soundId = this._soundManager.registerSound(\n 'shot',\n {\n position: { x: this.soundPositionX, y: this.soundPositionY },\n spatial: !this._isLocalShot,\n },\n () => {\n this._soundComplete = true;\n this._soundId = null;\n this._tryDestroy();\n },\n );\n\n // если по какой-то причине звук не запустился,\n // сразу считаем его \"завершенным\"\n if (!this._soundId) {\n this._soundComplete = true;\n }\n }\n\n run() {\n if (this._isDestroyed) {\n return;\n }\n\n this._lighting?.flash({\n ...lightingConfig.flash.shot,\n level: this.startLevel,\n x: this.startPositionX,\n y: this.startPositionY,\n z: this.startLevel,\n });\n\n // вспышка у дула — по направлению луча: оно и есть направление ствола\n const dx = this.endPositionX - this.startPositionX;\n const dy = this.endPositionY - this.startPositionY;\n const dist = Math.hypot(dx, dy);\n\n if (dist > 0.001) {\n this._flashComplete = false;\n this.flash = new MuzzleFlashEffect(\n this.startPositionX,\n this.startPositionY,\n dx / dist,\n dy / dist,\n () => {\n this._flashComplete = true;\n this._tryDestroy();\n },\n );\n this.addChild(this.flash);\n this.flash.run();\n }\n\n const pieces = tracerPieces(\n this._shots?.path?.(\n this.startPositionX,\n this.startPositionY,\n this.endPositionX,\n this.endPositionY,\n this.startLevel,\n ),\n dist,\n this.endLevel,\n );\n\n this.tracer = new TracerEffect(\n this.startPositionX,\n this.startPositionY,\n this.endPositionX,\n this.endPositionY,\n this._onTracerComplete.bind(this),\n undefined,\n {\n pieces,\n layerFor: level => this._layerFor(level, pieces),\n },\n );\n\n this.addChild(this.tracer);\n this.tracer.run();\n }\n\n // Контейнер куска трассера уровня `level`. Днём кусок уровня конца\n // рисуется в самом контроллере, как раньше. Остальные уровни — соседние\n // контейнеры на сцене со своими zIndex и проекцией. Ночью трассер — свет:\n // под картой освещённости (multiply) вдали от фар он гас до `ambient`, и\n // дальний выстрел «не появлялся», поэтому все его куски — над картой\n // своего уровня (эмиссив). Осколки и вспышка остаются под ней\n _layerFor(level, pieces) {\n const night = Boolean(this._lighting?.isNight?.());\n\n if ((!night && level === this.endLevel) || !this.parent) {\n return this;\n }\n\n let layer = this.layers.get(level);\n\n if (!layer) {\n const piece = pieces.find(entry => entry.level === level);\n const mid = piece ? (piece.from + piece.to) / 2 : 0;\n const dx = this.endPositionX - this.startPositionX;\n const dy = this.endPositionY - this.startPositionY;\n const total = Math.hypot(dx, dy) || 1;\n\n layer = new Container();\n layer.label = `shot-tracer-${level}`;\n layer.eventMode = 'none';\n layer.zIndex = levelZ(night ? EMISSIVE_BASE_Z : SHOT_BASE_Z, level);\n // точка, по которой слой уступает видимость игроку под плитой\n layer.refX = this.startPositionX + (dx / total) * mid;\n layer.refY = this.startPositionY + (dy / total) * mid;\n this.parent.addChild(layer);\n this.layers.set(level, layer);\n }\n\n return layer;\n }\n\n // Танк едет, эффект живёт в мире: за время пролёта трассера корпус\n // проезжает свою длину, и при стрельбе вбок хвост отрывался от ствола. А\n // чужой танк ещё и нарисован с задержкой интерполяции. Поэтому трассер и\n // вспышка идут за ТЕКУЩИМ дулом стрелка: луч переносится целиком\n // (`TracerEffect.shiftTo`), без поворота. Осколки попадания появляются в\n // исходной точке удара — стена на месте\n _followMuzzle() {\n if (!this._shots || this.impact) {\n return;\n }\n\n const tracerRunning = this.tracer && !this.tracer.isComplete;\n const flashRunning = this.flash && !this.flash.isComplete;\n\n if (!tracerRunning && !flashRunning) {\n return;\n }\n\n const muzzle = this._shots.muzzle(this._shooterId);\n\n if (!muzzle) {\n return;\n }\n\n this.startPositionX = muzzle.x;\n this.startPositionY = muzzle.y;\n\n if (tracerRunning) {\n this.tracer.shiftTo(muzzle.x, muzzle.y);\n }\n }\n\n // Контроллер рисуется в проекции уровня КОНЦА луча, а дуло — на уровне\n // начала. Если они разные (выстрел с моста вниз), вспышку переносим так,\n // чтобы после проекции контроллера она легла в проекцию дула:\n // q = cam + (p − cam)·(1 + k_s)/(1 + k_e), масштаб — то же отношение\n _placeFlash(camera) {\n if (!this.flash || this.flash.destroyed) {\n return;\n }\n\n // Кадр без центра камеры: проекции нет ни у кого, и `applyParallax`\n // сбросил трансформ самого контроллера в единичный — дети рисуются по\n // сырым мировым точкам. Оставить вспышку в проекции ПРОШЛОГО кадра\n // значило бы увести её от дула на величину параллакса, поэтому здесь\n // тот же случай, что `ratio === 1`: вспышка ровно в точке вылета\n if (!camera) {\n this.flash.position.set(this.startPositionX, this.startPositionY);\n this.flash.scale.set(1);\n\n return;\n }\n\n const ratio =\n (1 + this.startLevel * parallaxConfig.shear) /\n (1 + this.endLevel * parallaxConfig.shear);\n\n this.flash.position.set(\n camera.x + (this.startPositionX - camera.x) * ratio,\n camera.y + (this.startPositionY - camera.y) * ratio,\n );\n this.flash.scale.set(ratio);\n }\n\n // трассер завершил анимацию.\n // Его графика уже должна быть очищена TracerEffect'ом.\n // сам объект TracerEffect будет уничтожен в destroy\n _onTracerComplete() {\n if (this._isDestroyed) {\n return;\n }\n\n if (this.hit) {\n let impactX = this.endPositionX;\n let impactY = this.endPositionY;\n\n // ящик мог уехать за 45-80 мс анимации трассера (TracerEffect): точка\n // удара пересчитывается из ТЕКУЩЕГО трансформа ящика, а не из того,\n // что был в момент выстрела, — иначе облако осколков окажется позади\n // уехавшего ящика. Дальше осколки остаются лежать на месте: за ящиком\n // они не следуют (ImpactEffect работает в мире)\n if (this.anchorKey !== null && this._mapDynamics) {\n const point = this._mapDynamics.toWorld(\n this.anchorKey,\n this.anchorLocalX,\n this.anchorLocalY,\n );\n\n if (point) {\n impactX = point.x;\n impactY = point.y;\n }\n }\n\n const dx = impactX - this.startPositionX;\n const dy = impactY - this.startPositionY;\n const dist = Math.hypot(dx, dy);\n let impactDirectionX = 0,\n impactDirectionY = 0;\n\n if (dist > 0.001) {\n impactDirectionX = -(dx / dist);\n impactDirectionY = -(dy / dist);\n }\n\n this.impact = new ImpactEffect(\n impactX,\n impactY,\n impactDirectionX,\n impactDirectionY,\n this._onImpactComplete.bind(this), // callback\n this._assets,\n );\n\n this.addChild(this.impact);\n this.impact.run();\n\n // иначе, если попадания не было,\n // то после завершения трассера эффект считается завершенным\n } else {\n // визуальная часть завершена, попытка уничтожить объект\n this._visualsComplete = true;\n this._tryDestroy();\n }\n }\n\n _onImpactComplete() {\n if (this._isDestroyed) {\n return;\n }\n\n // визуальная часть завершена, попытка уничтожить объект\n this._visualsComplete = true;\n this._tryDestroy();\n }\n\n // проверяет, завершены ли звук и визуал, и если да, уничтожает объект\n _tryDestroy() {\n if (this._visualsComplete && this._soundComplete && this._flashComplete) {\n this.destroy();\n }\n }\n\n destroy() {\n if (this._isDestroyed) {\n return;\n }\n\n this._isDestroyed = true;\n\n // этот вызов остается на случай, если эффект уничтожат принудительно,\n // до того как звук закончится сам\n if (this._soundId) {\n this._soundManager.unregisterSound(this._soundId);\n this._soundId = null;\n }\n\n if (this.tracer) {\n this.tracer.destroy();\n this.tracer = null;\n }\n\n for (const layer of this.layers.values()) {\n layer.destroy({ children: true });\n }\n\n this.layers.clear();\n\n if (this.impact) {\n this.impact.destroy();\n this.impact = null;\n }\n\n if (this.flash) {\n this.flash.destroy();\n this.flash = null;\n }\n\n if (this.parent) {\n this.parent.removeChild(this);\n }\n\n // children:true уничтожит все, что еще осталось\n super.destroy({ children: true });\n }\n}\n","import Map from './Map.js';\nimport MapRadar from './MapRadar.js';\nimport TankRadar from './TankRadar.js';\nimport Tank from './Tank.js';\nimport Bomb from './Bomb.js';\nimport ExplosionEffect from './effects/explosion/ExplosionEffectController.js';\nimport Smoke from './Smoke.js';\nimport Tracks from './tracks/index.js';\nimport Dust from './Dust.js';\nimport ShotEffect from './effects/shot/ShotEffectController.js';\n\nexport default {\n Map,\n MapRadar,\n TankRadar,\n Tank,\n Bomb,\n ExplosionEffect,\n Smoke,\n Tracks,\n Dust,\n ShotEffect,\n};\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// создаёт текстуру размытого круга (взрыв, дым, частицы попаданий)\n// params.radius - Радиус круга\n// params.blur - Сила размытия\n// params.color - Цвет заливки (белый - для последующего tint'а)\n// params.quality - Количество проходов размытия\n// renderer - PIXI рендерер\n// возвращает { texture, contentSize }, где contentSize - диаметр самого круга:\n// по нему потребители нормируют масштаб, чтобы запас под размытие\n// не влиял на видимый размер\nexport default function blurredCircleTexture(params, renderer) {\n const { radius, blur, color, quality = 40 } = params;\n const graphics = new Graphics();\n\n // круг остаётся радиусом radius, вокруг него - прозрачный запас под размытие\n const textureSize = (radius + blurMargin(blur)) * 2;\n const center = textureSize / 2;\n\n graphics.circle(center, center, radius);\n graphics.fill(color);\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // без явного padding Pixi рендерит размытие лишь на 2 * strength вокруг\n // фигуры и обрезает его раньше рамки, каким бы большим ни был холст;\n // область шире рамки — чтобы мусор пула с края не попал в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureSize, textureSize),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: radius * 2 };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport { randomRange } from 'vimp-engine/lib/math.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// создаёт набор процедурных текстур воронки: тёмная выемка со светлым бортиком\n// двухтоновая заливка нужна, чтобы след читался и на светлых, и на тёмных картах\n// вариантов несколько, чтобы у воронок не был один и тот же силуэт\n// params.baseRadius - Базовый радиус фигуры.\n// params.irregularity - Степень неровности краев.\n// params.blur - Сила размытия.\n// params.numPoints - Количество точек для построения кривой.\n// params.colorFill - Цвет выемки.\n// params.colorRim - Цвет бортика по контуру.\n// params.rimWidth - Толщина бортика.\n// params.variants - Количество вариантов силуэта.\n// renderer - Рендерер PIXI.\n// возвращает { textures, contentSize }, где contentSize - диаметр силуэта\n// (без запаса под размытие): по нему потребитель нормирует масштаб\nexport default function funnelTexture(params, renderer) {\n const {\n baseRadius,\n irregularity = 0,\n blur = 0,\n numPoints = 20,\n colorFill,\n colorRim = colorFill,\n rimWidth = 0,\n variants = 1,\n } = params;\n\n // силуэт всегда хотя бы один: пустой набор уронил бы FunnelEffect\n const variantCount = Math.max(1, Math.floor(variants) || 1);\n const contentSize = (baseRadius + irregularity + rimWidth) * 2;\n const canvasSize = contentSize + blurMargin(blur) * 2;\n const center = canvasSize / 2;\n const textures = [];\n\n for (let variant = 0; variant < variantCount; variant += 1) {\n const graphics = new Graphics();\n const path = [];\n\n for (let i = 0; i < numPoints; i += 1) {\n const angle = (i / numPoints) * Math.PI * 2;\n const r = baseRadius + randomRange(-irregularity, irregularity);\n\n path.push(center + r * Math.cos(angle), center + r * Math.sin(angle));\n }\n\n // бортик рисуется наружу, чтобы не съедать выемку\n graphics\n .poly(path)\n .fill(colorFill)\n .stroke({ width: rimWidth, color: colorRim, alignment: 1 });\n\n const filter = new BlurFilter({ strength: blur, quality: 10 });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n textures.push(\n renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, canvasSize, canvasSize),\n }),\n );\n\n graphics.destroy(true);\n }\n\n return { textures, contentSize };\n}\n","import { Graphics, Container, Rectangle } from 'pixi.js';\nimport { scaleTint } from '../tilt.js';\n\n// Нормаль в цвет карты нормалей: xyz ∈ [−1, 1] → канал 0..255. Оси —\n// оси ТЕКСТУРЫ: `+x` — нос, `+y` — экранный низ квада, `+z` — к камере.\n// Вектор нормируется здесь, поэтому скат задаётся просто наклоном,\n// например `(−1, 0, 1)` — грань под 45° к корме\nexport function normalColor(x, y, z) {\n const len = Math.hypot(x, y, z) || 1;\n const channel = value => Math.round(((value / len) * 0.5 + 0.5) * 255);\n\n return (channel(x) << 16) | (channel(y) << 8) | channel(z);\n}\n\n// плоский верх: нормаль смотрит прямо в камеру\nexport const FLAT_NORMAL = normalColor(0, 0, 1);\n\n// Рамка фаски: четыре трапеции по краям прямоугольника, у каждой своя\n// заливка. Одна функция для цветной текстуры и для карты нормалей —\n// так фаски совпадают пиксель в пиксель\nfunction bevelFrame(g, { x, y, w, h }, { l, r, t, b }, fills) {\n g.poly([x, y, x + w, y, x + w - r, y + t, x + l, y + t]).fill(fills.t);\n g.poly([x, y + h, x + w, y + h, x + w - r, y + h - b, x + l, y + h - b]).fill(\n fills.b,\n );\n g.poly([x, y, x + l, y + t, x + l, y + h - b, x, y + h]).fill(fills.l);\n g.poly([x + w, y, x + w - r, y + t, x + w - r, y + h - b, x + w, y + h]).fill(\n fills.r,\n );\n}\n\n// Кольцо фаски выпуклого многоугольника с центром в начале координат:\n// трапеция на каждое ребро между контуром и его копией, сжатой в `inner`\n// раз. `fill(nx, ny)` получает внешнюю нормаль ребра в плоскости\nfunction bevelRing(g, points, inner, fill) {\n const count = points.length / 2;\n\n for (let i = 0; i < count; i += 1) {\n const j = (i + 1) % count;\n const ax = points[i * 2];\n const ay = points[i * 2 + 1];\n const bx = points[j * 2];\n const by = points[j * 2 + 1];\n let nx = by - ay;\n let ny = -(bx - ax);\n\n // внешняя сторона — та, что смотрит от центра\n if (nx * (ax + bx) + ny * (ay + by) < 0) {\n nx = -nx;\n ny = -ny;\n }\n\n g.poly([\n ax,\n ay,\n bx,\n by,\n bx * inner,\n by * inner,\n ax * inner,\n ay * inner,\n ]).fill(fill(nx, ny));\n }\n}\n\n// фиксированный размер\nconst _size = 10;\nconst _width = _size * 4;\nconst _height = _size * 3;\n\n// палуба — стальной серый, а не почти белый: свет граней 3D-модели на\n// наклоне поднимает яркость до ~1.1, и при 0xeeeeee палуба упиралась в чистый\n// белый («выгорала» на горке). Запас яркости ≈ 1.27\nconst baseColor = 0xc8c9c4;\n\n// корпус: гусеницы по бортам, между ними палуба с фаской (у носа — пологий\n// лобовой лист) и решётка моторного отсека в корме. Габарит — ровно\n// 40 × 30, как у прежнего корпуса: по нему считаются размер меша и тень\nconst hullRect = { x: -_width / 2, y: -_height / 2, w: _width, h: _height };\nconst deck = { x: -18, y: -9, w: 36, h: 18 };\nconst deckBevel = { l: 2, r: 4, t: 2, b: 2 };\nconst tracks = [\n { x: -19, y: -14, w: 38, h: 5 },\n { x: -19, y: 9, w: 38, h: 5 },\n];\n\n// башня: восьмигранник с фаской, люк, ствол с дульным тормозом\nconst turret = [\n 1.33, -0.42, 0.42, -1, -0.42, -1, -1.33, -0.42, -1.33, 0.42, -0.42, 1, 0.42,\n 1, 1.33, 0.42,\n].map(value => value * _size);\nconst turretInner = 0.6;\nconst barrel = { x: 0.25 * _size, y: -0.25 * _size, w: 2.08 * _size };\nconst brake = {\n x: 2.1 * _size,\n y: -0.36 * _size,\n w: 0.35 * _size,\n h: 0.72 * _size,\n};\nconst hatch = { x: -0.45 * _size, y: 0.35 * _size, r: 0.28 * _size };\n// обводка башни и ствола: тонкая и в тон цвету команды (см. drawTankGun) —\n// светлая широкая читалась мультяшным контуром\nconst outline = { width: 0.08 * _size };\nconst OUTLINE_SHADE = 0.55;\n\n// корпус сверху: общий для плоской текстуры и атласа 3D-модели\n// (`tankModelTexture.js`), поэтому верх модели совпадает с рисунком\nexport function drawTankBody(g) {\n g.rect(hullRect.x, hullRect.y, hullRect.w, hullRect.h).fill(0x555555);\n\n for (const track of tracks) {\n g.rect(track.x, track.y, track.w, track.h).fill(0x3c3c3c);\n\n // траки поперёк хода\n for (let x = track.x + 1; x < track.x + track.w; x += 3) {\n g.rect(x, track.y, 1, track.h).fill(0x2a2a2a);\n }\n }\n\n g.rect(deck.x, deck.y, deck.w, deck.h).fill(baseColor);\n bevelFrame(g, deck, deckBevel, {\n l: 0xa9aaa5,\n r: 0xb6b7b2,\n t: 0xa9aaa5,\n b: 0xa9aaa5,\n });\n\n // решётка моторного отсека\n for (let y = -6; y <= 5; y += 3) {\n g.rect(-15, y, 4, 1).fill(0x777873);\n }\n}\n\n// башня со стволом сверху, цвет команды — на башне и стволе\nexport function drawTankGun(g, color) {\n const edge = { ...outline, color: scaleTint(color, OUTLINE_SHADE) };\n const thinEdge = { width: 0.06 * _size, color: edge.color };\n\n g.poly(turret).fill(color).stroke(edge);\n bevelRing(g, turret, turretInner, () => scaleTint(color, 0.8));\n g.circle(hatch.x, hatch.y, hatch.r)\n .fill(scaleTint(color, 0.65))\n .stroke(thinEdge);\n g.rect(barrel.x, barrel.y, barrel.w, -barrel.y * 2)\n .fill(color)\n .stroke(edge);\n g.rect(brake.x, brake.y, brake.w, brake.h)\n .fill(scaleTint(color, 0.7))\n .stroke(thinEdge);\n}\n\n// геометрия рисунка для атласа модели\nexport const TANK_ART = {\n width: _width,\n height: _height,\n turret,\n barrel,\n brake,\n};\n\n// создает набор текстур для танка\n// (нормальное состояние для команд и уничтоженное)\n// params.colors - Объект с цветами для команд\n// renderer - PIXI рендерер\n//\n// К каждой цветной текстуре запекается парная КАРТА НОРМАЛЕЙ (`bodyNormal`,\n// `gunNormal`, `destroyedNormal`) — той же геометрией и в тот же кадр\n// (`frame`), поэтому она ложится на цветную пиксель в пиксель. Светотень\n// самой цветной текстуры симметрична (фаски темнее со всех сторон): танк\n// вращается, и свет, запечённый с одной стороны, поворачивался бы с ним.\n// Направленный свет — дело карты нормалей\nexport default function tankTexture(params, renderer) {\n const { colors } = params;\n const textures = {};\n\n const drawBody = drawTankBody;\n const drawGun = drawTankGun;\n\n const drawBodyNormal = g => {\n g.rect(hullRect.x, hullRect.y, hullRect.w, hullRect.h).fill(FLAT_NORMAL);\n\n // внешние кромки гусениц и торцы корпуса\n g.rect(hullRect.x, hullRect.y, hullRect.w, 1).fill(normalColor(0, -1, 1));\n g.rect(hullRect.x, hullRect.y + hullRect.h - 1, hullRect.w, 1).fill(\n normalColor(0, 1, 1),\n );\n g.rect(hullRect.x, hullRect.y + 1, 1, hullRect.h - 2).fill(\n normalColor(-1, 0, 1),\n );\n g.rect(hullRect.x + hullRect.w - 1, hullRect.y + 1, 1, hullRect.h - 2).fill(\n normalColor(1, 0, 1),\n );\n\n bevelFrame(g, deck, deckBevel, {\n l: normalColor(-1, 0, 1),\n // лобовой лист пологий: вдвое шире и наклонён вдвое слабее\n r: normalColor(1, 0, 2),\n t: normalColor(0, -1, 1),\n b: normalColor(0, 1, 1),\n });\n };\n\n const drawGunNormal = g => {\n // подложка — тот же силуэт с той же обводкой: кромка цветной текстуры\n // тоже получает нормаль, а не пустоту\n g.poly(turret)\n .fill(FLAT_NORMAL)\n .stroke({ ...outline, color: FLAT_NORMAL });\n bevelRing(g, turret, turretInner, (nx, ny) => normalColor(nx, ny, 1));\n\n // ствол и тормоз — цилиндры: верхняя половина смотрит к `−y`, нижняя\n // к `+y`\n const cylinder = ({ x, y, w, h }, stroke) => {\n g.rect(x, y, w, h)\n .fill(FLAT_NORMAL)\n .stroke({ ...stroke, color: FLAT_NORMAL });\n g.rect(x, y, w, h / 2).fill(normalColor(0, -1, 1));\n g.rect(x, y + h / 2, w, h / 2).fill(normalColor(0, 1, 1));\n };\n\n cylinder({ ...barrel, h: -barrel.y * 2 }, outline);\n cylinder(brake, { width: 0.06 * _size });\n };\n\n // карта нормалей запекается в КАДР цветной текстуры: так их пиксели\n // совпадают, даже если нормальная геометрия чуть меньше цветной\n const bake = (draw, frame) => {\n const g = new Graphics();\n\n draw(g);\n\n const texture = renderer.generateTexture({ target: g, frame });\n\n g.destroy(true);\n\n return texture;\n };\n\n const boundsFrame = draw => {\n const g = new Graphics();\n\n draw(g);\n\n const bounds = g.getBounds();\n\n g.destroy(true);\n\n return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);\n };\n\n // генерация текстур для живого танка\n const createLiveTankTextures = color => {\n const bodyFrame = boundsFrame(drawBody);\n const gunFrame = boundsFrame(g => drawGun(g, color));\n\n // якорь пушки: gunFrame.x — смещение левого края текстуры\n // относительно (0,0) Graphics\n const gunAnchor = {\n x: -gunFrame.x / gunFrame.width,\n y: -gunFrame.y / gunFrame.height,\n };\n\n return {\n body: bake(drawBody, bodyFrame),\n bodyNormal: bake(drawBodyNormal, bodyFrame),\n gun: bake(g => drawGun(g, color), gunFrame),\n gunNormal: bake(drawGunNormal, gunFrame),\n gunAnchor,\n };\n };\n\n // генерация текстуры для уничтоженного состояния\n const createDestroyedTankTexture = () => {\n const destroyedContainer = new Container();\n const destroyedBody = new Graphics();\n const destroyedGun = new Graphics();\n\n destroyedContainer.addChild(destroyedBody, destroyedGun);\n\n const bodyColorDark = 0x3a3a3a;\n const bodyColorDarker = 0x252525;\n const gunColorDark = 0x303030;\n const damageColor = 0x181818;\n const edgeColor = 0x505050;\n\n const w = _width;\n const h = _height;\n const s = _size;\n\n // поврежденный корпус\n destroyedBody\n .moveTo(-w / 2 - s * 0.1, -h / 2 + s * 0.2)\n .lineTo(w / 2 + s * 0.2, -h / 2 - s * 0.1)\n .lineTo(w / 2 - s * 0.1, h / 2 + s * 0.3)\n .lineTo(-w / 2 + s * 0.3, h / 2 - s * 0.2)\n .closePath()\n .fill(bodyColorDark)\n .stroke({ width: s * 0.2, color: edgeColor, alignment: 0.5 });\n\n destroyedBody\n .moveTo(-w / 2 + s * 0.4, -h / 2 + s * 0.5)\n .lineTo(w / 2 - s * 0.3, -h / 2 + s * 0.2)\n .lineTo(w / 2 - s * 0.4, h / 2 - s * 0.1)\n .lineTo(-w / 2 + s * 0.2, h / 2 - s * 0.4)\n .closePath()\n .fill(bodyColorDarker);\n\n destroyedBody.circle(w * 0.15, h * 0.1, s * 1.2).fill(damageColor);\n\n destroyedBody.circle(-w * 0.3, -h * 0.25, s * 0.5).fill(damageColor);\n\n // поврежденная башня\n destroyedGun.position.set(s * 0.3, -s * 0.2);\n\n const gunBasePoints = [\n s * 1.1,\n -s * 0.7,\n s * 0.2,\n -s * 1.0,\n -s * 0.6,\n -s * 0.9,\n -s * 1.3,\n -s * 0.2,\n -s * 1.1,\n s * 0.6,\n -s * 0.2,\n s * 1.0,\n s * 0.7,\n s * 0.8,\n s * 1.3,\n s * 0.1,\n ];\n\n destroyedGun\n .poly(gunBasePoints)\n .fill(gunColorDark)\n .stroke({ width: s * 0.15, color: edgeColor, alignment: 0 });\n\n const barrelPoints = [\n s * 0.8,\n -s * 0.25,\n s * 1.5,\n -s * 0.4,\n s * 1.4,\n s * 0.15,\n s * 0.7,\n s * 0.05,\n ];\n\n destroyedGun\n .poly(barrelPoints)\n .fill(gunColorDark)\n .stroke({ width: s * 0.1, color: edgeColor, alignment: 0 });\n\n destroyedGun.circle(s * 0.1, -s * 0.2, s * 0.4).fill(damageColor);\n\n // генерация единой текстуры для уничтоженного состояния\n const destroyedBounds = destroyedContainer.getBounds();\n const destroyedTexture = renderer.generateTexture({\n target: destroyedContainer,\n frame: new Rectangle(\n destroyedBounds.x,\n destroyedBounds.y,\n destroyedBounds.width,\n destroyedBounds.height,\n ),\n });\n\n destroyedContainer.destroy({ children: true });\n\n // рисунок остова ровный: карта нормалей — плоскость на весь кадр,\n // объём ему даёт только наклон\n const normal = new Graphics()\n .rect(\n destroyedBounds.x,\n destroyedBounds.y,\n destroyedBounds.width,\n destroyedBounds.height,\n )\n .fill(FLAT_NORMAL);\n const destroyedNormal = renderer.generateTexture({\n target: normal,\n frame: new Rectangle(\n destroyedBounds.x,\n destroyedBounds.y,\n destroyedBounds.width,\n destroyedBounds.height,\n ),\n });\n\n normal.destroy(true);\n\n return { texture: destroyedTexture, normal: destroyedNormal };\n };\n\n // создание текстур для каждой команды и состояния\n textures.liveTeamId1 = createLiveTankTextures(colors.teamId1);\n textures.liveTeamId2 = createLiveTankTextures(colors.teamId2);\n const destroyed = createDestroyedTankTexture();\n\n textures.destroyed = destroyed.texture;\n textures.destroyedNormal = destroyed.normal;\n\n return textures;\n}\n","import { Graphics, Container, Rectangle } from 'pixi.js';\nimport { scaleTint } from '../tilt.js';\nimport { drawTankBody, drawTankGun, TANK_ART } from './tankTexture.js';\nimport { tankModel as tankModelConfig } from '../../config/render.js';\n\n// Атлас граней 3D-модели танка (`src/client/tank3d/`, plan/tank-3d/): один\n// на команду. Верх корпуса и башни — тот же рисунок, что у плоской текстуры\n// (`drawTankBody`/`drawTankGun`): модель проецирует на него свои верхи и\n// скаты по (u, v). Отвесные бока — свои полосы: борт гусеницы с катками,\n// ствол, дульный тормоз. Светотень атласа симметрична — направленный свет\n// даёт `faceShade` по нормали грани.\n//\n// Области — в пикселях атласа; у планарных (`body`, `gun`) есть\n// `originX/originY` — куда в атласе попадает точка (0, 0) модели.\n\n// зазор между областями: без него линейная выборка на кромке грани тянет\n// соседнюю область\nconst PAD = 2;\n\n// отвесный борт гусеницы: тёмная лента, катки и ленивец\nfunction drawTrackSide(g, { x, y, w, h }) {\n g.rect(x, y, w, h).fill(0x2e2e2e);\n // верхняя ветвь ленты\n g.rect(x, y, w, 1).fill(0x3c3c3c);\n\n const radius = Math.max(1, h * 0.38);\n\n for (let cx = x + radius + 1; cx < x + w - radius; cx += radius * 2.6) {\n g.circle(cx, y + h * 0.58, radius).fill(0x4a4a4a);\n g.circle(cx, y + h * 0.58, radius * 0.4).fill(0x222222);\n }\n}\n\n// цилиндр сбоку: цвет с затемнёнными кромками (симметрично)\nfunction drawCylinderSide(g, { x, y, w, h }, color) {\n g.rect(x, y, w, h).fill(scaleTint(color, 0.75));\n\n if (h > 2) {\n g.rect(x, y + 1, w, h - 2).fill(color);\n }\n}\n\n/**\n * @param {object} params\n * @param {{ teamId1: number, teamId2: number }} params.colors\n * @param {object} renderer\n * @param {object} [config] `tankModel` из src/config/render.js\n * @returns {{ liveTeamId1: object, liveTeamId2: object, destroyed: object }}\n * у каждого\n * `{ texture, width, height, regions }`\n */\nexport default function tankModelTexture(\n params,\n renderer,\n config = tankModelConfig,\n) {\n const { colors } = params;\n\n // `burnt` — остов: тот же рисунок под тёмной вуалью с пятнами копоти\n const bake = (color, burnt = false) => {\n // габарит башни со стволом — по её же рисунку\n const probe = new Graphics();\n\n drawTankGun(probe, color);\n\n const gunBounds = probe.getBounds();\n\n probe.destroy(true);\n\n const gunW = Math.ceil(gunBounds.width);\n const gunH = Math.ceil(gunBounds.height);\n\n const body = {\n x: PAD,\n y: PAD,\n w: TANK_ART.width,\n h: TANK_ART.height,\n originX: PAD + TANK_ART.width / 2,\n originY: PAD + TANK_ART.height / 2,\n };\n const gun = {\n x: body.x + body.w + PAD,\n y: PAD,\n w: gunW,\n h: gunH,\n originX: body.x + body.w + PAD - gunBounds.x,\n originY: PAD - gunBounds.y,\n };\n\n const rowY = PAD + Math.max(body.h, gun.h) + PAD;\n const trackSide = {\n x: PAD,\n y: rowY,\n w: TANK_ART.width,\n h: config.trackHeight,\n };\n const barrelSide = {\n x: trackSide.x + trackSide.w + PAD,\n y: rowY,\n w: 22,\n h: config.barrelRadius * 2,\n };\n const brakeSide = {\n x: barrelSide.x + barrelSide.w + PAD,\n y: rowY,\n w: 8,\n h: config.brakeRadius * 2,\n };\n const bottom = { x: brakeSide.x + brakeSide.w + PAD, y: rowY, w: 4, h: 4 };\n\n const width = Math.max(gun.x + gun.w, bottom.x + bottom.w) + PAD;\n const height =\n rowY + Math.max(trackSide.h, barrelSide.h, brakeSide.h, bottom.h) + PAD;\n\n const atlas = new Container();\n const bodyArt = new Graphics();\n const gunArt = new Graphics();\n const strips = new Graphics();\n\n drawTankBody(bodyArt);\n bodyArt.position.set(body.originX, body.originY);\n drawTankGun(gunArt, color);\n gunArt.position.set(gun.originX, gun.originY);\n\n drawTrackSide(strips, trackSide);\n drawCylinderSide(strips, barrelSide, color);\n drawCylinderSide(strips, brakeSide, scaleTint(color, 0.7));\n strips.rect(bottom.x, bottom.y, bottom.w, bottom.h).fill(0x1a1a1a);\n\n atlas.addChild(bodyArt, gunArt, strips);\n\n if (burnt) {\n const soot = new Graphics();\n\n for (const region of [body, gun, trackSide, barrelSide, brakeSide]) {\n soot.rect(region.x, region.y, region.w, region.h).fill({\n color: 0x0c0a08,\n alpha: 0.62,\n });\n }\n\n // пятна копоти и прогаров на палубе и башне\n soot.circle(body.originX + 4, body.originY + 2, 6).fill({\n color: 0x000000,\n alpha: 0.5,\n });\n soot.circle(body.originX - 9, body.originY - 4, 3).fill({\n color: 0x000000,\n alpha: 0.5,\n });\n soot.circle(gun.originX - 2, gun.originY + 1, 4).fill({\n color: 0x000000,\n alpha: 0.5,\n });\n\n atlas.addChild(soot);\n }\n\n const texture = renderer.generateTexture({\n target: atlas,\n frame: new Rectangle(0, 0, width, height),\n });\n\n atlas.destroy({ children: true });\n\n return {\n texture,\n width,\n height,\n regions: { body, gun, trackSide, barrelSide, brakeSide, bottom },\n };\n };\n\n return {\n liveTeamId1: bake(colors.teamId1),\n liveTeamId2: bake(colors.teamId2),\n // остов без цвета команды: обгоревший металл\n destroyed: bake(0x3a3a3a, true),\n };\n}\n","import { Graphics, Rectangle } from 'pixi.js';\n\n// создает набор текстур для отображения танка на радаре\n// params.radius - Радиус круга для живого танка\n// params.borderWidth - Толщина обводки круга\n// params.crossSize - Размер креста для уничтоженного танка\n// params.crossThickness - Толщина линий креста\n// params.colors - Объект с цветами для команд\n// renderer - PIXI рендерер\nexport default function tankRadarTexture(params, renderer) {\n const { radius, borderWidth, crossSize, crossThickness, colors } = params;\n const textures = {};\n const fullSize = (radius + borderWidth) * 2;\n const center = fullSize / 2;\n const baseColor = 0xeeeeee;\n const destroyedColor = 0x333333;\n\n // генерация текстуры живого танка\n const createLiveTankTexture = color => {\n const graphics = new Graphics();\n\n graphics.circle(center, center, radius).fill(baseColor);\n graphics.circle(center, center, radius - borderWidth).fill(color);\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, fullSize, fullSize),\n });\n\n graphics.destroy(true);\n\n return texture;\n };\n\n // генерация текстуры уничтоженного танка\n const createDestroyedTankTexture = () => {\n const graphics = new Graphics();\n const halfSize = crossSize / 2;\n\n graphics\n .moveTo(center - halfSize, center - halfSize)\n .lineTo(center + halfSize, center + halfSize)\n .moveTo(center - halfSize, center + halfSize)\n .lineTo(center + halfSize, center - halfSize)\n .stroke({ width: crossThickness, color: destroyedColor });\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, fullSize, fullSize),\n });\n\n graphics.destroy(true);\n\n return texture;\n };\n\n // создание текстур для каждой команды и состояния\n textures.liveTeamId1 = createLiveTankTexture(colors.teamId1);\n textures.liveTeamId2 = createLiveTankTexture(colors.teamId2);\n textures.destroyed = createDestroyedTankTexture();\n\n return textures;\n}\n","import { Graphics, Rectangle } from 'pixi.js';\n\n// создает текстуру для бомбы\n// params.colorOuter - Цвет внешней рамки\n// params.colorInner - Цвет внутренней заливки\n// renderer - PIXI рендерер\nexport default function bombTexture(params, renderer) {\n const { colorOuter, colorInner } = params;\n const graphics = new Graphics();\n\n // базовый размер\n const size = 20;\n const borderWidth = 1;\n\n // внешний контур\n graphics.rect(0, 0, size, size).fill(colorOuter);\n\n // внутренняя часть\n graphics\n .rect(\n borderWidth,\n borderWidth,\n size - borderWidth * 2,\n size - borderWidth * 2,\n )\n .fill(colorInner);\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, size, size),\n });\n\n graphics.destroy(true);\n\n return texture;\n}\n","import { Graphics, Rectangle } from 'pixi.js';\n\n// создает текстуру для сегмента следа от гусеницы\n// params.width - Ширина сегмента\n// params.length - Длина сегмента\n// params.color - Цвет заливки\n// renderer - PIXI рендерер\nexport default function trackMarkTexture(params, renderer) {\n const { width, length, color } = params;\n const graphics = new Graphics();\n\n // рисуем один сегмент\n graphics.rect(0, 0, length, width).fill(color);\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, length, width),\n });\n\n graphics.destroy(true);\n\n return texture;\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// Тень танка — СИЛУЭТ корпуса, а не круг: круглая тень с мягким ореолом\n// вылезала из-под углов вращающегося корпуса и читалась как серый кружок\n// рядом с машиной.\n//\n// params.width - длина фигуры (вдоль курса), в пропорции корпуса\n// params.height - ширина фигуры (поперёк курса)\n// params.radius - скругление углов\n// params.blur - сила размытия (мягкий край, а не второй силуэт)\n// params.color - цвет заливки\n// params.quality - количество проходов размытия\n// renderer - PIXI рендерер\n// возвращает { texture, contentSize }, где contentSize - ЧЁТКАЯ длина\n// фигуры: по ней потребитель нормирует масштаб, чтобы запас под размытие\n// не влиял на видимый размер (тот же контракт, что у blurredCircleTexture)\nexport default function tankShadowTexture(params, renderer) {\n const { width, height, radius, blur, color, quality = 20 } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const textureWidth = width + margin * 2;\n const textureHeight = height + margin * 2;\n\n graphics.roundRect(margin, margin, width, height, radius);\n graphics.fill(color);\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // без явного padding Pixi рендерит размытие лишь на 2 * strength вокруг\n // фигуры и обрезает его раньше рамки, каким бы большим ни был холст;\n // область шире рамки — чтобы мусор пула с края не попал в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureWidth, textureHeight),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: width };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport { randomRange } from 'vimp-engine/lib/math.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// создаёт набор процедурных текстур копоти: размытое тёмное пятно с ещё\n// более тёмной серединой — след разрушенного пропа (бочки), у которого нет\n// своей картинки `game.imgDestroyed`. Вариантов несколько, чтобы соседние\n// пятна не повторяли один силуэт\n// params.baseRadius - Базовый радиус пятна.\n// params.irregularity - Степень неровности краёв.\n// params.blur - Сила размытия.\n// params.numPoints - Количество точек контура.\n// params.color - Цвет пятна.\n// params.coreColor - Цвет середины.\n// params.coreRatio - Доля радиуса, которую занимает середина.\n// params.variants - Количество вариантов силуэта.\n// renderer - Рендерер PIXI.\n// возвращает { textures, contentSize }, где contentSize - диаметр силуэта\n// без запаса под размытие: по нему потребитель нормирует масштаб\nexport default function scorchTexture(params, renderer) {\n const {\n baseRadius,\n irregularity = 0,\n blur = 0,\n numPoints = 16,\n color,\n coreColor = color,\n coreRatio = 0.5,\n variants = 1,\n } = params;\n\n // хотя бы один силуэт: пустой набор уронил бы потребителя\n const variantCount = Math.max(1, Math.floor(variants) || 1);\n const contentSize = (baseRadius + irregularity) * 2;\n const canvasSize = contentSize + blurMargin(blur) * 2;\n const center = canvasSize / 2;\n const textures = [];\n\n // неровный замкнутый контур радиуса `radius`\n const blob = (radius, jitter) => {\n const path = [];\n\n for (let i = 0; i < numPoints; i += 1) {\n const angle = (i / numPoints) * Math.PI * 2;\n const r = radius + randomRange(-jitter, jitter);\n\n path.push(center + r * Math.cos(angle), center + r * Math.sin(angle));\n }\n\n return path;\n };\n\n for (let variant = 0; variant < variantCount; variant += 1) {\n const graphics = new Graphics();\n\n graphics\n .poly(blob(baseRadius, irregularity))\n .fill(color)\n .poly(blob(baseRadius * coreRatio, irregularity * coreRatio))\n .fill(coreColor);\n\n const filter = new BlurFilter({ strength: blur, quality: 10 });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n textures.push(\n renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, canvasSize, canvasSize),\n }),\n );\n\n graphics.destroy(true);\n }\n\n return { textures, contentSize };\n}\n","import { Graphics, Rectangle } from 'pixi.js';\nimport { randomRange } from 'vimp-engine/lib/math.js';\n\n// создаёт набор текстур щепок для разлёта при разрушении пропа: узкие\n// неровные четырёхугольники вдоль холста. Рисуются белым (params.color),\n// цвет обломков задаёт tint спрайта\n// params.length - Длина щепки и сторона квадратного холста.\n// params.width - Наибольшая толщина щепки.\n// params.color - Цвет заливки.\n// params.variants - Количество вариантов силуэта.\n// renderer - Рендерер PIXI.\n// возвращает { textures, contentSize }, где contentSize - длина щепки:\n// по ней потребитель нормирует масштаб\nexport default function debrisTexture(params, renderer) {\n const { length, width, color, variants = 1 } = params;\n const variantCount = Math.max(1, Math.floor(variants) || 1);\n const middle = length / 2;\n const textures = [];\n\n for (let variant = 0; variant < variantCount; variant += 1) {\n const graphics = new Graphics();\n // толщина у концов разная: щепка, а не ровный брусок\n const head = randomRange(width * 0.3, width) / 2;\n const tail = randomRange(width * 0.1, width * 0.6) / 2;\n\n graphics\n .poly([\n 0,\n middle - tail,\n length,\n middle - head,\n length,\n middle + head,\n 0,\n middle + tail,\n ])\n .fill(color);\n\n textures.push(\n renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, length, length),\n }),\n );\n\n graphics.destroy(true);\n }\n\n return { textures, contentSize: length };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// Кольца радиального спада: `rings` непересекающихся колец от центра к\n// краю, прозрачность каждого — по функции `alphaAt(t)`, где `t` — доля\n// радиуса середины кольца. Кольца не перекрываются, поэтому прозрачность\n// точная, а лёгкое размытие поверх убирает ступени\nexport function drawRadialRings(graphics, center, radius, rings, alphaAt) {\n for (let i = 0; i < rings; i += 1) {\n const inner = (radius * i) / rings;\n const outer = (radius * (i + 1)) / rings;\n const alpha = alphaAt((i + 0.5) / rings);\n\n if (alpha <= 0) {\n continue;\n }\n\n graphics.circle(center, center, outer);\n graphics.fill({ color: 0xffffff, alpha });\n\n if (inner > 0) {\n graphics.circle(center, center, inner);\n graphics.cut();\n }\n }\n}\n\n// Радиальное пятно света: белый центр, плавный спад к прозрачному краю без\n// жёсткой кромки. Цвет и силу даёт tint/alpha источника.\n// params.radius - радиус пятна, params.rings - колец спада,\n// params.blur - размытие против ступеней\n// возвращает { texture, contentSize } — contentSize: диаметр пятна\nexport default function lightRadialTexture(params, renderer) {\n const { radius, rings = 32, blur = 2, quality = 10 } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const textureSize = (radius + margin) * 2;\n const center = textureSize / 2;\n\n // спад (1 - t)² — свет сходит на нет к краю, центр не пересвечен плато\n drawRadialRings(graphics, center, radius, rings, t => (1 - t) * (1 - t));\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureSize, textureSize),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: radius * 2 };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// Конус фары: клин от вершины к широкому концу, яркость спадает по длине,\n// края мягкие (размытие). Вершина — в точке (margin, середина высоты), то\n// есть на нормированной оси это (0, 0.5) содержимого; потребитель ставит\n// якорь в неё.\n// params.length - длина клина, params.halfWidth - полуширина на конце,\n// params.strips - полос спада по длине, params.blur - мягкость краёв\n// возвращает { texture, length, halfWidth, margin }\nexport default function headlightConeTexture(params, renderer) {\n const { length, halfWidth, strips = 24, blur = 4, quality = 10 } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const width = length + margin * 2;\n const height = (halfWidth + margin) * 2;\n const apexX = margin;\n const apexY = height / 2;\n\n // полосы-трапеции не перекрываются: прозрачность каждой точная\n for (let i = 0; i < strips; i += 1) {\n const x0 = (length * i) / strips;\n const x1 = (length * (i + 1)) / strips;\n const h0 = (halfWidth * i) / strips;\n const h1 = (halfWidth * (i + 1)) / strips;\n const t = (i + 0.5) / strips;\n // яркий у фары, к концу луча сходит на нет\n const alpha = (1 - t) * (1 - t * 0.35);\n\n graphics.poly([\n apexX + x0,\n apexY - h0,\n apexX + x1,\n apexY - h1,\n apexX + x1,\n apexY + h1,\n apexX + x0,\n apexY + h0,\n ]);\n graphics.fill({ color: 0xffffff, alpha });\n }\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, width, height),\n });\n\n graphics.destroy(true);\n\n return { texture, length, halfWidth, margin };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\nimport { drawRadialRings } from './lightRadialTexture.js';\n\n// Голова фонаря: маленький яркий диск с ореолом. Рисуется аддитивно под\n// танком (светильник в асфальте), цвет даёт tint.\n// params.core - радиус яркого диска, params.halo - радиус ореола,\n// params.rings - колец ореола, params.blur - мягкость\n// возвращает { texture, contentSize } — contentSize: диаметр ореола\nexport default function lampHeadTexture(params, renderer) {\n const { core, halo, rings = 12, blur = 1, quality = 10 } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const textureSize = (halo + margin) * 2;\n const center = textureSize / 2;\n const coreShare = core / halo;\n\n // внутри диска — полная яркость, снаружи ореол спадает до нуля\n drawRadialRings(graphics, center, halo, rings, t =>\n t <= coreShare ? 1 : 0.6 * (1 - (t - coreShare) / (1 - coreShare)) ** 2,\n );\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureSize, textureSize),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: halo * 2 };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// Засвет: градиент «край → центр» в квадрате `2 · radius`. Ярче всего у\n// края `+x`, к центру сходит на нет, левая половина пустая. Потребитель\n// поворачивает `+x` к источнику и режет блик силуэтом предмета (маска),\n// цвет и силу даёт tint/alpha.\n// params.radius - полуразмер квадрата, params.strips - полос градиента,\n// params.blur - против ступеней\n// возвращает { texture, contentSize } — contentSize: сторона квадрата\nexport default function glintTexture(params, renderer) {\n const { radius, strips = 16, blur = 2, quality = 10 } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const textureSize = (radius + margin) * 2;\n const center = textureSize / 2;\n\n // полосы-прямоугольники не перекрываются: прозрачность каждой точная\n for (let i = 0; i < strips; i += 1) {\n const x0 = (radius * i) / strips;\n const x1 = (radius * (i + 1)) / strips;\n const t = (i + 0.5) / strips;\n\n graphics.rect(center + x0, center - radius, x1 - x0, radius * 2);\n graphics.fill({ color: 0xffffff, alpha: t * t });\n }\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureSize, textureSize),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: radius * 2 };\n}\n","import { Graphics, BlurFilter, Rectangle } from 'pixi.js';\nimport blurMargin, { blurPadding } from './blurMargin.js';\n\n// детерминированный ГПСЧ (mulberry32): рисунок лучей один на всех клиентах\nexport function seededRandom(seed) {\n let state = seed >>> 0;\n\n return () => {\n state = (state + 0x6d2b79f5) >>> 0;\n\n let t = state;\n\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n// Лучи фонаря в воздухе: `rays` узких клиньев из центра разной длины и\n// яркости, каждый гаснет к концу, края мягкие (размытие). Направления\n// равномерны с разбросом. Цвет и силу даёт tint/alpha.\n// params.radius - наибольшая длина луча, params.rays - число лучей,\n// params.width - полуширина луча на конце (рад), params.strips - полос\n// спада по длине, params.seed - рисунок, params.blur - мягкость\n// возвращает { texture, contentSize } — contentSize: диаметр лучей\nexport default function lightShaftTexture(params, renderer) {\n const {\n radius,\n rays = 14,\n width = 0.05,\n strips = 8,\n seed = 1,\n blur = 3,\n quality = 10,\n } = params;\n const graphics = new Graphics();\n const margin = blurMargin(blur);\n const textureSize = (radius + margin) * 2;\n const center = textureSize / 2;\n const random = seededRandom(seed);\n\n for (let i = 0; i < rays; i += 1) {\n const angle = ((i + (random() - 0.5) * 0.8) / rays) * Math.PI * 2;\n const length = radius * (0.55 + 0.45 * random());\n const brightness = 0.35 + 0.65 * random();\n const half = width * (0.5 + random());\n const cos0 = Math.cos(angle - half);\n const sin0 = Math.sin(angle - half);\n const cos1 = Math.cos(angle + half);\n const sin1 = Math.sin(angle + half);\n\n // полосы-трапеции вдоль луча: яркий у фонаря, к концу сходит на нет\n for (let s = 0; s < strips; s += 1) {\n const r0 = (length * s) / strips;\n const r1 = (length * (s + 1)) / strips;\n const t = (s + 0.5) / strips;\n\n graphics.poly([\n center + cos0 * r0,\n center + sin0 * r0,\n center + cos0 * r1,\n center + sin0 * r1,\n center + cos1 * r1,\n center + sin1 * r1,\n center + cos1 * r0,\n center + sin1 * r0,\n ]);\n graphics.fill({ color: 0xffffff, alpha: brightness * (1 - t) });\n }\n }\n\n const filter = new BlurFilter({ strength: blur, quality });\n\n // область фильтра шире рамки: мусор пула с края не попадёт в текстуру\n filter.padding = blurPadding(blur);\n graphics.filters = [filter];\n\n const texture = renderer.generateTexture({\n target: graphics,\n frame: new Rectangle(0, 0, textureSize, textureSize),\n });\n\n graphics.destroy(true);\n\n return { texture, contentSize: radius * 2 };\n}\n","import blurredCircleTexture from './blurredCircleTexture.js';\nimport funnelTexture from './funnelTexture.js';\nimport tankTexture from './tankTexture.js';\nimport tankModelTexture from './tankModelTexture.js';\nimport tankRadarTexture from './tankRadarTexture.js';\nimport bombTexture from './bombTexture.js';\nimport trackMarkTexture from './trackMarkTexture.js';\nimport tankShadowTexture from './tankShadowTexture.js';\nimport scorchTexture from './scorchTexture.js';\nimport debrisTexture from './debrisTexture.js';\nimport lightRadialTexture from './lightRadialTexture.js';\nimport headlightConeTexture from './headlightConeTexture.js';\nimport lampHeadTexture from './lampHeadTexture.js';\nimport glintTexture from './glintTexture.js';\nimport lightShaftTexture from './lightShaftTexture.js';\n\nexport default {\n // четыре ассета - один и тот же размытый круг, различаются параметрами\n explosionTexture: blurredCircleTexture,\n smokeTexture: blurredCircleTexture,\n impactParticleTexture: blurredCircleTexture,\n dustTexture: blurredCircleTexture,\n tankShadowTexture,\n funnelTexture,\n tankTexture,\n // атлас граней 3D-модели танка (plan/tank-3d/)\n tankModelTexture,\n tankRadarTexture,\n bombTexture,\n trackMarkTexture,\n scorchTexture,\n debrisTexture,\n lightRadialTexture,\n headlightConeTexture,\n lampHeadTexture,\n glintTexture,\n lightShaftTexture,\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 { seeThrough, parallax } from '../config/render.js';\nimport { cameraCenter } from './camera.js';\nimport { offsetPoint } from './parallax.js';\nimport { seeThroughAlpha, seeThroughTint } from './seeThrough.js';\n\n// Сервис пула зависимостей `levelView`: где и на каком уровне находится\n// локальный игрок. Движок такого сервиса не даёт и не должен — это игровое\n// понятие; плагин отдаёт его своим же партам через\n// ClientPlugin.hooks.services (src/client/index.js).\n//\n// Пишет локальный `Tank` (он единственный знает и свой уровень, и свою\n// позицию, и свою высоту, и что он локальный — по сервису `localPlayer`),\n// читают все парты, которые обязаны уступить видимость игроку под ними:\n// слои `Map`, ящики, чужие танки, дым, бомбы и эффекты.\n//\n// Формула прозрачности здесь одна на всех (`alphaFor`), режим тоже один\n// (`mode`): иначе каждый парт читал бы конфиг по-своему и «дыра» вокруг\n// игрока разъехалась бы между слоем и ящиком на нём.\n//\n// Расстояние до игрока считается в НАРИСОВАННЫХ координатах: и игрок, и\n// сущность смещены проекцией 2.5D каждый на свою высоту\n// (`src/client/parallax.js`), а дыру в плите `Map` центрирует по той же\n// смещённой точке. Считать alpha по сырым мировым точкам значило бы\n// разводить круг прозрачности сущностей с нарисованной дырой тем сильнее,\n// чем дальше игрок от центра экрана и чем выше сущность.\n// Центр камеры — свойство КАДРА, а не парта: его добывает сам сервис.\n// Раньше его публиковал ОДИН парт — локальный танк, — и без него\n// (наблюдатель, промежуток между смертью и респауном, кадры до появления\n// `localPlayer.id`) камеры не было вовсе, а слой карты считал её сам: дыра\n// в плите ехала по свежей проекции, а alpha сущностей — по прошлой.\n// Порядок `onRender` партов тоже не определён, и тот, кого вызвали раньше\n// танка, брал камеру прошлого кадра.\nexport function createLevelView(cfg = seeThrough, deps = {}) {\n const state = {\n level: 0,\n x: 0,\n y: 0,\n z: 0,\n camera: null,\n // трансформ сцены, по которому посчитан центр; null — ещё ни разу\n key: null,\n stage: deps.stage || null,\n renderer: deps.renderer || null,\n // предупреждение об отсутствующем центре камеры выдано (один раз на\n // сессию): кадров без камеры бывает много подряд, и поток в консоли\n // на проде бесполезен\n warned: false,\n // журнал клиентских ошибок движка (сервис пула `diagnostics`): сервис\n // игры создаётся до полотна, и его отдают парты (`setDiagnostics`)\n diagnostics: null,\n };\n\n // Кеш живёт на ОДНОМ состоянии сцены, а не на тике общего тикера. Ключ\n // по тику был неверен: за один тик полотно может рисоваться НЕСКОЛЬКО\n // раз — `vimp-engine` до 0.34 зовёт `app.render()` прямо из\n // `updateCoords` (CanvasManagerView), то есть на каждый кадр камеры, а в\n // тике их два и больше (сперва камера дискретного кадра — интерполяция,\n // следом предсказанная своего танка) плюс отрисовка самого тикера.\n // Сколько отрисовок придётся на тик — дело движка и его версии, и\n // опираться на это число плагин не вправе. Всем\n // отрисовкам после первой кеш отдавал камеру ПЕРВОЙ, тогда как сцена\n // стояла уже на последней: проекция 2.5D считалась от чужого центра, и\n // на верхних уровнях всё дрожало на разнице «интерполяция ↔\n // предсказание» — тем сильнее, чем быстрее едет игрок. Парты, читающие\n // сцену напрямую (`Tracks`, `Smoke`, эффекты), в тех же кадрах смещались\n // по свежему центру и разъезжались с танком и плитой.\n //\n // Трансформ сцены во время отрисовки не меняется, поэтому ключ по нему\n // даёт и единый центр на всю отрисовку, и свежесть между отрисовками.\n // Центра камеры не бывает только в кадре без трансформа сцены, и путь, по\n // которому такой кадр возникает, — дело движка (масштаб сцены ставит он).\n // Потребители проекции `null` терпят (`offsetPoint`, `modelLean`), поэтому\n // тихо он бы и остался. Один раз сказать — в консоль и в журнал\n // клиентских ошибок движка (`diagnostics`, если движок его даёт), —\n // единственный способ прижать причину на проде, где идут опубликованные\n // сборки\n const warnNoCamera = () => {\n if (state.warned) {\n return;\n }\n\n state.warned = true;\n const stage = state.stage;\n const screen = state.renderer?.screen;\n\n const payload = {\n // уничтоженная сцена — отдельная причина, и по трансформу её не\n // отличить от просто обнулённого масштаба: у мёртвого контейнера\n // трансформ обнулён весь\n destroyed: stage ? stage.destroyed : null,\n position: stage ? { x: stage.position?.x, y: stage.position?.y } : null,\n scale: stage ? { x: stage.scale?.x, y: stage.scale?.y } : null,\n screen: screen ? { width: screen.width, height: screen.height } : null,\n };\n\n console.warn(\n '[tanks] levelView: центра камеры нет — кадр без трансформа сцены',\n payload,\n );\n state.diagnostics?.warn('tanks.camera.missing', payload);\n };\n\n const camera = () => {\n // сцены ещё нет — штатное начало кадра (парт спрашивает сервис до того,\n // как первый парт полотна отдал ему сцену), и говорить тут не о чем\n if (!state.stage) {\n return null;\n }\n\n // мёртвая сцена: у уничтоженного контейнера PixiJS `position` и `scale`\n // обнулены, и читать трансформ ниже нельзя. Аномалия — говорим о ней,\n // а ждём новую сцену от `attachStage`\n if (state.stage.destroyed) {\n warnNoCamera();\n\n return null;\n }\n\n const { position, scale } = state.stage;\n const screen = state.renderer?.screen;\n const width = screen ? screen.width : 0;\n const height = screen ? screen.height : 0;\n const key = state.key;\n\n // `camera === null` — центра не было вовсе (сцена без масштаба до\n // первого кадра): такой ответ не кешируется, иначе он остался бы\n // навсегда\n if (\n state.camera === null ||\n key === null ||\n key.x !== position.x ||\n key.y !== position.y ||\n key.scaleX !== scale.x ||\n key.scaleY !== scale.y ||\n key.width !== width ||\n key.height !== height\n ) {\n state.key = {\n x: position.x,\n y: position.y,\n scaleX: scale.x,\n scaleY: scale.y,\n width,\n height,\n };\n state.camera = cameraCenter(state.stage, state.renderer);\n\n // сцена есть, а центра нет — это и есть аномальный кадр\n if (state.camera === null) {\n warnNoCamera();\n }\n }\n\n return state.camera;\n };\n\n return {\n // Ленивая привязка к сцене: сервис собирается ещё до полотна\n // (`hooks.services(core)` зовётся один раз на ядро), а парт попадает на\n // сцену позже своего конструктора. Привязывается ПЕРВЫЙ, кто позвал, и\n // звать вправе только парты игрового полотна (`Tank`, `Map`): у радара\n // своя сцена и своя проекция.\n //\n // Исключение из «первый и навсегда» — УНИЧТОЖЕННАЯ сцена: сервис живёт\n // столько же, сколько ядро, а полотно движок вправе пересобрать. Держать\n // мёртвую сцену значило бы отдавать `null` до конца матча, то есть\n // выключить проекцию 2.5D целиком\n attachStage(stage, renderer) {\n if (!stage || !renderer) {\n return;\n }\n\n if (state.stage && !state.stage.destroyed) {\n return;\n }\n\n state.stage = stage;\n state.renderer = renderer;\n state.key = null;\n state.camera = null;\n },\n\n // журнал клиентских ошибок движка (сервис пула `diagnostics`, vimp-engine\n // ≥ 0.35.0): его отдают парты, у которых он есть в dependencies. Первый\n // непустой — навсегда; на старом движке сервиса нет, и остаётся console.warn\n setDiagnostics(diagnostics) {\n if (!state.diagnostics && diagnostics) {\n state.diagnostics = diagnostics;\n }\n },\n\n // центр камеры этого кадра в мировых единицах; null — сцены ещё нет\n camera() {\n return camera();\n },\n\n set(level, x, y, z = 0) {\n state.level = level;\n state.x = x;\n state.y = y;\n state.z = z;\n },\n\n // `z` — высота сущности в уровнях; по умолчанию она равна уровню (тело\n // стоит на своей плите), а танк и дым передают свой дробный `z`\n alphaFor(level, worldX, worldY, z = level) {\n const center = camera();\n const view = offsetPoint(\n state.x,\n state.y,\n center,\n state.z * parallax.shear,\n );\n const point = offsetPoint(worldX, worldY, center, z * parallax.shear);\n\n return seeThroughAlpha({\n viewLevel: state.level,\n viewX: view.x,\n viewY: view.y,\n level,\n x: point.x,\n y: point.y,\n cfg,\n });\n },\n\n tintFor(level) {\n return seeThroughTint(state.level, level, cfg);\n },\n\n get cfg() {\n return cfg;\n },\n get mode() {\n return cfg.mode;\n },\n get level() {\n return state.level;\n },\n get x() {\n return state.x;\n },\n get y() {\n return state.y;\n },\n get z() {\n return state.z;\n },\n };\n}\n","import {\n AlphaFilter,\n Container,\n Graphics,\n Mesh,\n MeshGeometry,\n Rectangle,\n Sprite,\n Texture,\n} from 'pixi.js';\nimport { levelZ } from '../levelZ.js';\nimport { applyParallax } from '../parallax.js';\nimport { createHole, dispose as disposeHole } from '../parts/map/holeOverlay.js';\nimport {\n LIGHT_OVERLAY_BASE_Z,\n fanIndices,\n rampWedgePolygon,\n} from './lightMath.js';\n\nconst WHITE = 0xffffff;\n\n// Карта освещённости ОДНОГО уровня.\n//\n// Оверлей — контейнер на сцене (`zIndex = levelZ(40, L)`) в МИРОВЫХ\n// координатах, без собственного трансформа: фон, маска этажа и аддитивные\n// источники. Фильтр на нём рисует содержимое в пуловую\n// текстуру пониженного разрешения и кладёт результат на сцену умножением\n// (`blendMode: 'multiply'`). Отдельной RenderTexture с `renderer.render`\n// здесь нет намеренно: `onRender` частей зовётся ВНУТРИ кадра, когда экран\n// уже привязан (`renderStart`), и вложенный рендер в текстуру сбросил бы\n// стек целей — весь кадр ушёл бы в карту освещённости. Фильтр Pixi рисует\n// через push/pop стека целей, в том же проходе.\n//\n// Область фильтра — `filterArea`: мировой прямоугольник карты с запасом\n// (`area`), задаётся один раз. НЕ `boundsArea`: для него Pixi 8.19\n// (`getFastGlobalBounds`) применяет трансформ сцены дважды, область уезжала\n// за экран вместе с камерой, фильтр пропускался (`skip`), и содержимое\n// ложилось на сцену без умножения — белый экран или пропавшая ночь. Путь\n// `filterArea` считается верно, обрезку по экрану делает сам Pixi.\n//\n// Содержимое:\n// L = 0 фон цвета `ambient`, источники уровня 0 — аддитивно;\n// L >= 1 фон БЕЛЫЙ (умножение на белый картинку не меняет), поверх —\n// маска этажа цвета `ambient` в проекции уровня, затем источники.\n// Вне этажа карта остаётся белой, и уровень 0 не темнеет дважды.\n//\n// Свет верхнего уровня на рампах (`setRamps`, `layoutRamps`): у карты\n// уровня `from` рамп, ведущих вверх, — второй контейнер источников\n// `rampLights` с маской «клинья этих рамп» в проекции клина. В него\n// кладутся источники уровня `to`: клин нарисован в части уровня `from` и\n// затемнён её оверлеем, а свет плиты `to` без маски осветил бы и землю под\n// мостом.\n//\n// Конус фары, упёршийся в стену (`fan` у источника), рисуется не спрайтом, а\n// веером-мешем по полигону видимости с той же текстурой: свет кончается на\n// стене без стенсил-масок на каждый конус.\n//\n// Над источниками — лучи фонарей в воздухе (`layoutShafts`) с просветами-\n// тенями предметов.\n//\n// Поверх источников — вершины объёмов уровня (`setTops`): прямоугольники\n// клеток цвета `ambient` в проекции `(L + volume) · shear`. Боковые грани\n// остаются освещёнными, верх стены фары наземного танка не ловит.\n//\n// `roof: true` — карта крыш уровня (`game.roofs`): та же маска и те же\n// источники, но своя дыра — она открывается, только когда крыша закрывает\n// танк (ведёт сервис).\nexport default class LevelLightMap {\n // `area` — `{ x, y, width, height }` в мировых единицах: карта с запасом\n // на отдалённую камеру\n constructor({ level, ambient, resolution, area, roof = false }) {\n this.level = level;\n this.ambient = ambient;\n this.roof = roof;\n\n this.overlay = new Container();\n this.overlay.label = roof ? `lighting-roof-${level}` : `lighting-${level}`;\n this.overlay.zIndex = levelZ(LIGHT_OVERLAY_BASE_Z, level);\n this.overlay.eventMode = 'none';\n\n // фон на всю область: Texture.WHITE с tint\n this.base = new Sprite(Texture.WHITE);\n this.base.tint = level === 0 ? ambient : WHITE;\n this.base.position.set(area.x, area.y);\n this.base.width = area.width;\n this.base.height = area.height;\n this.overlay.addChild(this.base);\n\n this.mask = level >= 1 ? new Graphics() : null;\n\n if (this.mask) {\n this.overlay.addChild(this.mask);\n }\n\n this.lights = new Container();\n this.overlay.addChild(this.lights);\n\n // свет верхнего уровня, обрезанный клиньями рамп (стенсил-маска):\n // маска заводится вместе с первыми рампами\n this.rampLights = new Container();\n this.overlay.addChild(this.rampLights);\n this.rampMask = null;\n this.ramps = [];\n this.rampPool = [];\n this.rampFanPool = [];\n\n // лучи фонарей в воздухе: по контейнеру на фонарь — спрайт лучей и\n // клинья теней предметов. Тени — ИНВЕРСНАЯ стенсил-маска контейнера:\n // гасят только лучи своего фонаря, пятно на земле и полумрак не трогают\n this.shafts = new Container();\n this.overlay.addChild(this.shafts);\n this.shaftPool = [];\n\n // вершины объёмов: по графике на высоту объёма, `{ graphics, volume }`\n this.tops = new Container();\n this.overlay.addChild(this.tops);\n this.topGroups = [];\n\n // спрайты источников переиспользуются между кадрами: число видимых\n // источников меняется, объекты — нет\n this.pool = [];\n // веера конусов, упёршихся в стену (`item.fan`)\n this.fanPool = [];\n\n // проходной фильтр: разрешение карты и режим наложения на сцену\n this.filter = new AlphaFilter({ alpha: 1 });\n this.filter.resolution = resolution;\n this.filter.blendMode = 'multiply';\n this.overlay.filters = [this.filter];\n // область фильтра переживает смену цепочки (дыра заменяет `filters`):\n // она живёт в том же FilterEffect контейнера\n this.overlay.filterArea = new Rectangle(\n area.x,\n area.y,\n area.width,\n area.height,\n );\n\n this.resolution = resolution;\n\n // «дыра» над игроком: у оверлея своя, как у плиты\n this.hole = createHole();\n\n this.hasMask = false;\n }\n\n // маска этажа: прогоны клеток в МИРОВЫХ единицах, один раз на изменение.\n // Проекцию уровня даёт трансформ графики (`applyParallax`), вершины на\n // кадр не пересчитываются\n setMask(runs, step, scale) {\n if (!this.mask) {\n return;\n }\n\n this.mask.clear();\n this.hasMask = runs.length > 0;\n\n for (const run of runs) {\n this.mask.rect(\n run.col * step * scale.x,\n run.row * step * scale.y,\n run.length * step * scale.x,\n step * scale.y,\n );\n }\n\n if (this.hasMask) {\n this.mask.fill(this.ambient);\n }\n }\n\n // вершины объёмов: `groups` — `[{ volume, runs }]`, прогоны клеток в\n // мировых единицах, как у маски. Проекцию высоты даёт трансформ графики\n setTops(groups, step, scale) {\n for (const group of this.topGroups) {\n group.graphics.destroy();\n }\n\n this.topGroups = [];\n\n for (const { volume, runs } of groups) {\n if (!runs.length) {\n continue;\n }\n\n const graphics = new Graphics();\n\n for (const run of runs) {\n graphics.rect(\n run.col * step * scale.x,\n run.row * step * scale.y,\n run.length * step * scale.x,\n step * scale.y,\n );\n }\n\n graphics.fill(this.ambient);\n this.tops.addChild(graphics);\n this.topGroups.push({ graphics, volume });\n }\n }\n\n // клинья рамп, ведущих с этого уровня вверх: полосы в клетках\n // (`buildRampLanes`) и то, что нужно их проекции. Контур пересчитывает\n // `place` — у клина своя высота на каждую вершину\n setRamps(lanes, step, scale, segments) {\n this.ramps = lanes;\n this.rampGeometry = { step, scale, segments };\n\n if (lanes.length && !this.rampMask) {\n this.rampMask = new Graphics();\n this.overlay.addChild(this.rampMask);\n this.rampLights.mask = this.rampMask;\n } else if (!lanes.length && this.rampMask) {\n this.rampLights.mask = null;\n this.rampMask.destroy();\n this.rampMask = null;\n }\n }\n\n // уровни вершин рамп: источники этих уровней идут в `rampLights`\n rampLevels() {\n return new Set(this.ramps.map(lane => lane.to));\n }\n\n // проекция уровня для маски и вершин объёмов; фон и область фильтра —\n // мировые и от камеры не зависят\n place(camera, shear) {\n if (this.mask) {\n applyParallax(this.mask, camera, this.level * shear, 1);\n }\n\n if (this.rampMask) {\n const { step, scale, segments } = this.rampGeometry;\n\n this.rampMask.clear();\n\n for (const lane of this.ramps) {\n this.rampMask.poly(\n rampWedgePolygon(lane, step, scale, camera, shear, segments),\n );\n }\n\n this.rampMask.fill(WHITE);\n }\n\n for (const { graphics, volume } of this.topGroups) {\n applyParallax(graphics, camera, (this.level + volume) * shear, 1);\n }\n }\n\n // раскладка источников кадра: `items` уже спроецированы и отсечены\n // сервисом — `{ texture, x, y, anchorX, anchorY, scaleX, scaleY,\n // rotation, color, alpha }`\n layout(items) {\n layoutPool(this.pool, this.lights, items.filter(item => !item.fan));\n layoutFans(this.fanPool, this.lights, items.filter(item => item.fan));\n }\n\n // источники верхнего уровня на клиньях рамп — тот же формат, что `layout`\n layoutRamps(items) {\n layoutPool(this.rampPool, this.rampLights, items.filter(item => !item.fan));\n layoutFans(\n this.rampFanPool,\n this.rampLights,\n items.filter(item => item.fan),\n );\n }\n\n // раскладка лучей кадра: `items` — `{ texture, x, y, scale, rotation,\n // color, alpha, shadows }`, где `shadows` — клинья теней\n // `[[x0, y0, …], …]` в тех же нарисованных координатах\n layoutShafts(items) {\n while (this.shaftPool.length < items.length) {\n const container = new Container();\n const sprite = new Sprite();\n const shadow = new Graphics();\n\n sprite.anchor.set(0.5);\n sprite.blendMode = 'add';\n container.addChild(sprite, shadow);\n this.shafts.addChild(container);\n this.shaftPool.push({ container, sprite, shadow, masked: false });\n }\n\n for (let i = 0; i < this.shaftPool.length; i += 1) {\n const entry = this.shaftPool[i];\n const item = items[i];\n\n if (!item) {\n entry.container.visible = false;\n continue;\n }\n\n const { container, sprite, shadow } = entry;\n const shadows = item.shadows || [];\n\n container.visible = true;\n sprite.texture = item.texture;\n sprite.position.set(item.x, item.y);\n sprite.scale.set(item.scale);\n sprite.rotation = item.rotation;\n sprite.tint = item.color;\n sprite.alpha = item.alpha;\n\n shadow.clear();\n\n for (const polygon of shadows) {\n shadow.poly(polygon);\n }\n\n if (shadows.length) {\n shadow.fill(WHITE);\n }\n\n // маска ставится и снимается только на смене: без теней стенсил не\n // нужен вовсе\n const masked = shadows.length > 0;\n\n if (masked !== entry.masked) {\n entry.masked = masked;\n\n if (masked) {\n container.setMask({ mask: shadow, inverse: true });\n } else {\n container.mask = null;\n // снятая маска возвращает графике отрисовку: пустая она невидима\n shadow.visible = false;\n }\n }\n\n if (masked) {\n shadow.visible = true;\n }\n }\n }\n\n // Освобождение: сначала со сцены, потом ресурсы. Текстуры источников —\n // общие запечённые ассеты, их не трогаем\n destroy() {\n const overlay = this.overlay;\n\n overlay.parent?.removeChild(overlay);\n disposeHole(this.hole, overlay);\n overlay.filters = [];\n this.filter.destroy();\n\n for (const sprite of [...this.pool, ...this.rampPool]) {\n sprite.texture = null;\n }\n\n // геометрию вееров Mesh.destroy не уничтожает — только отвязывает\n for (const mesh of [...this.fanPool, ...this.rampFanPool]) {\n mesh.texture = Texture.EMPTY;\n mesh.geometry.destroy();\n }\n\n this.pool = [];\n this.rampPool = [];\n this.fanPool = [];\n this.rampFanPool = [];\n this.rampLights.mask = null;\n this.rampMask = null;\n this.ramps = [];\n\n for (const entry of this.shaftPool) {\n entry.container.mask = null;\n entry.sprite.texture = null;\n }\n\n this.shaftPool = [];\n this.topGroups = [];\n\n overlay.destroy({ children: true, texture: false, textureSource: false });\n }\n}\n\n// спрайты источников переиспользуются между кадрами: число видимых\n// источников меняется, объекты — нет\nfunction layoutPool(pool, container, items) {\n while (pool.length < items.length) {\n const sprite = new Sprite();\n\n sprite.blendMode = 'add';\n pool.push(sprite);\n container.addChild(sprite);\n }\n\n for (let i = 0; i < pool.length; i += 1) {\n const sprite = pool[i];\n const item = items[i];\n\n if (!item) {\n sprite.visible = false;\n continue;\n }\n\n sprite.visible = true;\n sprite.texture = item.texture;\n sprite.anchor.set(item.anchorX, item.anchorY);\n sprite.position.set(item.x, item.y);\n sprite.scale.set(item.scaleX, item.scaleY);\n sprite.rotation = item.rotation;\n sprite.tint = item.color;\n sprite.alpha = item.alpha;\n }\n}\n\n// Веера конусов: меш на источник. `item.fan` — `{ shape, x, y, scale }`:\n// `shape` (`points` и `uvs` в мировых единицах) один на все кадры, пока фара\n// стоит; проекцию высоты даёт трансформ меша `p·scale + (x, y)` — ровно\n// `offsetPoint`\nfunction layoutFans(pool, container, items) {\n while (pool.length < items.length) {\n const geometry = new MeshGeometry({\n positions: new Float32Array(6),\n uvs: new Float32Array(6),\n indices: fanIndices(2),\n });\n const mesh = new Mesh({ geometry, texture: Texture.EMPTY });\n\n mesh.blendMode = 'add';\n mesh.shape = null;\n mesh.topology = null;\n pool.push(mesh);\n container.addChild(mesh);\n }\n\n for (let i = 0; i < pool.length; i += 1) {\n const mesh = pool[i];\n const item = items[i];\n\n if (!item) {\n mesh.visible = false;\n continue;\n }\n\n const { fan } = item;\n const { shape } = fan;\n\n // буферы переписываются только на смене веера\n if (mesh.shape !== shape) {\n const geometry = mesh.geometry;\n const rays = shape.points.length / 2 - 1;\n const topology = `${rays}:${Boolean(shape.closed)}`;\n\n // индексы зависят только от числа лучей и замкнутости веера\n if (mesh.topology !== topology) {\n geometry.indices = fanIndices(rays, shape.closed);\n mesh.topology = topology;\n }\n\n geometry.positions = shape.points;\n geometry.uvs = shape.uvs;\n mesh.shape = shape;\n }\n\n mesh.visible = true;\n mesh.texture = item.texture;\n mesh.position.set(fan.x, fan.y);\n mesh.scale.set(fan.scale);\n mesh.tint = item.color;\n mesh.alpha = item.alpha;\n }\n}\n","import { Container, Sprite, Ticker } from 'pixi.js';\nimport { levelZ } from '../levelZ.js';\nimport { cameraCenter } from '../camera.js';\nimport {\n baseScale,\n cellOfPoint,\n cellsCoverPoint,\n} from '../parts/map/tileGrid.js';\nimport {\n advance as advanceHole,\n apply as applyHole,\n} from '../parts/map/holeOverlay.js';\nimport {\n lighting as lightingConfig,\n parallax as parallaxConfig,\n volume as volumeConfig,\n} from '../../config/render.js';\nimport { offsetPoint } from '../parallax.js';\nimport LevelLightMap from './LevelLightMap.js';\nimport {\n EMISSIVE_BASE_Z,\n LAMP_HEAD_BASE_Z,\n buildLightGrid,\n castRay,\n cellCenter,\n cellRuns,\n coneFan,\n fanUvs,\n firstHit,\n lightStrength,\n rampBlocks,\n flashFactor,\n flicker,\n isOnScreen,\n projectLight,\n queryLightGrid,\n selectLights,\n shadowWedge,\n shaftSway,\n} from './lightMath.js';\n\n// цвет полумрака, если карта его не задала\nconst DEFAULT_AMBIENT = 0x3a4260;\n\n// ключи текстур, которые принимает сервис (7.3); `glint` и `shaft` —\n// засветы и лучи фонарей\nconst TEXTURE_KEYS = ['radial', 'cone', 'head', 'glint', 'shaft'];\n\n// наибольшее покачивание лучей фонаря, рад\nconst SHAFT_SWAY = 0.08;\n\n// отступ отсвета фары от стены в долях его радиуса\nconst BOUNCE_PULL = 0.6;\n\n// Сервис пула зависимостей `lighting`: ночь, карты освещённости уровней,\n// источники света и эмиссивный слой. Возвращается из `hooks.services(core)`\n// (src/client/index.js), по экземпляру на ядро.\n//\n// Два вида состояния:\n// - состояние КАРТЫ — оверлеи уровней, фонари (источники и головы), вклады\n// масок этажей. Живёт по ключу карты (`lightMath.mapKeyOf`), освобождается\n// `clear()` и при переключении ключа;\n// - состояние СЕССИИ — зарегистрированные текстуры, источники `addLight`,\n// записи эмиссива. Переживает смену карты: танки и эффекты — не части\n// карты, движок уничтожает их отдельно, и снимают своё они сами.\n// Область карт освещённости в мировых единицах: карта плюс запас на\n// каждую сторону, равный её большей стороне, — при максимальном отдалении\n// камеры за краем карты тоже полумрак\nexport function lightArea(size, step, scale) {\n const width = (size?.cols ?? 0) * step * scale.x;\n const height = (size?.rows ?? 0) * step * scale.y;\n const margin = Math.max(width, height);\n\n return {\n x: -margin,\n y: -margin,\n width: width + margin * 2,\n height: height + margin * 2,\n };\n}\n\nexport function createLighting(cfg = lightingConfig, deps = {}) {\n const enabled = Boolean(cfg.enabled);\n const levelView = deps.levelView || null;\n const shear = deps.shear ?? parallaxConfig.shear;\n\n let stage = null;\n let renderer = null;\n\n // --- состояние сессии ---\n const textures = {};\n const lights = new Set();\n // sprite -> level\n const emissive = new Map();\n const flashes = [];\n // предметы, отбрасывающие тени в лучах: owner -> { x, y, z, level, radius }\n const casters = new Map();\n // бюджет засветов на тик: `lightsAt` с ответом — не больше `maxLights`\n let glintTick = null;\n let glintCount = 0;\n\n // --- состояние карты ---\n let mapKey = null;\n // ключ -> число частей, взявших его\n const counts = new Map();\n let map = null;\n // level -> Map(owner -> cells)\n const masks = new Map();\n // то же для крыш (`game.roofs`): у них своя карта освещённости уровня\n const roofMasks = new Map();\n // level -> Map(owner -> { cells, volume }) — вершины объёмов\n const tops = new Map();\n // level -> Map(owner -> lanes) — клинья рамп, ведущих с уровня вверх\n // (полосы `buildRampLanes`): на них светят источники уровня вершины\n const ramps = new Map();\n let masksDirty = false;\n\n // ключ раскладки: трансформ сцены + тик (см. render)\n let layoutKey = null;\n\n // веера фар у стен: источник -> { key, shape, hit }. Пересчёт — только\n // когда фара сдвинулась, повернулась или сменилась сетка препятствий\n const fans = new WeakMap();\n // номер сборки сетки препятствий (`syncLevels`): ключ кеша вееров\n let blockersVersion = 0;\n\n const seeThroughCfg = () => levelView?.cfg || null;\n\n // --- эмиссив ---\n\n // контейнер уровня `level` в наборе `store` (level -> Container)\n const levelContainer = (store, base, name, level) => {\n let container = store.get(level);\n\n if (!container) {\n container = new Container();\n container.label = `${name}-${level}`;\n container.zIndex = levelZ(base, level);\n container.eventMode = 'none';\n store.set(level, container);\n }\n\n return container;\n };\n\n const emissiveContainer = level =>\n levelContainer(map.emissive, EMISSIVE_BASE_Z, 'emissive', level);\n\n const lampHeadContainer = level =>\n levelContainer(map.lampHeads, LAMP_HEAD_BASE_Z, 'lamp-heads', level);\n\n // головы фонарей: создаются, когда есть и карта, и текстура `head`\n const ensureLampHeads = () => {\n if (!map?.night || !textures.head || map.headsReady) {\n return;\n }\n\n map.headsReady = true;\n\n for (const lamp of map.lamps) {\n if (!lamp.head) {\n continue;\n }\n\n const sprite = new Sprite(textures.head.texture);\n\n sprite.anchor.set(0.5);\n sprite.blendMode = 'add';\n sprite.tint = lamp.color;\n lamp.headSprite = sprite;\n lampHeadContainer(lamp.level).addChild(sprite);\n }\n };\n\n // --- жизненный цикл карты ---\n\n const initMap = (lightingCfg, step, scale, size) => {\n const source = lightingCfg || {};\n const night = Boolean(source.night);\n const mapScale = baseScale(scale ?? 1);\n\n map = {\n night,\n ambient: source.ambient ?? DEFAULT_AMBIENT,\n step,\n scale: mapScale,\n area: lightArea(size, step, mapScale),\n cols: size?.cols ?? 0,\n rows: size?.rows ?? 0,\n // level -> Uint8Array(cols·rows): клетки объёмов уровня — стены, в\n // которые упирается свет фар\n blockers: new Map(),\n // level -> { cells: Int32Array(cols·rows), lanes } — клетки рамп,\n // ведущих с уровня вверх (номер полосы + 1): склон ловит свет своего\n // подножия, только если луч вошёл в него снизу\n rampCells: new Map(),\n lamps: [],\n headsReady: false,\n levels: new Map(),\n // level -> LevelLightMap крыш уровня\n roofLevels: new Map(),\n emissive: new Map(),\n // level -> контейнер голов фонарей (под танком, см. LAMP_HEAD_BASE_Z)\n lampHeads: new Map(),\n // level -> Set('col,row') — для режима 'layer' дыры\n maskCells: new Map(),\n // level -> Set('col,row') — клетки крыш: дыра их карты открывается,\n // только когда крыша закрывает танк\n roofCells: new Map(),\n };\n\n if (!night) {\n return;\n }\n\n (source.lamps || []).forEach((lamp, index) => {\n const [col, row] = lamp.cell || [0, 0];\n const level = lamp.level || 0;\n const point = cellCenter(col, row, step, mapScale);\n\n map.lamps.push({\n kind: 'radial',\n level,\n x: point.x,\n y: point.y,\n z: level,\n radius: lamp.radius ?? 0,\n rotation: 0,\n color: lamp.color ?? 0xffffff,\n intensity: lamp.intensity ?? 1,\n flicker: lamp.flicker || 0,\n seed: index + 1,\n head: Boolean(lamp.head),\n headSprite: null,\n });\n });\n\n // уже принятые спрайты эмиссива возвращаются в контейнеры новой карты\n for (const [sprite, level] of emissive) {\n emissiveContainer(level).addChild(sprite);\n }\n\n // сетка фонарей для `lightsAt`: клетка — наибольший радиус фонаря,\n // фонарь лежит не больше чем в девяти клетках\n map.lampGrid = buildLightGrid(\n map.lamps,\n Math.max(1, ...map.lamps.map(lamp => lamp.radius)),\n );\n\n ensureLampHeads();\n masksDirty = true;\n layoutKey = null;\n };\n\n // освобождает оверлеи, эмиссивные контейнеры и фонари карты: сначала со\n // сцены, потом destroy. Спрайты эмиссива сессии вынимаются, но не\n // уничтожаются — ими владеют части\n const releaseMapResources = () => {\n if (!map) {\n return;\n }\n\n for (const container of [\n ...map.emissive.values(),\n ...map.lampHeads.values(),\n ]) {\n container.parent?.removeChild(container);\n }\n\n for (const levelMap of allLevelMaps()) {\n levelMap.overlay.parent?.removeChild(levelMap.overlay);\n }\n\n for (const container of map.emissive.values()) {\n for (const sprite of emissive.keys()) {\n if (sprite.parent === container) {\n container.removeChild(sprite);\n }\n }\n }\n\n for (const lamp of map.lamps) {\n if (lamp.headSprite) {\n lamp.headSprite.destroy({ texture: false, textureSource: false });\n lamp.headSprite = null;\n }\n }\n\n for (const container of [\n ...map.emissive.values(),\n ...map.lampHeads.values(),\n ]) {\n container.destroy({ children: true });\n }\n\n for (const levelMap of allLevelMaps()) {\n levelMap.destroy();\n }\n\n map = null;\n layoutKey = null;\n };\n\n const clear = () => {\n releaseMapResources();\n masks.clear();\n roofMasks.clear();\n tops.clear();\n ramps.clear();\n masksDirty = false;\n mapKey = null;\n };\n\n // --- маски этажей ---\n\n // все карты освещённости карты: обычные и крыш\n const allLevelMaps = () => [...map.levels.values(), ...map.roofLevels.values()];\n\n // объединение вкладов уровня без повторов: `[cells, keys]`, где `keys` —\n // Set('col,row'); клетки из `exclude` пропускаются\n const unionOf = (byOwner, exclude = null) => {\n const union = [];\n const keys = new Set();\n\n for (const cells of byOwner?.values() || []) {\n for (const cell of cells) {\n const key = `${cell[0]},${cell[1]}`;\n\n if (!keys.has(key) && !exclude?.has(key)) {\n keys.add(key);\n union.push(cell);\n }\n }\n }\n\n return [union, keys];\n };\n\n // карта уровня в реестре `registry`: пустой маске — уходит, иначе\n // создаётся при необходимости и получает маску\n const syncLevelMap = (registry, level, cells, roof) => {\n let levelMap = registry.get(level);\n\n if (cells.length === 0) {\n // вкладов уровня не осталось — оверлей уходит\n if (levelMap) {\n levelMap.destroy();\n registry.delete(level);\n }\n\n return;\n }\n\n if (!levelMap) {\n levelMap = new LevelLightMap({\n level,\n ambient: map.ambient,\n resolution: cfg.resolution,\n area: map.area,\n roof,\n });\n registry.set(level, levelMap);\n }\n\n levelMap.setMask(cellRuns(cells), map.step, map.scale);\n };\n\n // вершины объёмов уровня группами по высоте: `[{ volume, runs }]`\n const topGroupsOf = level => {\n const byVolume = new Map();\n\n for (const { cells, volume } of tops.get(level)?.values() || []) {\n if (!byVolume.has(volume)) {\n byVolume.set(volume, []);\n }\n\n byVolume.get(volume).push(...cells);\n }\n\n return [...byVolume].map(([volume, cells]) => ({\n volume,\n runs: cellRuns(cells),\n }));\n };\n\n // Сетка препятствий свету фар: на уровень — клетки его объёмов (стены\n // зданий, канала, перила; то же, что закрывают вершины `setTops`) и\n // клетки рамп, ведущих с него вверх\n const syncBlockers = () => {\n map.blockers = new Map();\n map.rampCells = new Map();\n blockersVersion += 1;\n\n const { cols, rows } = map;\n\n if (!(cols > 0 && rows > 0)) {\n return;\n }\n\n const inside = (col, row) => col >= 0 && col < cols && row >= 0 && row < rows;\n\n for (const [level, byOwner] of tops) {\n let grid = null;\n\n for (const { cells } of byOwner.values()) {\n for (const [col, row] of cells) {\n if (inside(col, row)) {\n grid ||= new Uint8Array(cols * rows);\n grid[row * cols + col] = 1;\n }\n }\n }\n\n if (grid) {\n map.blockers.set(level, grid);\n }\n }\n\n for (const [level, byOwner] of ramps) {\n const lanes = [...byOwner.values()].flat();\n\n if (!lanes.length) {\n continue;\n }\n\n const cells = new Int32Array(cols * rows);\n\n lanes.forEach((lane, index) => {\n for (let row = lane.row0; row < lane.row1; row += 1) {\n for (let col = lane.col0; col < lane.col1; col += 1) {\n if (inside(col, row)) {\n cells[row * cols + col] = index + 1;\n }\n }\n }\n });\n\n map.rampCells.set(level, { cells, lanes });\n }\n };\n\n // Препятствия свету фары `light` на её уровне: `isBlocked` для\n // `castRay`; null — ни стен, ни рамп на уровне нет. На полосу, где\n // стоит сама фара, правила рамп (`rampBlocks`) не действуют: танк на\n // склоне светит по нему как раньше\n const obstaclesFor = light => {\n const level = light.level ?? 0;\n const grid = map.blockers.get(level) || null;\n const { cols, rows } = map;\n const cellW = map.step * map.scale.x;\n const cellH = map.step * map.scale.y;\n const indexOf = (col, row) =>\n col >= 0 && col < cols && row >= 0 && row < rows ? row * cols + col : -1;\n const ramp = map.rampCells.get(level) || null;\n\n if (!grid && !ramp) {\n return null;\n }\n\n const laneAt = index => (ramp && index >= 0 && ramp.cells[index] > 0\n ? ramp.lanes[ramp.cells[index] - 1]\n : null);\n // полоса, на которой стоит сама фара: танк на склоне светит по ней\n // как раньше, а соседние горки загораживают его свет по общим правилам\n const home = laneAt(\n indexOf(Math.floor(light.x / cellW), Math.floor(light.y / cellH)),\n );\n return (col, row, prevCol, prevRow, x, y) => {\n const index = indexOf(col, row);\n\n if (grid && index >= 0 && grid[index] === 1) {\n return true;\n }\n\n if (!ramp || prevCol === null || prevCol === undefined) {\n return false;\n }\n\n const lane = laneAt(index);\n const prevLane = laneAt(indexOf(prevCol, prevRow));\n\n if (home && (lane === home || prevLane === home)) {\n return false;\n }\n\n return rampBlocks({\n lane,\n prevLane,\n col,\n row,\n prevCol,\n prevRow,\n x,\n y,\n z: light.z ?? level,\n cellW,\n cellH,\n });\n };\n };\n\n const occlusionCfg = () => cfg.headlights?.occlusion;\n\n // Веер фары у стены и точка упора её оси — `{ shape, hit }`: `shape` —\n // null, пока конус никуда не упёрся (рисуется прежним спрайтом). Без\n // окклюзии, без стен на уровне или без текстуры — null\n const occlusionOf = (light, asset) => {\n const occlusion = occlusionCfg();\n\n if (!occlusion?.enabled || !asset || !(light.radius > 0)) {\n return null;\n }\n\n const level = light.level ?? 0;\n const rotation = light.rotation || 0;\n const spread = light.spread ?? 0.5;\n // высота — в ключе: от неё зависит, пропустит ли фару борт рампы\n const key = `${light.x},${light.y},${light.z},${rotation},${level},${light.radius},${spread},${blockersVersion},${occlusion.rays}`;\n const cached = fans.get(light);\n\n if (cached && cached.key === key && cached.texture === asset.texture) {\n return cached;\n }\n\n const isBlocked = obstaclesFor(light);\n\n if (!isBlocked) {\n return null;\n }\n\n const cellW = map.step * map.scale.x;\n const cellH = map.step * map.scale.y;\n const width = asset.texture.width;\n const height = asset.texture.height;\n // мировых единиц на пиксель текстуры — как у спрайта в `itemOf`\n const sx = light.radius / asset.length;\n const sy = (light.radius * spread) / asset.halfWidth;\n const { points, clipped, closed } = coneFan(\n {\n x: light.x,\n y: light.y,\n rotation,\n alongMax: (width - asset.margin) * sx,\n alongBack: asset.margin * sx,\n acrossMax: (height / 2) * sy,\n rays: occlusion.rays,\n },\n isBlocked,\n cellW,\n cellH,\n );\n const result = {\n key,\n texture: asset.texture,\n shape: clipped\n ? {\n points,\n closed,\n uvs: fanUvs(points, {\n x: light.x,\n y: light.y,\n rotation,\n sx,\n sy,\n margin: asset.margin,\n width,\n height,\n }),\n }\n : null,\n hit: firstHit(\n light.x,\n light.y,\n Math.cos(rotation),\n Math.sin(rotation),\n light.radius,\n isBlocked,\n cellW,\n cellH,\n ),\n };\n\n fans.set(light, result);\n\n return result;\n };\n\n // видна ли точка из фары: между ними нет стены уровня фары\n const reachesPoint = (light, x, y) => {\n if (light.kind !== 'cone' || !occlusionCfg()?.enabled) {\n return true;\n }\n\n const isBlocked = obstaclesFor(light);\n\n if (!isBlocked) {\n return true;\n }\n\n const distance = Math.hypot(x - light.x, y - light.y);\n\n if (distance < 1e-6) {\n return true;\n }\n\n return (\n castRay(\n light.x,\n light.y,\n (x - light.x) / distance,\n (y - light.y) / distance,\n distance,\n isBlocked,\n map.step * map.scale.x,\n map.step * map.scale.y,\n ) >= distance\n );\n };\n\n const syncLevels = () => {\n const dirty = masksDirty;\n\n if (masksDirty) {\n masksDirty = false;\n\n const levels = new Set([\n ...masks.keys(),\n ...roofMasks.keys(),\n ...map.levels.keys(),\n ...map.roofLevels.keys(),\n ]);\n\n for (const level of levels) {\n if (level < 1) {\n continue;\n }\n\n // крыши — своя карта; обычная маска уровня их клеток не несёт,\n // иначе дыра обычной карты снимала бы затемнение и с крыш\n const [roofUnion, roofKeys] = unionOf(roofMasks.get(level));\n const [union, keys] = unionOf(masks.get(level), roofKeys);\n\n map.maskCells.set(level, keys);\n map.roofCells.set(level, roofKeys);\n syncLevelMap(map.levels, level, union, false);\n syncLevelMap(map.roofLevels, level, roofUnion, true);\n }\n }\n\n // уровень 0 на ночной карте есть всегда: полумрак нужен всей земле\n if (!map.levels.has(0)) {\n map.levels.set(\n 0,\n new LevelLightMap({\n level: 0,\n ambient: map.ambient,\n resolution: cfg.resolution,\n area: map.area,\n }),\n );\n }\n\n // вершины объёмов и клинья рамп — в обычную карту своего уровня\n if (dirty) {\n syncBlockers();\n\n for (const levelMap of map.levels.values()) {\n levelMap.setTops(topGroupsOf(levelMap.level), map.step, map.scale);\n levelMap.setRamps(\n [...(ramps.get(levelMap.level)?.values() || [])].flat(),\n map.step,\n map.scale,\n volumeConfig.rampSegments,\n );\n }\n }\n };\n\n const attachToStage = () => {\n for (const levelMap of allLevelMaps()) {\n if (levelMap.overlay.parent !== stage) {\n stage.addChild(levelMap.overlay);\n }\n }\n\n for (const container of [\n ...map.emissive.values(),\n ...map.lampHeads.values(),\n ]) {\n if (container.parent !== stage) {\n stage.addChild(container);\n }\n }\n };\n\n // --- раскладка источников ---\n\n const itemOf = (light, camera, factor) => {\n const asset = light.kind === 'cone' ? textures.cone : textures.radial;\n\n if (!asset || !(light.intensity * factor > 0)) {\n return null;\n }\n\n const view = projectLight(light.x, light.y, light.z ?? light.level, camera, stage, shear);\n\n if (light.kind === 'cone') {\n const length = light.radius * view.scale;\n const halfWidth = length * (light.spread ?? 0.5);\n const reach = length * stage.scale.x;\n\n return {\n reach,\n view,\n // веер и упор оси — `occludeItem`, уже после отсечения по экрану\n fan: null,\n hit: null,\n texture: asset.texture,\n anchorX: asset.margin / asset.texture.width,\n anchorY: 0.5,\n scaleX: length / asset.length,\n scaleY: halfWidth / asset.halfWidth,\n rotation: light.rotation || 0,\n color: light.color,\n alpha: light.intensity * factor,\n };\n }\n\n const radius = light.radius * view.scale;\n\n return {\n reach: radius * stage.scale.x,\n view,\n texture: asset.texture,\n anchorX: 0.5,\n anchorY: 0.5,\n scaleX: (radius * 2) / asset.contentSize,\n scaleY: (radius * 2) / asset.contentSize,\n rotation: 0,\n color: light.color,\n alpha: light.intensity * factor,\n };\n };\n\n // Отсвет фары от стены: радиальное пятно на полу перед точкой упора оси,\n // сила — доля фары, спадает с расстоянием до стены. Центр отнесён от\n // стены на `BOUNCE_PULL` радиуса: пятно почти не заходит за неё\n const bounceOf = (light, hit) => {\n const bounce = cfg.headlights?.bounce;\n\n if (\n !bounce ||\n !(bounce.intensity > 0) ||\n !(bounce.radius > 0) ||\n hit.distance > (bounce.maxDistance ?? light.radius)\n ) {\n return null;\n }\n\n const rotation = light.rotation || 0;\n const back = bounce.radius * BOUNCE_PULL;\n\n return {\n kind: 'radial',\n level: light.level,\n levels: light.levels,\n x: hit.x - Math.cos(rotation) * back,\n y: hit.y - Math.sin(rotation) * back,\n z: light.z,\n radius: bounce.radius,\n color: light.color,\n intensity:\n (light.intensity ?? 1) *\n bounce.intensity *\n (1 - hit.distance / light.radius),\n };\n };\n\n // Окклюзия конуса, прошедшего отсечение по экрану: веер по полигону\n // видимости, если конус упёрся в стену, и точка упора оси для отсвета.\n // Считать её до отсечения значило бы гонять лучи для всех танков карты\n const occludeItem = (item, light) => {\n if (light.kind !== 'cone') {\n return;\n }\n\n const occlusion = occlusionOf(light, textures.cone);\n\n if (!occlusion) {\n return;\n }\n\n const { view } = item;\n\n if (occlusion.shape) {\n item.fan = {\n shape: occlusion.shape,\n x: view.x - light.x * view.scale,\n y: view.y - light.y * view.scale,\n scale: view.scale,\n };\n }\n\n item.hit = occlusion.hit;\n };\n\n const layoutLights = (camera, screen, now) => {\n const perLevel = new Map();\n // свет верхнего уровня на клиньях рамп: level подножия -> items\n const perRamp = new Map();\n let count = 0;\n\n // источник с `levels` (танк на рампе) светит в карты нескольких уровней:\n // проекция одна (по `z`), лимит считается по добавленным спрайтам\n // клинья рамп: уровень вершины -> уровни подножия, чья карта кладёт\n // его источники в `rampLights`\n const rampTargets = new Map();\n\n for (const levelMap of map.levels.values()) {\n for (const top of levelMap.rampLevels()) {\n if (!rampTargets.has(top)) {\n rampTargets.set(top, []);\n }\n\n rampTargets.get(top).push(levelMap.level);\n }\n }\n\n const spill = cfg.rampSpill ?? 1;\n\n const push = (light, factor) => {\n const own = light.levels ?? [light.level];\n const levels = own.filter(level => map.levels.has(level));\n // подножия рамп, ведущих на уровни источника. Уровень, в чью карту\n // источник уже светит (танк на рампе — `levels [0, 1]`), не\n // получает его второй раз\n const feet = spill > 0\n ? own.flatMap(level => rampTargets.get(level) || [])\n .filter(level => !own.includes(level))\n : [];\n\n if (count >= cfg.maxLights || (levels.length === 0 && feet.length === 0)) {\n return;\n }\n\n const item = itemOf(light, camera, factor);\n\n if (\n !item ||\n !isOnScreen(\n item.view.screenX,\n item.view.screenY,\n item.reach,\n screen.width,\n screen.height,\n )\n ) {\n return;\n }\n\n item.x = item.view.x;\n item.y = item.view.y;\n occludeItem(item, light);\n\n for (const level of levels) {\n if (count >= cfg.maxLights) {\n return;\n }\n\n if (!perLevel.has(level)) {\n perLevel.set(level, []);\n }\n\n perLevel.get(level).push(item);\n count += 1;\n }\n\n for (const level of new Set(feet)) {\n if (count >= cfg.maxLights) {\n return;\n }\n\n if (!perRamp.has(level)) {\n perRamp.set(level, []);\n }\n\n perRamp.get(level).push({ ...item, alpha: item.alpha * spill });\n count += 1;\n }\n\n // фара упёрлась в стену: свет не пропадает, а отскакивает пятном.\n // Пятна раскладываются после фонарей: вторичный свет не вытесняет\n // из `maxLights` уличные фонари\n const bounce = item.hit ? bounceOf(light, item.hit) : null;\n\n if (bounce) {\n bounces.push({ light: bounce, factor });\n }\n };\n const bounces = [];\n\n // вспышки — первыми: они короткие и заметнее всего\n for (let i = flashes.length - 1; i >= 0; i -= 1) {\n const flash = flashes[i];\n const factor = flashFactor(now - flash.start, flash.duration);\n\n if (factor <= 0) {\n flashes.splice(i, 1);\n } else {\n push(flash, factor);\n }\n }\n\n for (const light of lights) {\n push(light, 1);\n }\n\n for (const lamp of map.lamps) {\n push(lamp, flicker(lamp.seed, now, lamp.flicker));\n }\n\n for (const { light, factor } of bounces) {\n push(light, factor);\n }\n\n const shafts = layoutShafts(camera, screen, now);\n\n // карта крыш уровня получает те же источники, что и обычная\n for (const levelMap of allLevelMaps()) {\n levelMap.place(camera, shear);\n levelMap.layout(perLevel.get(levelMap.level) || []);\n // у карты крыш рамп нет: их свет — только в обычной карте подножия\n levelMap.layoutRamps(\n levelMap.roof ? [] : perRamp.get(levelMap.level) || [],\n );\n levelMap.layoutShafts(shafts.get(levelMap.level) || []);\n }\n };\n\n // клинья теней предметов уровня фонаря в его лучах: ближайшие\n // `maxShadowCasters` в радиусе лучей, в нарисованных координатах\n const shadowsOf = (lamp, view, reach, camera, shaftsCfg) => {\n if (!shaftsCfg.shadows || !(shaftsCfg.maxShadowCasters > 0)) {\n return [];\n }\n\n const worldReach = lamp.radius * shaftsCfg.length;\n const near = [];\n\n for (const caster of casters.values()) {\n const distance = Math.hypot(caster.x - lamp.x, caster.y - lamp.y);\n\n if (caster.level === lamp.level && distance < worldReach) {\n near.push({ caster, distance });\n }\n }\n\n near.sort((a, b) => a.distance - b.distance);\n\n const polygons = [];\n\n for (const { caster } of near.slice(0, shaftsCfg.maxShadowCasters)) {\n const k = (caster.z ?? caster.level) * shear;\n const point = offsetPoint(caster.x, caster.y, camera, k);\n const wedge = shadowWedge(\n view.x,\n view.y,\n point.x,\n point.y,\n caster.radius * (1 + k),\n reach,\n );\n\n if (wedge) {\n polygons.push(wedge);\n }\n }\n\n return polygons;\n };\n\n // лучи в воздухе вокруг голов фонарей: level -> items для\n // `LevelLightMap.layoutShafts`\n const layoutShafts = (camera, screen, now) => {\n const perLevel = new Map();\n const shaftsCfg = cfg.shafts;\n const asset = textures.shaft;\n\n if (!shaftsCfg?.enabled || !asset) {\n return perLevel;\n }\n\n let count = 0;\n\n for (const lamp of map.lamps) {\n if (!lamp.head || !map.levels.has(lamp.level)) {\n continue;\n }\n\n if (count >= cfg.maxLights) {\n break;\n }\n\n const view = projectLight(lamp.x, lamp.y, lamp.level, camera, stage, shear);\n const reach = lamp.radius * shaftsCfg.length * view.scale;\n\n if (\n !(reach > 0) ||\n !isOnScreen(\n view.screenX,\n view.screenY,\n reach * stage.scale.x,\n screen.width,\n screen.height,\n )\n ) {\n continue;\n }\n\n if (!perLevel.has(lamp.level)) {\n perLevel.set(lamp.level, []);\n }\n\n perLevel.get(lamp.level).push({\n texture: asset.texture,\n x: view.x,\n y: view.y,\n scale: (reach * 2) / asset.contentSize,\n rotation: shaftSway(lamp.seed, now, SHAFT_SWAY),\n color: lamp.color,\n alpha:\n shaftsCfg.intensity *\n lamp.intensity *\n flicker(lamp.seed, now, lamp.flicker),\n shadows: shadowsOf(lamp, view, reach, camera, shaftsCfg),\n });\n count += 1;\n }\n\n return perLevel;\n };\n\n // --- прозрачность над игроком ---\n\n const updateHoles = (camera, stepped) => {\n const see = seeThroughCfg();\n\n if (!see) {\n return;\n }\n\n const dt = Ticker.shared.deltaMS / 1000;\n const rate = stepped ? Math.min(1, see.fadeRate * dt) : 0;\n // нарисованная точка игрока: крыша закрывает её, а не мировую\n const player = offsetPoint(levelView.x, levelView.y, camera, levelView.z * shear);\n\n for (const levelMap of allLevelMaps()) {\n if (levelMap.level < 1) {\n continue;\n }\n\n const below = levelView.level < levelMap.level;\n // крыша уступает, только когда закрывает танк (как её слой)\n const roofHides =\n levelMap.roof &&\n below &&\n cellsCoverPoint(\n map.roofCells.get(levelMap.level),\n map.step,\n map.scale,\n player,\n camera,\n [levelMap.level * shear],\n see.roofMargin ?? 0,\n );\n const overlay = levelMap.overlay;\n\n if (levelView.mode === 'layer') {\n let under = roofHides;\n\n if (!levelMap.roof) {\n const col = cellOfPoint(levelView.x, map.scale.x, map.step);\n const row = cellOfPoint(levelView.y, map.scale.y, map.step);\n\n under =\n below &&\n Boolean(map.maskCells.get(levelMap.level)?.has(`${col},${row}`));\n }\n\n const target = under ? see.layerAlpha : 1;\n\n overlay.alpha += (target - overlay.alpha) * rate;\n continue;\n }\n\n advanceHole(levelMap.hole, levelMap.roof ? roofHides : below, rate);\n applyHole(overlay, levelMap.hole, see, levelView, stage, camera);\n\n // фильтр дыры сам становится последним в цепочке и обязан класть\n // карту на сцену тем же умножением; без дыры — проходной фильтр\n if (levelMap.hole.attached) {\n levelMap.hole.filter.blendMode = 'multiply';\n } else if (overlay.filters?.[0] !== levelMap.filter) {\n overlay.filters = [levelMap.filter];\n }\n }\n };\n\n const updateEmissive = (camera, now) => {\n for (const lamp of map.lamps) {\n const sprite = lamp.headSprite;\n\n if (!sprite) {\n continue;\n }\n\n const view = projectLight(lamp.x, lamp.y, lamp.level, camera, stage, shear);\n\n sprite.position.set(view.x, view.y);\n sprite.scale.set(view.scale);\n sprite.alpha =\n (levelView ? levelView.alphaFor(lamp.level, view.x, view.y, 0) : 1) *\n flicker(lamp.seed, now, lamp.flicker);\n }\n\n // спрайты сессии уже в нарисованных координатах: `z = 0` не сдвигает\n // точку второй раз\n if (levelView) {\n for (const [sprite, level] of emissive) {\n sprite.alpha = levelView.alphaFor(level, sprite.x, sprite.y, 0);\n }\n }\n };\n\n const isNight = () => enabled && map?.night === true;\n\n // источники, светящие в точку уровня `level`: фонари (из сетки), фары\n // танков (конусы сессии) и вспышки — `[{ light, factor }]`. Свет под\n // корпусом (радиальные источники сессии) засвета не даёт\n const candidatesAt = (x, y, level, exclude, now) => {\n const candidates = [];\n const onLevel = light => (light.levels ?? [light.level ?? 0]).includes(level);\n\n for (const lamp of queryLightGrid(map.lampGrid, x, y)) {\n if (lamp.level === level) {\n candidates.push({\n light: lamp,\n factor: flicker(lamp.seed, now, lamp.flicker),\n });\n }\n }\n\n for (const light of lights) {\n // стены — после дешёвой проверки дальности и угла конуса\n if (\n light.kind === 'cone' &&\n !exclude?.includes(light) &&\n onLevel(light) &&\n lightStrength(light, x, y) > 0 &&\n reachesPoint(light, x, y)\n ) {\n candidates.push({ light, factor: 1 });\n }\n }\n\n for (const flash of flashes) {\n const factor = flashFactor(now - flash.start, flash.duration);\n\n if (factor > 0 && onLevel(flash)) {\n candidates.push({ light: flash, factor });\n }\n }\n\n return candidates;\n };\n\n return {\n get enabled() {\n return enabled;\n },\n\n // Ленивая привязка к сцене, как у levelView: зовут части игрового полотна\n attachStage(nextStage, nextRenderer) {\n if (!enabled || stage || !nextStage || !nextRenderer) {\n return;\n }\n\n stage = nextStage;\n renderer = nextRenderer;\n layoutKey = null;\n levelView?.attachStage(nextStage, nextRenderer);\n },\n\n // Каждая статическая часть. Первый вызов НОВОГО ключа инициализирует\n // карту (или переключает на неё), остальные только считают.\n // `size` — `{ cols, rows }` сетки карты: из него область карт\n // освещённости (`lightArea`)\n acquireMap(key, lightingCfg, step, scale, size) {\n if (!enabled) {\n return;\n }\n\n counts.set(key, (counts.get(key) || 0) + 1);\n\n if (key === mapKey) {\n return;\n }\n\n // переключение при живом старом ключе: освобождается только его\n // карта, вклады масок остаются у своих частей\n releaseMapResources();\n mapKey = key;\n initMap(lightingCfg, step, scale, size);\n },\n\n // Из destroy() части: снимает её вклад в маски; на нуле счётчика\n // ТЕКУЩЕГО ключа — clear(). Старый ключ на нуле просто забывается,\n // иначе clear() стёр бы уже переключённую карту\n releaseMap(key, owner) {\n if (!enabled) {\n return;\n }\n\n if (owner !== undefined) {\n for (const registry of [masks, roofMasks, tops, ramps]) {\n for (const byOwner of registry.values()) {\n if (byOwner.delete(owner)) {\n masksDirty = true;\n }\n }\n }\n }\n\n const count = counts.get(key);\n\n if (count === undefined) {\n return;\n }\n\n if (count > 1) {\n counts.set(key, count - 1);\n\n return;\n }\n\n counts.delete(key);\n\n if (key === mapKey) {\n clear();\n }\n },\n\n // вклад части в маску этажа уровня >= 1; маска — объединение вкладов.\n // `roof: true` — клетки крыш (`game.roofs`): они уходят в отдельную\n // карту уровня со своей дырой\n setLevelMask(level, cells, owner, { roof = false } = {}) {\n if (!enabled || !(level >= 1)) {\n return;\n }\n\n const registry = roof ? roofMasks : masks;\n\n if (!registry.has(level)) {\n registry.set(level, new Map());\n }\n\n if (cells && cells.length > 0) {\n registry.get(level).set(owner, cells);\n } else {\n registry.get(level).delete(owner);\n }\n\n masksDirty = true;\n },\n\n // вклад части в вершины объёмов уровня: клетки тайлов объёма и его\n // высота в уровнях. Снимается `releaseMap` вместе с масками\n setVolumeTops(level, cells, volume, owner) {\n if (!enabled || !(level >= 0)) {\n return;\n }\n\n if (!tops.has(level)) {\n tops.set(level, new Map());\n }\n\n if (cells && cells.length > 0 && volume > 0) {\n tops.get(level).set(owner, { cells, volume });\n } else {\n tops.get(level).delete(owner);\n }\n\n masksDirty = true;\n },\n\n // вклад части в клинья рамп, ведущих с уровня `level` вверх: полосы\n // `buildRampLanes` (клетки, `from`/`to`). На них светят источники\n // уровня вершины. Нисходящие полосы не нужны: их клин нарисован в\n // части верхнего уровня и освещён его картой. Снимается `releaseMap`\n setRampWedges(level, lanes, owner) {\n if (!enabled || !(level >= 0)) {\n return;\n }\n\n if (!ramps.has(level)) {\n ramps.set(level, new Map());\n }\n\n const rising = (lanes || []).filter(\n lane => lane.from === level && lane.to > lane.from,\n );\n\n if (rising.length > 0) {\n ramps.get(level).set(owner, rising);\n } else {\n ramps.get(level).delete(owner);\n }\n\n masksDirty = true;\n },\n\n // Частичная регистрация: переданные ключи перезаписываются. Спрайты\n // эмиссива со старой текстурой `head` получают свежую (перепечка после\n // потери контекста)\n registerTextures(patch) {\n if (!enabled || !patch) {\n return;\n }\n\n for (const key of TEXTURE_KEYS) {\n const asset = patch[key];\n\n if (!asset) {\n continue;\n }\n\n const previous = textures[key];\n\n textures[key] = asset;\n\n if (key === 'head' && previous && previous !== asset) {\n for (const sprite of emissive.keys()) {\n if (sprite.texture === previous.texture) {\n sprite.texture = asset.texture;\n }\n }\n\n for (const lamp of map?.lamps || []) {\n if (lamp.headSprite) {\n lamp.headSprite.texture = asset.texture;\n }\n }\n }\n }\n\n if (textures.head) {\n ensureLampHeads();\n }\n },\n\n // зарегистрированный ассет текстуры; null — ещё не передан\n texture(name) {\n return textures[name] || null;\n },\n\n addLight(light) {\n if (!enabled) {\n return null;\n }\n\n const record = {\n kind: 'radial',\n level: 0,\n x: 0,\n y: 0,\n radius: 0,\n rotation: 0,\n color: 0xffffff,\n intensity: 1,\n ...light,\n };\n\n lights.add(record);\n\n return record;\n },\n\n updateLight(handle, patch) {\n if (handle && lights.has(handle)) {\n Object.assign(handle, patch);\n }\n },\n\n removeLight(handle) {\n lights.delete(handle);\n },\n\n // короткая вспышка; без ночи или без текстуры `radial` — no-op\n flash(options) {\n if (!isNight() || !textures.radial) {\n return;\n }\n\n flashes.push({\n kind: 'radial',\n level: 0,\n color: 0xffffff,\n intensity: 1,\n ...options,\n start: Ticker.shared.lastTime,\n });\n },\n\n // false — ночи нет: спрайт остаётся у вызывающей части\n addEmissive(sprite, level) {\n if (!isNight() || !sprite) {\n return false;\n }\n\n const current = emissive.get(sprite);\n\n if (current !== undefined && sprite.parent) {\n sprite.parent.removeChild(sprite);\n }\n\n emissive.set(sprite, level || 0);\n emissiveContainer(level || 0).addChild(sprite);\n\n return true;\n },\n\n removeEmissive(sprite) {\n if (!emissive.has(sprite)) {\n return;\n }\n\n emissive.delete(sprite);\n\n for (const container of map?.emissive.values() || []) {\n if (sprite.parent === container) {\n container.removeChild(sprite);\n }\n }\n },\n\n isNight,\n\n // Засвет: до `limit` сильнейших источников в мировой точке уровня\n // `level` — `[{ light, strength, angle, color }]`, `angle` — от точки НА\n // источник. `exclude` — свои источники (фары самого танка). Без ночи —\n // пусто; после `maxLights` ответов за тик — тоже пусто (бюджет спрайтов)\n lightsAt(x, y, level, limit = 1, exclude = null) {\n if (!isNight() || !(limit > 0)) {\n return [];\n }\n\n const now = Ticker.shared.lastTime;\n\n if (glintTick !== now) {\n glintTick = now;\n glintCount = 0;\n }\n\n if (glintCount >= cfg.maxLights) {\n return [];\n }\n\n const hits = selectLights(\n candidatesAt(x, y, level, exclude, now),\n x,\n y,\n limit,\n );\n\n if (hits.length) {\n glintCount += 1;\n }\n\n return hits;\n },\n\n // виден ли круг `reach` (мировые единицы) вокруг точки на высоте `z`:\n // засветы вне экрана не считаются\n onScreen(x, y, z, reach) {\n if (!stage || !renderer?.screen) {\n return false;\n }\n\n const camera = levelView ? levelView.camera() : cameraCenter(stage, renderer);\n\n if (!camera) {\n return false;\n }\n\n const view = projectLight(x, y, z, camera, stage, shear);\n\n return isOnScreen(\n view.screenX,\n view.screenY,\n reach * view.scale * stage.scale.x,\n renderer.screen.width,\n renderer.screen.height,\n );\n },\n\n // предмет, отбрасывающий тень в лучах фонарей: `{ x, y, z, level,\n // radius }` в мировых единицах; null — снять. Состояние сессии, как\n // `addLight`: предметы снимают себя сами\n setCaster(owner, caster) {\n if (!enabled) {\n return;\n }\n\n if (caster) {\n casters.set(owner, caster);\n } else {\n casters.delete(owner);\n }\n },\n\n // Зовут части из onRender. Раскладка — один раз на трансформ сцены в\n // тике: за тик полотно рисуется несколько раз, и каждая отрисовка после\n // смены камеры получает свежую проекцию, а повторные вызовы той же\n // отрисовки ничего не делают. Сглаживание дыры шагает раз на тик\n render() {\n if (!isNight() || !stage || !renderer) {\n return;\n }\n\n const camera = levelView ? levelView.camera() : cameraCenter(stage, renderer);\n const screen = renderer.screen;\n\n if (!camera || !screen) {\n return;\n }\n\n const tick = Ticker.shared.lastTime;\n const key = layoutKey;\n\n if (\n key !== null &&\n key.tick === tick &&\n key.x === stage.position.x &&\n key.y === stage.position.y &&\n key.scaleX === stage.scale.x &&\n key.scaleY === stage.scale.y &&\n key.width === screen.width &&\n key.height === screen.height &&\n !masksDirty\n ) {\n return;\n }\n\n const stepped = key === null || key.tick !== tick;\n\n layoutKey = {\n tick,\n x: stage.position.x,\n y: stage.position.y,\n scaleX: stage.scale.x,\n scaleY: stage.scale.y,\n width: screen.width,\n height: screen.height,\n };\n\n syncLevels();\n ensureLampHeads();\n attachToStage();\n layoutLights(camera, screen, tick);\n\n if (levelView) {\n updateHoles(camera, stepped);\n }\n\n updateEmissive(camera, tick);\n },\n\n // внутренний, но открыт для тестов и восстановления\n clear,\n };\n}\n","// Шина «танк выстрелил»: эффект выстрела (`ShotEffectController`) сообщает\n// id стрелка, танк с этим id играет отдачу, а пока летит трассер — отдаёт\n// текущую точку своего дула: эффект живёт в мире, а танк за 45–80 мс\n// пролёта проезжает длину корпуса, и без привязки хвост трассера и вспышка\n// отрывались бы от ствола. Экземпляр на ядро, как `levelView`: в\n// headless-раннере в одном процессе живёт несколько клиентов.\n//\n// `segments(x, y, dx, dy, range, level)` — сегменты луча по уровням из ядра\n// (`core.shot_segments`, плоский `[t0, t1, level, …]`); без него `path`\n// отдаёт null, и трассер рисуется целиком на уровне конца\nexport function createShotEvents({ segments = null } = {}) {\n const handlers = new Map();\n\n return {\n // подписка танка по его id: `{ fired(), muzzle() }`, где `muzzle`\n // отдаёт мировую точку дула `{ x, y }` или null. Возвращает отписку\n subscribe(rawId, handler) {\n // id из контекста парта и из строки трассера могут прийти разных\n // типов (строка/число) — ключ всегда строка\n const id = String(rawId);\n\n if (!handlers.has(id)) {\n handlers.set(id, new Set());\n }\n\n handlers.get(id).add(handler);\n\n return () => {\n const set = handlers.get(id);\n\n if (set) {\n set.delete(handler);\n\n if (set.size === 0) {\n handlers.delete(id);\n }\n }\n };\n },\n\n fired(id) {\n const set = handlers.get(String(id));\n\n if (set) {\n for (const handler of set) {\n handler.fired?.();\n }\n }\n },\n\n // текущая точка дула стрелка; null — танка нет (уничтожен, вне кадра)\n muzzle(id) {\n const set = handlers.get(String(id));\n\n if (set) {\n for (const handler of set) {\n const point = handler.muzzle?.();\n\n if (point) {\n return point;\n }\n }\n }\n\n return null;\n },\n\n // сегменты луча от (x0, y0) до (x1, y1), выпущенного с уровня `level`:\n // `[{ t0, t1, level }]` в мировых единицах вдоль луча; null — ядра нет\n // или луч нулевой длины\n path(x0, y0, x1, y1, level) {\n const range = Math.hypot(x1 - x0, y1 - y0);\n\n if (!segments || !(range > 1e-3)) {\n return null;\n }\n\n const flat = segments(\n x0,\n y0,\n (x1 - x0) / range,\n (y1 - y0) / range,\n range,\n level || 0,\n );\n const list = [];\n\n for (let i = 0; i + 2 < (flat?.length ?? 0); i += 3) {\n list.push({ t0: flat[i], t1: flat[i + 1], level: flat[i + 2] });\n }\n\n return list.length ? list : null;\n },\n };\n}\n","// Шина «взрыв»: эффект взрыва (`ExplosionEffectController` — бомба и\n// бочка) сообщает точку, радиус и уровень, а каждый танк сам решает, задел\n// ли его взрыв (`src/client/blastJolt.js`). Экземпляр на ядро, как `shots`\nexport function createBlastEvents() {\n const listeners = new Set();\n\n return {\n // возвращает отписку\n subscribe(callback) {\n listeners.add(callback);\n\n return () => {\n listeners.delete(callback);\n };\n },\n\n exploded(blast) {\n for (const callback of listeners) {\n callback(blast);\n }\n },\n };\n}\n","import { ENGINE_API_VERSION } from 'vimp-engine/config/opcodes.js';\nimport init, { ClientCore } from '../../core/pkg-web/vimp_tanks_core.js';\n// игровой CSS (панель/полотна/команды) как строка — движок сам вставляет\n// его в DOM (поле styles контракта); ?inline не даёт Vite auto-inject\n// <style> при сборке (см. vite.config.js — сборка без index.html)\nimport styles from './tanks.css?inline';\nimport parts from './parts/index.js';\nimport bakers from './bakers/index.js';\nimport { isNodeCore, loadNodeCore } from '../nodeCore.js';\nimport { createLevelView } from './levelView.js';\nimport { createLighting } from './lighting/createLighting.js';\nimport { createShotEvents } from './shotEvents.js';\nimport { createBlastEvents } from './blastEvents.js';\n\n// стрелка клетки по индексу `surface_dir_at`: север/юг/запад/восток, как у\n// рамп (north = −y, east = +x)\nconst SURFACE_DIRS = [\n [0, -1],\n [0, 1],\n [-1, 0],\n [1, 0],\n];\n\n// ClientPlugin танков: рендеры сущностей (parts), процедурные текстуры\n// (bakers) и игровые хуки клиентского ядра (ClientCore). default export\n// client-entry игры (vite.config.js --mode client, Этап 6.1);\n// грузится динамически по GameManifest мастера (Этап 6.3, main.js).\n// Движок (main.js) не знает игровых методов ядра —\n// set_model/sync_panel/try_fire/cycle_weapon зовутся только отсюда.\nexport default {\n id: 'tanks',\n engineApi: ENGINE_API_VERSION,\n\n // см. комментарий в src/host/index.js: обе половины плагина обязаны\n // объявлять одинаковый список возможностей\n requires: ['map.layers', 'map.levelsN', 'map.gameData', 'map.bodyState'],\n\n // wasmUrl — из GameManifest.entries.wasm (общий с host-плагином ассет)\n async createClientCore(clientConfigJson, { wasmUrl }) {\n // node-сборка ядра (headless-раннер): памяти WASM наружу нет и она не\n // нужна — hot читается копией (hot_values), а не вьюхой\n if (isNodeCore(wasmUrl)) {\n const node = await loadNodeCore(wasmUrl);\n\n return { core: new node.ClientCore(clientConfigJson), memory: null };\n }\n\n // eslint-disable-next-line camelcase -- wasm-bindgen init() option name\n const wasm = await init({ module_or_path: wasmUrl });\n\n return { core: new ClientCore(clientConfigJson), memory: wasm.memory };\n },\n\n parts,\n bakers,\n styles,\n\n // имена сервисов, которые доливает hooks.services(core): без этого списка\n // контрактный чекер (правило C4) не может отличить игровой сервис от\n // опечатки в componentDependencies — хук требует живого ядра, а имена\n // из его `return` статически не видны\n serviceNames: [\n 'levelView',\n 'mapDynamics',\n 'rampRuns',\n 'surfaces',\n 'lighting',\n 'shots',\n 'blasts',\n ],\n\n hooks: {\n // сервисы игры для её же parts (движок их не описывает — только раздаёт\n // тем, кто объявил их в componentDependencies, см. src/config/client.js).\n // mapDynamics — геометрия предсказанной динамики карты из ядра: по ней\n // ShotEffect пересчитывает точку удара по ТЕКУЩЕМУ трансформу ящика.\n // levelView — где и на каком уровне локальный игрок: по нему плита моста\n // над ним становится полупрозрачной (2.5D).\n // rampRuns — прогоны рамп из ядра: клин горки рисуется по ТОЙ ЖЕ\n // геометрии, по которой физика ставит стражей прогона\n services(core) {\n // экземпляр на ядро, а не на модуль: в headless-раннере\n // (`npm run sim:scenarios`) в одном процессе живёт несколько\n // VirtualClient, и общее на модуль состояние «где игрок» перезаписывал\n // бы тот из них, кто обновился последним\n const levelView = createLevelView();\n // ночь и освещение: камеру и прозрачность над игроком берёт у levelView\n const lighting = createLighting(undefined, { levelView });\n // разбор прогонов рамп: общий на все слои карты, живёт до её смены\n let runsCache = [];\n let runsGeneration = null;\n // имена типов поверхностей по индексу `surface_at`: живут до смены карты\n let surfaceNames = [];\n let surfaceGeneration = null;\n\n return {\n levelView,\n lighting,\n // «танк выстрелил»: эффект выстрела будит отдачу у танка стрелка\n shots: createShotEvents({\n segments: (x, y, dx, dy, range, level) =>\n core.shot_segments(x, y, dx, dy, range, level),\n }),\n // «взрыв»: эффект взрыва будит реакцию танков в его радиусе\n blasts: createBlastEvents(),\n mapDynamics: {\n // локальная точка тела → мировая в рендерном фрейме;\n // null — ключ неизвестен (карта сменилась, ящика больше нет)\n toWorld(key, localX, localY) {\n const point = core.map_dynamics_to_world(key, localX, localY);\n\n return point.length === 0 ? null : { x: point[0], y: point[1] };\n },\n },\n rampRuns: {\n // прогоны рамп этого уровня в МИРОВЫХ единицах; [] — карты нет,\n // она одноуровневая или рамп на уровне нет. Клин строит слой, в\n // чьём гриде лежат тайлы рампы, то есть уровень `from` — так же\n // движок раздаёт партам сами конфиги рамп.\n //\n // Разбор держится в кеше до смены карты: `core.ramp_runs()`\n // сериализует ВСЕ прогоны карты в строку через WASM-границу, а\n // спрашивает его каждый слой. Протухание ловит поколение карты\n // (`core.map_generation()` растёт на каждом `set_map`)\n forLevel(level) {\n const generation = core.map_generation();\n\n if (generation !== runsGeneration) {\n runsGeneration = generation;\n runsCache = JSON.parse(core.ramp_runs());\n }\n\n return runsCache.filter(run => run.from === level);\n },\n },\n // поверхности клеток из ядра: та же таблица, по которой физика\n // тормозит и разворачивает танк, — пыль, брызги и следы не могут\n // разойтись с тем, что чувствует гусеница\n surfaces: {\n // имя типа под мировой точкой уровня; null — нейтрально или карты нет\n kindAt(x, y, level) {\n const index = core.surface_at(x, y, level);\n\n if (index < 0) {\n return null;\n }\n\n const generation = core.map_generation();\n\n if (generation !== surfaceGeneration) {\n surfaceGeneration = generation;\n surfaceNames = JSON.parse(core.surface_types());\n }\n\n return surfaceNames[index] ?? null;\n },\n\n // единичный вектор стрелки клетки; null — у клетки нет направления\n dirAt(x, y, level) {\n return SURFACE_DIRS[core.surface_dir_at(x, y, level)] ?? null;\n },\n },\n };\n },\n\n // авторизация: модель танка пользователя — для реплик движения и выстрелов\n onAuth(core, authData) {\n core.set_model(authData.model);\n },\n\n // кадр панели: зеркало боезапаса/активного оружия для гейтов try_fire\n onPanel(core, panelData) {\n core.sync_panel(JSON.stringify(panelData));\n },\n\n // локальное действие игрока; возвращает JSON спавна выстрела либо null.\n // Гейты в ядре: предикт активен, свой танк жив, хватает боезапаса\n onLocalAction(core, action, name, now) {\n if (action !== 'down') {\n return null;\n }\n\n if (name === 'fire') {\n return core.try_fire(now) || null;\n }\n\n if (name === 'nextWeapon' || name === 'prevWeapon') {\n core.cycle_weapon(name === 'prevWeapon');\n }\n\n return null;\n },\n },\n};\n"],"names":["ENGINE_API_VERSION","ClientCore","ptr","ClientCoreFinalization","wasm","deferred1_0","deferred1_1","ret","getStringFromWasm0","x","y","flags","local_now","action","key_name","ptr0","passStringToWasm0","len0","WASM_VECTOR_LEN","ptr1","len1","back","data","deferred2_0","deferred2_1","passArray8ToWasm0","op","payload","v3","getArrayU8FromWasm0","v1","getArrayF32FromWasm0","key","local_x","local_y","v2","config_json","takeFromExternrefTable0","active","map_json","model","dx","dy","range","level","panel_json","__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","seeThrough","parallax","volume","shadow","tilt","tankLight","recoil","tankModel","blastJolt","tracer","muzzleFlash","surfaceFx","landing","animations","lighting","LEVEL_Z_STRIDE","levelZ","base","renderLevel","z","cameraCenter","parent","renderer","screen","offsetPoint","camera","k","applyParallax","target","baseScale","scaleX","scaleY","scale","cellOfPoint","world","step","cellOfEdge","tileGrid","map","tileAt","grid","worldX","worldY","tiles","col","row","tile","covers","test","point","ks","margin","offsets","coversPoint","tileSet","cellsCoverPoint","cellSet","innerRadius","cfg","smoothStep","edge0","edge1","t","seeThroughAlpha","viewLevel","viewX","viewY","distance","seeThroughTint","fragment","wgsl","createHoleFilter","holeUniforms","UniformGroup","Filter","GlProgram","defaultFilterVert","GpuProgram","setHoleUniforms","filter","centerX","centerY","radius","softness","minAlpha","uniforms","HOLE_EPSILON","createHole","advance","hole","wanted","rate","apply","stage","parallaxConfig","dispose","parseSpriteSheet","baseTexture","spriteSheetData","framesData","frameDef","index","width","height","sheetDataForPixi","spriteSheet","Spritesheet","bakeTileLayer","exclude","excluded","baked","mapWidth","mapHeight","tempContainer","Container","lenY","lenX","tileIndex","textureName","texture","sprite","Sprite","bakedTexture","Rectangle","buildRampLanes","runs","toCell","lanes","run","alongAxis","crossAxis","along0","along1","cross0","cross1","hasRails","mergeLanes","groups","lane","group","merged","b","current","batchedMesh","options","mesh","MeshSimple","WALL_EPSILON","MAX_WALL_QUADS","QUAD_INDICES","wallEdges","edges","rows","cols","max","has","ny","nx","buildVolumeWalls","shear","sideTint","textures","tileRepeats","k0","k1","kSort","cellWorld","slices","byTile","edge","tileEdges","first","chunk","quads","uvs","indices","normals","centers","q","x0","y0","x1","y1","ax","ay","bx","by","ua","ub","v","i","atB","top","slot","updateWallMesh","slice","bleedPx","vertices","rise","bleedWorld","dist","quad","span","orderWallMesh","facing","changed","front","pass","buildRampMeshes","segmentsPerCell","meshes","bakedWidth","bakedHeight","perCell","cells","segments","along0Cell","rails","points","low","high","kBase","skirtUvs","heights","uvInset","progress","along","insetA","insetB","alongInset","buildRampSkirt","surface","endIndex","railFirst","railLast","railPoints","columns","skirtBase","skirtHeights","sourceOf","column","source","strip","last","updateHeightMesh","OCCLUDER_BASE_Z","NOTHING","disposeAssets","mapSprite","occluder","rampTexture","wallTextures","buildLayerAssets","spec","extrusion","buildExtrusion","assets","error","built","bakeWallTextures","volumeConfig","rampLanes","ramp","a","sink","repeats","owned","axis","updateSeeThrough","tick","Ticker","stepped","dt","updateLayerSeeThrough","updateOccluderSeeThrough","levelView","container","roofHides","roofHidesPlayer","above","advanceHole","applyHole","alpha","playerPoint","player","LIGHT_OVERLAY_BASE_Z","EMISSIVE_BASE_Z","LAMP_HEAD_BASE_Z","fnv1a","text","hash","mapKeyOf","cellCenter","cellsOfTiles","set","line","cellRuns","start","prev","projectLight","lightLevels","airborne","isOnScreen","screenX","screenY","reach","flicker","seed","timeMs","strength","noise","flashFactor","elapsedMs","durationMs","radialFalloff","lightStrength","light","intensity","cos","sin","across","halfWidth","selectLights","candidates","limit","hits","factor","gridKey","buildLightGrid","lights","cellSize","size","col0","col1","row0","row1","queryLightGrid","shadowWedge","lx","ly","cx","cy","tangent","angle","half","a0","a1","shaftSway","amount","rampWedgePolygon","alongX","project","sideA","sideB","castRay","ox","oy","maxDist","isBlocked","cellW","cellH","stepCol","stepRow","deltaX","deltaY","nextX","nextY","prevCol","prevRow","RAMP_CLEARANCE","rampBlocks","prevLane","clearance","firstHit","length","coneFan","rotation","alongMax","alongBack","acrossMax","rays","count","closed","j","clipped","fanUvs","sx","sy","rx","ry","fanIndices","open","createAnimationClock","ticker","lastTime","seconds","time","animationClock","quantizeTime","maxFps","animatedTileIds","defs","resolveFps","def","frameIndex","fps","buildAnimatedTiles","sheet","own","id","frame","updateAnimatedTiles","isVisible","destroyAnimatedTiles","ownedBy","items","layer","item","buildDecals","animated","decals","updateDecals","decal","destroyDecals","PULSE_HZ","DROPOUT_WINDOW","DROPOUT_DURATION","DROPOUT_CORE","DROPOUT_GLOW","splitMix32","cellSeed","unit","window","salt","clamp01","inDropout","dropouts","brightness","pulse","phase","BLUR_SPREAD_FACTOR","blurMargin","blurPadding","caches","neonKey","neonBlur","bake","blur","wrapper","label","Text","core","BlurFilter","glow","getNeonTextures","cache","entry","releaseNeonTextures","NeonSign","sign","roofAlpha","see","layerAnimationSpec","game","ids","hasAnimations","buildLayerAnimations","isAborted","state","onScreen","bounds","updateLayerAnimations","animationsConfig","destroyLayerAnimations","releases","release","MapLayer","dependencies","imageBase","Assets","isRoofLayer","releaseAnimations","disposeHole","Texture","roofs","lerp","clamp","min","degToRad","degrees","normalizeAngle","randomRange","BaseEffect","onCompleteCallback","DebrisEffect","onComplete","debris","spread","config","speed","deltaMs","damping","particle","M1_X","M1_Y","M1_ANGLE","M1_GUN_ROTATION","M1_VX","M1_VY","M1_ENGINE_LOAD","M1_CONDITION","M1_SIZE","M1_TEAM","M1_Z","M1_LEVEL","M1_VZ","M1_PITCH","M1_ROLL","W1_START_X","W1_START_Y","W1_END_X","W1_END_Y","W1_BODY_X","W1_BODY_Y","W1_WAS_HIT","W1_SHOOTER_ID","W1_START_LEVEL","W1_END_LEVEL","W1_ANCHOR","W2_X","W2_Y","W2_ANGLE","W2_SIZE","W2_TIME","W2_LEVEL","W2E_X","W2E_Y","W2E_RADIUS","W2E_LEVEL","C_X","C_Y","C_ANGLE","C_Z","C_LEVEL","C_STATE","STATE_INTACT","STATE_DAMAGED","STATE_DESTROYED","DESTROYED_LAYER","DEBRIS_LAYER","SCORCH_SCALE","MapObject","firstFrame","glints","lightingConfig","asset","center","hit","mask","body","local","url","promise","scorch","contentSize","halfW","halfH","effect","released","LEVEL_COLORS","levelColor","MAP_RADAR_BASE_Z","OTHER_LEVEL_ALPHA","MapRadar","_assets","Graphics","graphics","tileType","TANK_RADAR_BASE_Z","LEVEL_RING_RADIUS","LEVEL_RING_WIDTH","TankRadar","newCondition","teamId","needsVisualChange","tiltCorners","anchorX","anchorY","heading","pitch","roll","lift","u0","u1","v0","cosR","sinR","cosP","sinP","cosB","sinB","upU","upV","out","su","sv","u","up","h1","vp","h2","h","shift","tiltShade","lightDir","shading","dot","scaleTint","tint","channel","landingImpact","prevVz","vz","minImpact","fullImpact","recoilAmount","elapsed","duration","attack","recoilOffsets","gunRotation","gun","rock","TAU","SHAKE_FREQ_X","SHAKE_FREQ_Y","blastStrength","blastKick","hullLength","blast","rng","hop","blastJoltState","kick","envelope","wobble","hopping","shake","TURRET","PART_ORDER","signedArea","area","polygonNormal","w0","w1","rect","scalePolygon","createTankModel","tankModelConfig","faces","prism","bottom","part","topMaterial","sideMaterial","lower","upper","_","plan","barrel","brake","TURRET_PARTS","poseModel","gunKick","squash","cosG","sinG","heightScale","partOf","face","w","turn","normal","faceNormal","modelLean","levelHeight","maxLean","topHeight","gain","projectModel","lean","zScale","visibleFaces","projected","visible","polygon","faceShade","PLANAR_BY_PART","STRIP_BY_MATERIAL","isVertical","c","d","faceUVs","atlas","regions","norm","region","VERTEX","FRAGMENT","program","createTankModelShader","Shader","setTankModelTexture","shader","createTankModelMesh","vertexCount","triangleCount","positions","shades","writeUVs","corners","geometry","Geometry","Mesh","next","faceShades","order","f","vertex","cursor","convexHull","sorted","cross","shadowDrift","lightZ","wx","wy","shadowPolygons","drift","byPart","polygons","shadowed","pair","tankLightUniforms","ambient","diffuse","flat","createTankLightShader","setTankLightTextures","applyTankLight","values","SHADE_MIN","SHADE_MAX","TANK_BASE_Z","sharedModel","tankModelGeometry","modelLight","tiltConfig","tankLightConfig","MUZZLE_REACH","WRECK_SKEW","LEVEL_MAX","MIN_ENGINE_RATE","MAX_ENGINE_RATE","STRAIN_ENGINE_RATE","MIN_ENGINE_VOLUME_FACTOR","MAX_ENGINE_VOLUME_FACTOR","IDLE_WOBBLE_DEPTH","IDLE_WOBBLE_HZ","calculateEngineSoundParams","load","safeLoad","baseLoad","strainLoad","volumeFactor","shadowOffset","shadowConfig","Tank","context","PerspectiveMesh","liveTextures","gunAnchorData","BAKER_BASE_SIZE","engineConfig","waterConfig","wreckAtlas","impact","landingConfig","minVolume","fullSpeed","side","frontX","frontY","headlights","tankGlow","cone","levels","peak","recoilConfig","blastJoltConfig","wasHopping","bodyX","bodyY","created","anchor","squashY","baseTint","shade","dead","posed","AlphaFilter","groundZ","liftLevels","baseAlpha","BOMB_BASE_Z","Bomb","params","textureSize","renderFontSize","fontScale","remainingMs","ExplosionEffect","easeInQuad","coreProgress","pool","ParticlePool","Particle","REFERENCE_BLAST_RADIUS","BOUNDS_WIDTH","BOUNDS_HEIGHT","SmokeEffect","blastScale","boundsWidth","boundsHeight","ParticleContainer","grayLevel","colorVal","startScale","aspectX","aspectY","sway","ease","currentBaseScale","fadeP","FUNNEL_TO_BLAST_RATIO","FUNNEL_BASE_ALPHA","FUNNEL_SIZE_JITTER","FUNNEL_DURATION_MS","FUNNEL_FADE_IN_DURATION_MS","FUNNEL_FADE_DURATION_MS","SMOKE_DURATION_MS","FunnelEffect","funnelSprite","timeLeft","fadeIn","fadeOut","EXPLOSION_BASE_Z","FUNNEL_BASE_Z","ExplosionEffectController","SMOKE_BASE_Z","BOUNDS_PADDING","globalWindX","globalWindY","SMOKE_PARTICLE_BASE_SIZE","SMOKE_CONFIG","Smoke","prevCondition","numStreams","particlesPerStream","s","deltaTime","spawnRate","spawnInterval","gas","exhaustRate","exhaustInterval","oneMinusFriction","lifeProgress","currentSizeFactor","currentAlpha","currentScale","streamIndex","kind","isBurst","isExhaust","exhaust","lifetime","startSizeFactor","endSizeFactor","startAlpha","velocityMultiplier","backAngle","cosBack","sinBack","rightAngle","cosRight","sinRight","offsetSide","streamDisplacement","offsetBack","spawnX","spawnY","ejectionSpeed","sideSpeed","vx","vy","variance","TrackMark","initialAlpha","lifetimeFactor","TRACKS_BASE_Z","Tracks","tankHeight","host","mark","currentSpeed","rotationDiff","currentAngularSpeed","acceleration","absAcceleration","angularAcceleration","absAngularAcceleration","currentTime","shouldLeaveMark","trail","style","fade","sideOffsetX","sideOffsetY","backwardComponentX","backwardComponentY","markX","markY","DUST_BASE_Z","DUST_PARTICLE_BASE_SIZE","DUST_CONFIG","DUST_STYLE","SURFACE_EMITTERS","BOOST_TAIL_SPREAD","isSameBoost","cell","dir","Dust","landingSound","prevX","prevY","wasAlive","jumped","respawned","sideAngle","lateral","sizeMultiplier","strain","interval","frictionFactor","tracerSpan","totalDist","trailLength","trailShare","head","longest","maxTrail","TracerEffect","startX","startY","endX","endY","tracerConfig","tail","color","coreColor","coreWidth","glowWidth","glowAlpha","steps","pieces","from","to","share","parts","piece","tracerPieces","total","endLevel","end","list","segment","clip","mid","owner","appendPiece","cut","ImpactEffect","impactDirectionX","impactDirectionY","baseAngleRad","halfSpreadRad","particleAngle","initialSpeed","particleData","deltaSeconds","activeParticlesCount","pData","dragForceMagnitude","speedReductionFactor","currentLifetimeProgress","timeIntoFade","quickFadeDuration","fadeDuration","pSprite","rollMuzzleFlash","forward","sides","muzzleFlashShape","dirX","dirY","life","baseAngle","spike","MuzzleFlashEffect","muzzleFlashConfig","SHOT_BASE_Z","ShotEffectController","night","tracerRunning","flashRunning","muzzle","ratio","impactX","impactY","Map","ShotEffect","blurredCircleTexture","quality","funnelTexture","baseRadius","irregularity","numPoints","colorFill","colorRim","rimWidth","variants","variantCount","canvasSize","variant","path","r","normalColor","FLAT_NORMAL","bevelFrame","g","l","fills","bevelRing","inner","fill","_size","_width","_height","baseColor","hullRect","deck","deckBevel","tracks","turret","turretInner","hatch","outline","OUTLINE_SHADE","drawTankBody","track","drawTankGun","thinEdge","TANK_ART","tankTexture","colors","drawBody","drawGun","drawBodyNormal","drawGunNormal","cylinder","stroke","draw","boundsFrame","createLiveTankTextures","bodyFrame","gunFrame","gunAnchor","createDestroyedTankTexture","destroyedContainer","destroyedBody","destroyedGun","bodyColorDark","bodyColorDarker","gunColorDark","damageColor","edgeColor","gunBasePoints","barrelPoints","destroyedBounds","destroyedTexture","destroyedNormal","destroyed","PAD","drawTrackSide","drawCylinderSide","tankModelTexture","burnt","probe","gunBounds","gunW","gunH","rowY","trackSide","barrelSide","brakeSide","bodyArt","gunArt","strips","soot","tankRadarTexture","borderWidth","crossSize","crossThickness","fullSize","destroyedColor","createLiveTankTexture","halfSize","bombTexture","colorOuter","colorInner","trackMarkTexture","tankShadowTexture","textureWidth","textureHeight","scorchTexture","coreRatio","blob","jitter","debrisTexture","middle","drawRadialRings","rings","alphaAt","outer","lightRadialTexture","headlightConeTexture","apexX","apexY","h0","lampHeadTexture","halo","coreShare","glintTexture","seededRandom","lightShaftTexture","random","cos0","sin0","cos1","sin1","r0","r1","bakers","isNodeCore","wasmUrl","loadNodeCore","createLevelView","deps","warnNoCamera","position","diagnostics","WHITE","LevelLightMap","resolution","roof","layoutPool","layoutFans","shadows","masked","overlay","MeshGeometry","fan","shape","topology","DEFAULT_AMBIENT","TEXTURE_KEYS","SHAFT_SWAY","BOUNCE_PULL","lightArea","createLighting","enabled","emissive","flashes","casters","glintTick","glintCount","mapKey","counts","masks","roofMasks","tops","ramps","masksDirty","layoutKey","fans","blockersVersion","seeThroughCfg","levelContainer","store","name","emissiveContainer","lampHeadContainer","ensureLampHeads","lamp","initMap","lightingCfg","mapScale","releaseMapResources","levelMap","allLevelMaps","clear","unionOf","byOwner","union","keys","syncLevelMap","registry","topGroupsOf","byVolume","syncBlockers","inside","obstaclesFor","indexOf","laneAt","home","occlusionCfg","occlusionOf","occlusion","cached","result","reachesPoint","syncLevels","dirty","roofUnion","roofKeys","attachToStage","itemOf","bounceOf","bounce","occludeItem","layoutLights","now","perLevel","perRamp","rampTargets","spill","push","feet","bounces","flash","shafts","layoutShafts","shadowsOf","shaftsCfg","worldReach","near","caster","wedge","updateHoles","below","under","updateEmissive","isNight","candidatesAt","onLevel","nextStage","nextRenderer","rising","patch","previous","record","handle","createShotEvents","handlers","rawId","handler","createBlastEvents","listeners","callback","SURFACE_DIRS","clientConfigJson","node","init","styles","runsCache","runsGeneration","surfaceNames","surfaceGeneration","localX","localY","generation","authData","panelData"],"mappings":"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","x_google_ignoreList":[0,24]}
|