Files
enduro-trails/docs/work-items/ET-013/10-tech-risks.md
claude-bot 6b88bcee28
Some checks failed
CI / lint (push) Successful in 5s
CI / test (push) Failing after 5s
CI / build (push) Has been skipped
architect(ET): auto-commit from architect run_id=79
2026-06-04 09:40:50 +00:00

358 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` строка
`<span id="terrain-hillshade-hint">Зум 10+</span>`. Если правка
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 (все гейты)