--- type: tech-risks work_item_id: ET-013 title: "Технические риски — ET-013: Zoom-aware paint для terrain-слоёв на z9-z11" version: 1 status: approved created_at: 2026-06-04 authors: - "agent:architect" --- # Технические риски — ET-013 Технические риски этапа калибровки клиентского paint для растровых terrain-слоёв. Бизнес-риски — в BRD §5 (R-1..R-11). Шкала: вероятность (Н/С/В) × влияние (Н/С/В). ## R-T-1 — Тайлы hillshade z9-z11 отсутствуют на test-среде - **Описание:** BRD §2.1 утверждает, что PH-6 нарезала hillshade z8-z14. Если реальная нарезка на mva154 отличается (например, z10-z14), при включении hillshade на z9 пользователь увидит 404-шахматную доску, а в DevTools — череду failed requests. - **Вероятность / Влияние:** Н / В. - **Митигация:** - **Архитектурное решение (ADR-017 §U-A):** pre-deploy smoke `curl -I` на 3 разных тайла (z9/z10/z11) над ЦФО — обязателен перед merge (`07-infra-requirements.md` §6.2 шаг 1, AC-19). - **Эскалация:** при 404 — задача останавливается, открывается PH-6 follow-up «hillshade-z9-z14 backfill». ET-013 не мержится. - **Acceptance гейт:** AC-19 в `03-acceptance-criteria.md`. ## R-T-2 — `raster-contrast` 0.40 даёт «пересвет» / черноту на тёмных тайлах - **Описание:** На z9-z11 hillshade-тайлы из тёмных лесных зон (низкая средняя яркость PNG) при `raster-contrast: 0.40` могут «провалиться в черноту» — пиксели clipping'уются к 0, тени превращаются в чёрные кляксы, теряя информацию. - **Вероятность / Влияние:** С / С. - **Митигация:** - **Архитектурное решение (ADR-017 §C-A):** stops контраста подобраны консервативно (0.40 на z9 → быстрый спад к 0 на z14); значения калибруются по результатам визуальной приёмки. - **Acceptance гейт:** TC-UI-04-Z10-Q (BRD R-1, AC-07..AC-09) — оператор смотрит скриншоты на холмистом районе. При «пересвете» — снижаем contrast в stops до 0.25-0.30 итеративно. - **Принцип:** stops живут в коде, правка — одна строка, не ADR. ## R-T-3 — `'nearest'`-resampling на overzoom z12-z14 даёт пикселизацию - **Описание:** При overzoom (когда MapLibre тянет тайл z14 для z15-z18) `'nearest'`-resampling показывает крупные квадраты вместо плавных теней. Это особенно заметно на hillshade. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение (ADR-017 §R-A):** MapLibre не поддерживает `interpolate` для `raster-resampling`, поэтому глобальное `'nearest'` — единственный простой путь. Альтернатива (два layer'а) отклонена как overkill. - **Контекст использования:** на z12+ пользователь обычно отключает hillshade в пользу подложки (для города нужны улицы, а не тени). Это вторичный сценарий. - **Acceptance гейт:** AC-10 (TC-UI-06-Z14-Q) — оператор подтверждает «не темнее и не контрастнее, чем до ET-013» (т.к. opacity и contrast уже вернулись к baseline). Пикселизация допустима, если не нарушает читаемость. - **Fallback:** если визуально неприемлемо — отдельным минорным патчем вводится второй layer hillshade с `'linear'` для z12+, переключаемый по `getZoom()`. Это **не часть ET-013**. ## R-T-4 — Сетевой трафик растёт > +35% при активной zoom-сессии - **Описание:** Снижение UI-минзума hillshade с 10 до 9 добавляет +1 zoom-уровень. На активной сессии (пользователь крутит зум z8→z11→z8→z11 много раз) первая загрузка z9 тайлов даёт заметную дельту трафика. BRD M-10 = ≤ +35%. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** `Cache-Control: public, max-age=31536000, immutable` (`src/api/main.py:1252`) + браузерный кэш + nginx-кэш. После первого визита повторные запросы дают 304 If-Modified-Since (или вовсе не доходят до сервера — browser hits memory cache). - **Acceptance гейт:** AC-21 в `03-acceptance-criteria.md` — network-traffic ≤ 135% от baseline на сценарии zoom-петли z=8→9→10→11→10→9→8. - **Мониторинг:** см. `07-infra-requirements.md` §7.1 — первая неделя оператор смотрит `nginx access.log` на аномалии. ## R-T-5 — На тёмной теме (ET-007 `theme-dark`) hillshade с opacity 0.65 + contrast 0.40 сливается в кашу - **Описание:** Тёмная подложка + полупрозрачный тёмный hillshade с усиленным контрастом → визуально неразличимая «грязь». BRD R-2. - **Вероятность / Влияние:** С / С. - **Митигация:** - **Архитектурное решение (ADR-017 §T-A):** в MVP — один paint для всех тем. Если AC-11 проваливается — открывается ADR-018 «theme-specific terrain paint» с отдельной таблицей stops для `theme-dark` (через подписку на `theme-change` event и `setPaintProperty`). - **Acceptance гейт:** AC-11 (TC-UI-09-Z10-DARK-Q) — оператор проверяет на dark + holmistom районе. Если провал — фиксируется в `13-test-report.md` и открывается follow-up. - **Принцип:** не плодим сложность пока не доказана необходимость. ## R-T-6 — На спутниковой подложке (ET-007) hillshade «глушит» снимок - **Описание:** Esri World Imagery уже содержит визуальный рельеф (тени снимков). Поверх него полупрозрачный hillshade с opacity 0.65 → снимок превращается в «серую плёнку», пользователь теряет цвета поверхности. BRD R-3. - **Вероятность / Влияние:** Н / С. - **Митигация:** - **Архитектурное решение (ADR-017 §T-A):** UX-нота: на спутнике пользователь обычно отключает hillshade — снимок и так «показывает» рельеф. Если AC-12 проваливается — open ADR-018 с правилом «на satellite layer'е opacity hillshade = старые 0.40» (через подписку на `applyBaseLayer`). - **Acceptance гейт:** AC-12 (TC-UI-08-Z10-SAT-Q). - **Принцип:** не плодим сложность пока не доказана необходимость. ## R-T-7 — TRI с opacity 0.85 на z9-z11 перекрывает грунтовки/тропы - **Описание:** Слой `trails-*` (грунтовки, тропы) рисуется тонкими линиями. Если TRI поднять до opacity 0.85, цветные пятна категориальной палитры могут визуально «убить» линии трасс. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** существующая логика в `applyTerrainLayer` (`src/web/app.js:3337-3339`) вставляет terrain-слои **перед** первым `trails-*` или `poi-*` слоем — z-order корректный. TRI рисуется ПОД линиями трасс, не НАД. - **Тесты:** AC-07..AC-09 (визуальная приёмка на холмистом районе с грунтовками). ## R-T-8 — MapLibre 4.7.0 не поддерживает `interpolate` для `raster-contrast` - **Описание:** Если документация MapLibre врёт или версия 4.7.0 имеет regression на `raster-contrast` с zoom-выражением, paint не применится, в DevTools будет warning, hillshade покажется с default contrast = 0. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение (NFR-04 в TRZ §4):** MapLibre 4.7.0 официально поддерживает `interpolate` для всех raster paint properties, кроме `raster-resampling`. Проверка — публичная документация maplibre.org. - **Smoke-проверка после деплоя:** DevTools `window._map.getPaintProperty('terrain-hillshade', 'raster-contrast')` должен вернуть массив `['interpolate', ...]` (AC-04). - **Fallback:** если фактически не работает — заменить на `case`-step выражение (грубое stepwise) или просто оставить числовую константу `0.30` для z9-z11 (одно значение, без zoom-плавности). ## R-T-9 — Регрессия z8: после правки TRI_PAINT на z8 перепады выглядят иначе - **Описание:** В новой `TRI_PAINT` для z=8 стоит `0.70` — точно как было. Но если при правке нечаянно поставить `8, 0.75` (или пропустить стоп для z8 — тогда `interpolate` между `7→0.65` и `9→0.80` даст на z8 значение ~0.72), регрессия z8 нарушится. - **Вероятность / Влияние:** С / С. - **Митигация:** - **Архитектурное решение (ADR-017 §O-B):** в `TRI_PAINT` явно указан стоп `8, 0.70` (не полагаемся на интерполяцию между соседними стопами). - **Acceptance гейт:** AC-06 (TC-UI-02-Z8-REGR) — скриншот сравнивается с до-ET-013 baseline. - **Unit-тест:** REQ-F-13 проверяет наличие `8, 0.70` в исходнике `TRI_PAINT` через regex. ## R-T-10 — Регрессия z14: hillshade «не возвращается» к baseline - **Описание:** Если stops `HILLSHADE_PAINT` не закрываются явным стопом на z14 (например, `14, 0.40, 14, 0.00`), MapLibre экстраполирует за пределами последнего стопа, и на z14-z15 hillshade может остаться «перегретым» (opacity 0.55, contrast 0.20). - **Вероятность / Влияние:** Н / С. - **Митигация:** - **Архитектурное решение (ADR-017 §O-B / §C-A):** `interpolate` у MapLibre clamp'ит значения за пределами крайних stops (clamping behavior). Явные стопы `14, 0.40` для opacity и `14, 0.00` для contrast обеспечивают регрессию z14. - **Acceptance гейт:** AC-10 (TC-UI-06-Z14-Q) — скриншот сравнивается с до-ET-013 baseline. - **Unit-тест:** REQ-F-13 проверяет наличие `14, 0.40` и `14, 0` в исходнике `HILLSHADE_PAINT`. ## R-T-11 — `applyTerrainLayer` ломает обратную совместимость - **Описание:** При расширении сигнатуры `opacity → opacityOrPaint: number | object` существующая логика (если есть где-то ещё в `src/web/`) может сломаться при передаче числа. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение (ADR-017 §A-A):** внутри функции — нормализация `(typeof opacityOrPaint === 'number') ? {…linear…} : opacityOrPaint`. Старый контракт работает без изменений. - **Acceptance гейт:** AC-22, UT-COMPAT-01 (REQ-F-14) — статический grep по `src/web/*.js`: подтверждает, что вызовов `applyTerrainLayer` только два (оба в `onTerrainCheckbox`), оба переведены на новые константы. - **Принцип:** unit-тест на нормализацию + явный комментарий `// ET-013: backwards-compat shim` в коде. ## R-T-12 — Старый клиент (закэшированный в браузере) не подхватывает новый `app.js` - **Описание:** Пользователь с открытой вкладкой неделю назад имеет закэшированный старый `app.js` со старым `applyTerrainLayer` без paint-нормализации. При reload браузер должен дёрнуть свежий `app.js`. Service worker — не настроен в MVP (PH-9 не реализована). - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение:** `src/web/index.html` загружает `app.js` напрямую (без SW). nginx + `Cache-Control` на `*.js` — стандартные (не immutable; If-Modified-Since работает). При reload браузер делает conditional GET → 200 (если файл изменился) или 304. - **Backwards compat:** старый клиент с `minzoom=10` для hillshade продолжает работать; он просто не запрашивает hillshade z=9. Никаких 4xx-ответов нет (REQ-F-18 — контракт неизменен). - **Митигация в долгую:** PWA / SW (PH-9) введёт правильную inval-стратегию. ## R-T-13 — Hint «Зум 10+» забыт в HTML → расхождение с фактическим порогом - **Описание:** В `src/web/index.html` строка `Зум 10+`. Если правка REQ-F-10 потеряется (например, мердж-конфликт), у пользователя на z<9 будет hint «Зум 10+», который противоречит фактическому порогу 9. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение (REQ-F-10):** в HTML текст явно меняется на «Зум 9+». Это атомарная правка, проверяется grep'ом. - **Acceptance гейт:** AC-01 — проверяет `«Зум 9+»` в исходнике `index.html`. AC-03 — проверяет `hint.style.display === 'none'` на z=9. - **Unit-тест:** REQ-F-14 (UT-REG-02) — grep по строке `zoom < 9` в `app.js` и `«Зум 9+»` в `index.html`. ## R-T-14 — `nearest`-resampling на TRI делает «зернистую» картинку, пользователю не нравится - **Описание:** TRI — категориальная палитра (5 уровней). На `'nearest'` ясно видны 30-метровые SRTM-клетки, картинка выглядит «зернистой». BRD R-10 классифицирует это как «желаемое поведение» (показ «реальных» границ перепадов), но возможен субъективный негативный отзыв. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение (ADR-017 §R-A):** на TRI «зернистость» — спецификация. Категориальные данные требуют резких границ, `'linear'` их размывает. - **Fallback:** если AC-07..AC-09 проваливаются с пометкой «зернисто» — откатывается F-09 (TRI → `'linear'`), hillshade остаётся на `'nearest'`. Это одна строка кода в `TRI_PAINT`. - **Acceptance гейт:** AC-07..AC-09 — оператор подтверждает качественную приёмку. ## R-T-15 — Performance деградация из-за `interpolate` в paint - **Описание:** Если MapLibre на каждом zoom-tick пересчитывает `interpolate`-выражение без кэширования, на слабых устройствах (mobile, low-end) может появиться jank при зуме. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение (NFR-01 в TRZ §4):** MapLibre кэширует скомпилированные `interpolate`-выражения; вычисление при смене zoom — < 1 мс на frame. - **Эмпирически:** существующие слои `gps_tracks.js`, `trails-*` уже используют `interpolate` по zoom без жалоб. - **Тест:** AC-13 (TC-UI-07-Z9-MOBILE) — Playwright mobile viewport, проверяет работоспособность; не measure'ит FPS, но регрессия проявится визуально. ## R-T-16 — Pre-deploy smoke не покрывает все регионы (тайлы z9 могут отсутствовать вне ЦФО) - **Описание:** Pre-deploy `curl` проверяет 3 тайла над ЦФО. Если нарезка z9 ограничена только ЦФО, пользователь над Уралом / Алтаем увидит 404. По BRD §6 это OOS (MVP покрывает только ЦФО), но риск стоит явно зафиксировать. - **Вероятность / Влияние:** С / Н. - **Митигация:** - **Архитектурное решение:** в MVP test-среда обслуживает ЦФО (`centralfederal.sqlite`). Тайлы вне ЦФО — out of scope. - **Принцип:** если пользователь панорамирует за пределы ЦФО, на z9-z14 он увидит «шахматку» из 404 и для terrain, и для trails — это известная граница MVP, не баг ET-013. - **Документация:** зафиксировать в `14-deploy-log.md` как «known limitation». ## R-T-17 — `eslint` падает на новых `interpolate`-массивах - **Описание:** Если в проекте настроен `eslint` с правилами `no-magic-numbers` или жёстким `max-len`, длинные массивы `['interpolate', ['linear'], ['zoom'], 9, 0.65, …]` могут завалить линтер. - **Вероятность / Влияние:** Н / Н. - **Митигация:** - **Архитектурное решение:** существующие JS-файлы (`gps_tracks.js`) уже используют похожие массивы — значит, eslint их пропускает. - **Acceptance гейт:** AC-18 (`make lint` зелёный). При проблеме — добавить `// eslint-disable-next-line` точечно. ## R-T-18 — Калибровка stops «не угадывает» желаемую читаемость с первого раза - **Описание:** Значения `9→0.65, 10→0.60, 11→0.55` для hillshade выбраны архитектором по эстимейту из BRD. На реальных данных оператор может сказать «на z9 ещё мало, на z10 уже слишком темно». Это **итеративный процесс**, не «упало». - **Вероятность / Влияние:** В / Н. - **Митигация:** - **Архитектурное решение:** stops живут в JS-константах `HILLSHADE_PAINT` / `TRI_PAINT`. Правка одной цифры — одна строка кода + новый коммит. Не требует архитектурного re-decide (ADR-017 §«Технический долг» TD-1). - **Процесс:** после первого деплоя — фикс stops по фидбеку оператора без новой задачи. Учитывать в bandwidth-плане до закрытия ET-013. - **Гейт:** AC-07..AC-09 — качественные, оператор-driven. Они и есть «точка калибровки». ## Сводная таблица | # | Риск | Вер | Влиян | Митигация (тип) | |-------|--------------------------------------------------------------------|-----|-------|--------------------------------------------------| | R-T-1 | Тайлы z9-z11 отсутствуют | Н | В | Pre-deploy smoke + AC-19; STOP на 404 | | R-T-2 | `raster-contrast` 0.40 — пересвет/чернота | С | С | Итеративная калибровка stops; AC-07..AC-09 | | R-T-3 | `'nearest'` пикселизация на z12+ | С | Н | Принимается; fallback — двойной layer | | R-T-4 | Трафик +35% превышает гейт M-10 | Н | Н | `immutable` кэш; AC-21 | | R-T-5 | Hillshade на тёмной теме — каша | С | С | AC-11; follow-up ADR-018 при провале | | R-T-6 | Hillshade «глушит» спутник | Н | С | AC-12; follow-up ADR-018 при провале | | R-T-7 | TRI 0.85 перекрывает trails | Н | Н | Existing z-order (terrain ПОД trails) | | R-T-8 | MapLibre 4.7.0 не поддерживает interpolate для raster-contrast | Н | Н | Документация подтверждает; fallback — case-step | | R-T-9 | Регрессия z8 TRI | С | С | Явный стоп `8, 0.70`; AC-06; unit-тест | | R-T-10| Регрессия z14 hillshade | Н | С | Явные стопы `14, 0.40` и `14, 0`; AC-10 | | R-T-11| `applyTerrainLayer` обратная совместимость | Н | Н | Нормализация внутри функции; UT-COMPAT-01 | | R-T-12| Старый клиент в кэше браузера | С | Н | Backwards-compat контракта | | R-T-13| Hint «Зум 10+» забыт | С | Н | grep-проверка + AC-01 | | R-T-14| TRI `'nearest'` — зернисто | С | Н | Specified behavior; fallback — откат F-09 | | R-T-15| `interpolate` deg performance | Н | Н | MapLibre кэширует expr; NFR-01 | | R-T-16| Pre-deploy smoke ≠ покрытие региона | С | Н | Known MVP limitation; deploy-log | | R-T-17| eslint падает на длинных массивах | Н | Н | Существующий код уже использует такие массивы | | R-T-18| Stops не угадывают с первого раза | В | Н | Итеративная калибровка; AC-07..AC-09 — qualitative | ## Связанные документы - `01-brd.md` §5 Бизнес-риски R-1..R-11 (часть пересекается) - `02-trz.md` §3 REQ-F-04..REQ-F-15 (paint, тесты), §4 NFR-01..NFR-07 - `06-adr/ADR-017-zoom-aware-terrain-paint.md` §«Решение», §«Последствия», §«Технический долг» - `07-infra-requirements.md` §3 (network), §6 (deploy procedure), §7 (мониторинг) - `08-data-requirements.md` §3.3 (pre-deploy validation), §5 (API contracts) - `03-acceptance-criteria.md` AC-01..AC-22 (все гейты)