analyst(ET): auto-commit from analyst run_id=78
This commit is contained in:
232
docs/work-items/ET-013/01-brd.md
Normal file
232
docs/work-items/ET-013/01-brd.md
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
type: brd
|
||||
work_item_id: ET-013
|
||||
title: "BRD: Сохранить выразительность перепадов высот на z9-z11"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
related:
|
||||
- "PH-6.terrain"
|
||||
---
|
||||
|
||||
# BRD — ET-013: Сохранить выразительность перепадов высот на z9-z11
|
||||
|
||||
## 1. Цель
|
||||
|
||||
На зумах **z9-z11** перепады высот должны читаться визуально
|
||||
сопоставимо с z8: пользователь видит «где холмы, где равнина»,
|
||||
а не однородную засветку.
|
||||
|
||||
Сейчас при увеличении зума с z8 (где перепады бросаются в глаза
|
||||
через слой «Перепады»/TRI и общий цветовой контраст) до z9-z11
|
||||
происходит резкая потеря выразительности:
|
||||
|
||||
- **z8** — слой «Перепады» (TRI) хорошо читается: крупные пятна
|
||||
«шершавости» рельефа покрывают значимую долю кадра, базовая
|
||||
подложка остаётся видна, перепады бросаются в глаза.
|
||||
- **z9** — кнопка «Тени рельефа» (hillshade) **disabled**
|
||||
(UI-минзум = 10), TRI ещё работает, но визуально пятна
|
||||
становятся мельче и контраст слабее.
|
||||
- **z10-z11** — hillshade включается, но его `opacity=0.40` и
|
||||
отсутствие усиления контраста делают теневой рельеф «бледной
|
||||
плёнкой» поверх подложки; TRI не компенсирует, потому что
|
||||
его `opacity=0.70` рассчитано на z5-z8.
|
||||
|
||||
ET-013 = **скалировать paint-параметры (opacity, contrast,
|
||||
resampling) hillshade и TRI по зуму** так, чтобы на z9-z11
|
||||
рельеф читался сопоставимо с z8, без перегенерации растровых
|
||||
тайлов и без новых данных.
|
||||
|
||||
## 2. Контекст
|
||||
|
||||
### 2.1 Текущая реализация (после PH-6)
|
||||
|
||||
**Источники тайлов** (`src/api/main.py:1240`):
|
||||
- `/terrain/hillshade/{z}/{x}/{y}.png` — теневой рельеф.
|
||||
- `/terrain/tri/{z}/{x}/{y}.png` — Terrain Ruggedness Index («Перепады»).
|
||||
- `/terrain/hypso/{z}/{x}/{y}.png` — гипсометрия (на текущий
|
||||
момент в UI не подключён; вне scope ET-013).
|
||||
|
||||
По PH-6 BRD тайлы нарезаны **z8-z14** (PNG 256×256), сгенерированы
|
||||
из SRTM 30м со следующими параметрами:
|
||||
- hillshade: azimuth 315°, altitude 45°, **z-factor 1.5**;
|
||||
- TRI: классификация (flat / nearly flat / slightly rugged /
|
||||
rugged / very rugged), цветовая шкала.
|
||||
|
||||
**Клиентский рендеринг** (`src/web/app.js`):
|
||||
|
||||
```js
|
||||
// Строка ~2782-2783:
|
||||
applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png',
|
||||
hillshadeChecked, 0.40, 10, 15);
|
||||
applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png',
|
||||
triChecked, 0.70, 5, 15);
|
||||
```
|
||||
|
||||
`applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom)` (строка 3316):
|
||||
- создаёт `raster` source с `tileSize: 256`, `scheme: 'tms'`,
|
||||
`minzoom`, `maxzoom`;
|
||||
- добавляет `raster` layer с paint `{raster-opacity, raster-resampling: 'linear'}`;
|
||||
- никаких zoom-tier выражений: opacity — **константа**.
|
||||
|
||||
**UI-минзум hillshade** (`src/web/app.js:3359`):
|
||||
```js
|
||||
function updateHillshadeAvailability() {
|
||||
const zoom = map.getZoom();
|
||||
if (zoom < 10) { cb.disabled = true; hint.style.display = 'inline'; ... }
|
||||
}
|
||||
```
|
||||
То есть на z9 чекбокс «Тени рельефа» неактивен и видна подсказка
|
||||
«Зум 10+». На диске тайл z9 есть (нарезка z8-14), но клиент его
|
||||
не запрашивает.
|
||||
|
||||
### 2.2 Ответы на open questions из бизнес-запроса
|
||||
|
||||
| Вопрос | Ответ |
|
||||
|---|---|
|
||||
| Чем рисуется рельеф? | Двумя независимыми raster-слоями: **hillshade** (PNG, z8-14 на диске, z10-15 в UI) и **TRI/«Перепады»** (PNG, z8-14 на диске, z5-15 в UI). Гипсометрия в UI сейчас не подключена. |
|
||||
| Где задаётся стиль по зумам? | `src/web/app.js:2782-2783` (вызовы `applyTerrainLayer` с константой opacity), `src/web/app.js:3316-3357` (создание raster-слоя), `src/web/app.js:3359-3377` (UI-минзум hillshade). Никаких zoom-tier выражений нет — opacity скаляр. |
|
||||
| До какого зума нарезаны тайлы? | По PH-6 BRD: **z8-z14**. На z15 на клиенте работает overzoom MapLibre (maxzoom source < maxzoom layer). Для ET-013 ключевое: на z9-z11 тайлы **есть на диске** — проблема исключительно в рендеринге. |
|
||||
| Хватает ли разрешения SRTM 30м на z9-z11? | Да. На z9 1 пиксель тайла ≈ 300м, на z10 ≈ 150м, на z11 ≈ 75м — везде есть запас относительно 30м SRTM. Перепады «теряются» не из-за разрешения данных, а из-за низкого контраста при рендере + отключённого hillshade на z9. |
|
||||
| Нужен ли отдельный стиль для крупных зумов? | **Нет**, отдельный layer не нужен. Достаточно: (а) снизить UI-минзум hillshade до z9; (б) перевести `raster-opacity` и `raster-contrast` в zoom-aware `interpolate`-выражения; (в) на крупных зумах переключить `raster-resampling` на `nearest`, чтобы перепады были резкими. |
|
||||
|
||||
### 2.3 Почему это бизнес-важно
|
||||
|
||||
- **UX expectation**: пользователь зумит карту чтобы детальнее
|
||||
посмотреть рельеф — а получает обратное: «было видно — стало
|
||||
плоско». Это контр-интуитивно и снижает доверие к слою.
|
||||
- **Целевая задача продукта** (эндуро-планирование): на z9-z11
|
||||
пользователь оценивает «насколько холмистая зона между двумя
|
||||
точками маршрута» — именно этот масштаб ключевой для выбора
|
||||
направления. Сейчас на этом масштабе слой работает плохо.
|
||||
- **Низкозатратное исправление**: данные есть, тайлы есть,
|
||||
логика рендера тривиально дополняется zoom-tier выражениями.
|
||||
Полезность/стоимость очень высокая.
|
||||
|
||||
### 2.4 Что НЕ делаем (обоснование)
|
||||
|
||||
| Альтернатива | Решение | Причина |
|
||||
|---|---|---|
|
||||
| Перегенерировать hillshade с z-factor 2.5-3.0 для z9-z14 | **Out of scope.** | Требует доступа к infra-pipeline SRTM, пересборки и редеплоя растровых тайлов. Если frontend-калибровки (F-02..F-05) недостаточно — отдельный work item «hillshade-rerender-z9-z14». |
|
||||
| Добавить векторные горизонтали (contours) | **Out of scope.** | Контуров в стэке нет. Это новая фича уровня PH-6.5, требует pipeline на отдельных vector tiles. |
|
||||
| Перейти на MapLibre `hillshade` layer (raster-dem) | **Out of scope.** | Требует поднять DEM в формате Terrarium/Mapbox-RGB. Это смена архитектуры рельефа. |
|
||||
| Multidirectional hillshade (4 азимута) | **Out of scope.** | Требует пересборки тайлов и комбинирования; см. строку 1. |
|
||||
| Подключить гипсометрию в UI на z9-z11 | **Out of scope.** | Hypso тайлы есть на диске, но UI не имеет переключателя — отдельная задача. |
|
||||
| Менять PH-6 параметры hillshade (azimuth/altitude) | **Out of scope.** | Это калибровка генератора, не клиентская проблема. |
|
||||
|
||||
## 3. Scope
|
||||
|
||||
### In scope
|
||||
|
||||
| # | Функция |
|
||||
| ----- | ---------------------------------------------------------------------------------------------------- |
|
||||
| F-01 | Понизить UI-минзум hillshade с 10 до **9** в `updateHillshadeAvailability` (тайлы z9 есть на диске). |
|
||||
| F-02 | Понизить `minzoom` источника `terrain-hillshade-source` с 10 до 9 (через изменение вызова `applyTerrainLayer`). |
|
||||
| F-03 | Опционально: обновить UI-hint «Зум 10+» → «Зум 9+» в `#terrain-hillshade-hint`. |
|
||||
| F-04 | Расширить `applyTerrainLayer` так, чтобы параметр `opacity` мог быть либо числом (текущий контракт), либо MapLibre `interpolate`-выражением. Никаких новых публичных функций. |
|
||||
| F-05 | Для hillshade использовать `raster-opacity` zoom-aware: 9→0.65, 10→0.60, 11→0.55, 12→0.50, 14→0.40. Цель: компенсировать «бледность» теней на z9-z11. |
|
||||
| F-06 | Для hillshade добавить `raster-contrast` zoom-aware: 9→0.40, 10→0.35, 11→0.30, 12→0.15, 14→0.00. Цель: подчеркнуть перепады без перегенерации. |
|
||||
| F-07 | Для hillshade установить `raster-resampling: 'nearest'` на z9-z11 (т.е. везде, где `raster-resampling` не игнорируется). Цель: резкие края перепадов вместо размытия. Сейчас стоит `'linear'`. Замечание: MapLibre не поддерживает интерполяцию `raster-resampling` по зуму, поэтому компромисс — глобально `'nearest'` для hillshade на всех зумах ≥ 9. На z12+ это допустимо (текстура остаётся читаемой при overzoom). |
|
||||
| F-08 | Для TRI («Перепады») использовать `raster-opacity` zoom-aware: 5→0.55, 7→0.65, 8→0.70 (как сейчас), 9→0.80, 10→0.85, 11→0.85, 12→0.75, 15→0.70. Цель: усилить TRI ровно на z9-z11 (как компенсацию за рывок hillshade), не трогая z8 и не превращая карту в кашу на z5-z7. |
|
||||
| F-09 | Для TRI установить `raster-resampling: 'nearest'`. TRI — категориальная классификация (5 уровней), линейный ресемпл размывает границы классов. Цель: резкие границы «спокойно/шероховато». |
|
||||
| F-10 | UI: контракт переключателей «Тени рельефа» / «Перепады» в `#terrain-popup` не меняется. Чекбоксы, persistence в localStorage (`terrain-hillshade`, `terrain-tri`) — без изменений. |
|
||||
| F-11 | Регрессия z8: визуально слой «Перепады» на z8 выглядит как раньше (opacity 0.70). |
|
||||
| F-12 | Регрессия z12-z15: hillshade и TRI не становятся темнее/контрастнее, чем были (calibration возвращается к старым значениям к z14). |
|
||||
| F-13 | Регрессия performance: количество запросов растровых тайлов на сессию не должно вырасти больше, чем на +35% (грубая оценка: +1 zoom-уровень для hillshade на z9 добавляет ~25% тайлов на сессию активного зумирования). |
|
||||
| F-14 | Документация: ADR не нужен (это калибровка, не архитектурное решение). Опциональный `06-adr/` остаётся пустым. Изменения покрываются TRZ и комментарием в коде, ссылающимся на ET-013. |
|
||||
|
||||
### Out of scope
|
||||
|
||||
- **Перегенерация hillshade с большим z-factor** (отдельная задача, см. §2.4).
|
||||
- **Добавление векторных горизонталей** (отдельная задача).
|
||||
- **Переход на raster-dem / Mapbox Terrain RGB** (смена архитектуры).
|
||||
- **Multidirectional hillshade** (требует pipeline).
|
||||
- **Подключение гипсометрии в UI** (отдельная задача).
|
||||
- **Изменение PH-6 параметров hillshade на сервере** (azimuth, altitude, z-factor).
|
||||
- **Изменение генератора TRI** (классификация, цветовая шкала).
|
||||
- **Тайл-кэш на стороне сервера** (раздача через FastAPI с `Cache-Control: max-age=31536000` уже есть).
|
||||
- **Изменение UI чекбоксов** (только текст hint'а в F-03).
|
||||
- **Изменение TERRAIN_DIR / endpoint contract** (`src/api/main.py:1240-1255`).
|
||||
- **Изменения PWA / offline-кэш стратегии для тайлов** (PH-9, не сейчас).
|
||||
|
||||
## 4. Метрики успеха
|
||||
|
||||
| # | Метрика | Критерий |
|
||||
| --- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| M-1 | Hillshade доступен на z9 | Чекбокс «Тени рельефа» при `zoom = 9` **не disabled**; hint скрыт; vector-source запрашивает тайлы при включении. |
|
||||
| M-2 | Hillshade-opacity zoom-aware | `paint['raster-opacity']` для слоя `terrain-hillshade` — `interpolate`-выражение со stops для z9, z10, z11, z12, z14. |
|
||||
| M-3 | Hillshade-contrast zoom-aware | `paint['raster-contrast']` — `interpolate`-выражение с положительными значениями на z9-z11 и 0 на z14. |
|
||||
| M-4 | Hillshade-resampling | `paint['raster-resampling']` для `terrain-hillshade` = `'nearest'`. |
|
||||
| M-5 | TRI-opacity zoom-aware | `paint['raster-opacity']` для `terrain-tri` — `interpolate`-выражение со stops для z5..z15. |
|
||||
| M-6 | TRI-resampling | `paint['raster-resampling']` для `terrain-tri` = `'nearest'`. |
|
||||
| M-7 | Регрессия z8 | На z8 видимость слоя «Перепады» (TRI) визуально не отличается от состояния до ET-013 (opacity stops содержат точку `8 → 0.70`). |
|
||||
| M-8 | Регрессия z14-z15 | На z14 hillshade visually близок к до-ET-013 (opacity ~0.40, contrast ~0). |
|
||||
| M-9 | Качественный тест z9-z11 | На скриншоте z10 над холмистым районом (например, юг Москвы / Ока) перепады «явно различимы» — критерий ручной (TC-UI-04-Z10-Q). При отказе — донастройка stops. |
|
||||
| M-10 | Сетевой объём | При типичной сессии (10 зумов между z8 и z12 c включёнными обоими слоями) объём загруженных PNG-тайлов hillshade и TRI вырос не более чем на 35%. |
|
||||
|
||||
## 5. Риски
|
||||
|
||||
| # | Риск | Вероятность | Влияние | Митигация |
|
||||
| ---- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| R-1 | `raster-contrast` со значением 0.4 даёт «жесть» — пересвет/чернота на тёмных тайлах. | Средняя | Среднее | TC-UI-04-Z10-Q — визуальная приёмка. При проблеме — снизить contrast в stops до 0.25-0.30. F-06 — точки калибруются итеративно. |
|
||||
| R-2 | На тёмной теме (`theme-dark`, ET-007) hillshade при opacity 0.65 и contrast 0.4 сливается с подложкой в кашу. | Средняя | Среднее | TC-UI-09-Z10-DARK-Q. При проблеме — добавить отдельные stops для dark-theme через `theme-change` event. Прозрачнее (например 0.55 вместо 0.65) на dark. |
|
||||
| R-3 | На спутниковой подложке (ET-007) opacity 0.65 + contrast 0.4 слишком «глушит» космоснимок. | Низкая | Среднее | TC-UI-08-Z10-SAT-Q. Hillshade на спутнике пользователю обычно не нужен — он использует подложку как замену рельефу. Если визуально некрасиво — на спутнике hillshade оставить opacity 0.40 (старое поведение). |
|
||||
| R-4 | Снижение UI-минзума hillshade до 9 раздувает сетевой трафик (z9 тайл = 4× больше z8 → область покрывается 4× меньшим числом тайлов, но каждый сессия теперь видит на 1 zoom-уровень больше). | Низкая | Низкое | M-10 (≤ +35%). На практике пользователь либо «включил и не двигается», либо «зумит — тайлы кэшируются». nginx и браузер кэшируют PNG агрессивно (Cache-Control: immutable, см. main.py:1252). |
|
||||
| R-5 | `raster-resampling: 'nearest'` на overzoom (z12-z15) даёт «пикселизацию», крупные квадраты вместо плавных теней. | Средняя | Низкое | TC-UI-06-Z14-Q. На z12-z14 пользователь обычно отключает hillshade — для города нужна подложка. Если визуально плохо — переключить на `'linear'` на z12+ через JS-логику (отдельный layer). В MVP оставляем `'nearest'`. |
|
||||
| R-6 | Изменение opacity TRI на z9-z11 (с 0.7 до 0.85) перекрывает грунтовки / тропы (`trails-track`, `trails-path-bridleway`). | Низкая | Низкое | `applyTerrainLayer` уже вставляет terrain-слои **перед** первым слоем `trails-*` или `poi-*` (`src/web/app.js:3337-3339`). z-order остаётся правильным. |
|
||||
| R-7 | После изменения paint-выражения старый clients (вкладка в браузере) видит «сломанный стиль» при F5. | Очень низкая| Низкое | Простой релоад страницы решает (стили задаются в JS, не в localStorage). Никакой миграции состояния не требуется. |
|
||||
| R-8 | `interpolate` с `raster-contrast` плохо поддерживается старыми версиями MapLibre. | Низкая | Низкое | MapLibre 4.7.0 (`unpkg.com/maplibre-gl@4.7.0`, см. index.html:10) поддерживает `interpolate` для всех raster paint-properties. |
|
||||
| R-9 | TRI на z5-z7 при увеличении opacity на крупных зумах остаётся как было — но без stops для z5/z6/z7 может «прыгнуть». | Низкая | Низкое | F-08 явно задаёт stops для z5, z7, z8 — сохранение прежнего поведения на z5-z7. interpolate-линейный гарантирует гладкость. |
|
||||
| R-10 | Цвета TRI (категориальная палитра) на nearest-resampling показывают резкие границы 30-метровых клеток SRTM — выглядит «зернисто». | Средняя | Низкое | Это и есть желаемое поведение: пользователь видит «реальные» границы перепадов, а не сглаженный туман. Если визуально не нравится — оставить `'linear'` для TRI (откатить F-09). |
|
||||
| R-11 | Если на test-среде тайлы z9-z11 не нарезаны (расхождение с PH-6 BRD), при включении hillshade на z9 будут 404. | Низкая | Высокое | Pre-implementation check: `curl https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/X/Y.png` должен вернуть 200. Если 404 — задача делится: сначала догенерить тайлы (PH-6 follow-up), потом ET-013. |
|
||||
|
||||
## 6. Зависимости
|
||||
|
||||
### Frontend
|
||||
- `src/web/app.js`:
|
||||
- `onTerrainCheckbox` (~2782): вызовы `applyTerrainLayer`.
|
||||
- `applyTerrainLayer` (~3316): расширить, чтобы принимать opacity-выражение и paint-объект.
|
||||
- `updateHillshadeAvailability` (~3359): сменить порог `< 10` на `< 9`.
|
||||
- `src/web/index.html`:
|
||||
- `#terrain-hillshade-hint` (строка 60): обновить текст «Зум 10+» → «Зум 9+».
|
||||
- Стили карты `style.json`/`style-dark.json` — без изменений (растровые слои не описаны в стилях, они добавляются динамически из JS).
|
||||
|
||||
### Backend
|
||||
- `src/api/main.py:1240-1255` (`terrain_tile`) — **без изменений**. Никаких новых endpoint, query, заголовков.
|
||||
|
||||
### Тесты
|
||||
- Новые unit-тесты `tests/unit/test_terrain_paint.py` (новый файл) — проверка структуры paint-выражений (stops, типы значений). Запуск через Node/jsdom либо чистый JS-парсер MapLibre style spec (см. TRZ §3.13).
|
||||
- Расширение существующих тестов слоёв (если есть). На текущий момент в репо нет тестов для `applyTerrainLayer` — добавляем минимальные.
|
||||
- UI-тесты: `04b-ui-test-cases.md`.
|
||||
|
||||
### Документация
|
||||
- `01-brd.md` (этот файл).
|
||||
- `02-trz.md`, `03-acceptance-criteria.md`, `04-test-plan.yaml`, `04b-ui-test-cases.md`.
|
||||
- ADR не требуется (это калибровка paint-параметров, не архитектурное решение). Если в реализации возникнет нужда в добавлении dark/satellite-specific paint-таблиц — добавляется `06-adr/adr-0001-theme-specific-terrain.md`.
|
||||
|
||||
### Инфра / Данные
|
||||
- Test-среда `https://openclaw.mva154.duckdns.org/enduro/` — существующий деплой.
|
||||
- Растровые тайлы рельефа в `/home/slin/enduro-trails/data/terrain/{hillshade,tri}/{z}/{x}/{y}.png` — **существующие**, без перегенерации.
|
||||
- **Обязательная pre-implementation проверка**: тайлы hillshade z9 и z10 над ЦФО действительно доступны (R-11).
|
||||
```bash
|
||||
curl -I https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png
|
||||
curl -I https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png
|
||||
```
|
||||
Ожидается HTTP 200 на оба.
|
||||
|
||||
### Связи с другими work items
|
||||
- **PH-6.terrain** — родительская фаза. ET-013 — post-MVP калибровка её UI.
|
||||
- **ET-007** — переключатель подложки Схема/Спутник. R-3 покрывает совместимость.
|
||||
- **ET-009 / ET-008** — публичные GPS-треки. Не пересекаются (отдельные источники и слои).
|
||||
- Будущий work item «hillshade-rerender-z9-z14 с z-factor 2.5» — на случай, если frontend-калибровки недостаточно.
|
||||
|
||||
## 7. План в одну строку
|
||||
|
||||
Снижаем UI-минзум hillshade с 10 до 9, переводим `raster-opacity` и
|
||||
`raster-contrast` hillshade в zoom-aware `interpolate`-выражения
|
||||
с пиком контраста на z9-z11, аналогично усиливаем opacity TRI на
|
||||
z9-z11, переключаем `raster-resampling` на `'nearest'` — без
|
||||
перегенерации растровых тайлов и без изменения backend.
|
||||
606
docs/work-items/ET-013/02-trz.md
Normal file
606
docs/work-items/ET-013/02-trz.md
Normal file
@@ -0,0 +1,606 @@
|
||||
---
|
||||
type: trz
|
||||
work_item_id: ET-013
|
||||
title: "ТЗ: Перепады высот на z9-z11 — zoom-aware paint для hillshade и TRI"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
related:
|
||||
- "PH-6.terrain"
|
||||
- "ET-007"
|
||||
---
|
||||
|
||||
# ТЗ — ET-013: Перепады высот на z9-z11
|
||||
|
||||
## 1. Терминология
|
||||
|
||||
- **Hillshade** — растровый слой теневого рельефа из
|
||||
`/terrain/hillshade/{z}/{x}/{y}.png`. MapLibre layer id —
|
||||
`terrain-hillshade`, source id — `terrain-hillshade-source`.
|
||||
- **TRI** («Перепады») — растровый слой Terrain Ruggedness Index
|
||||
из `/terrain/tri/{z}/{x}/{y}.png`. Layer id — `terrain-tri`,
|
||||
source id — `terrain-tri-source`.
|
||||
- **Zoom-tier paint** — MapLibre `interpolate`-выражение со
|
||||
stops по `['zoom']`, задаёт значение paint-property как функцию
|
||||
текущего зума.
|
||||
- **Raster paint properties** (MapLibre spec):
|
||||
- `raster-opacity` ∈ [0, 1] — прозрачность слоя.
|
||||
- `raster-contrast` ∈ [-1, 1] — усиление контраста PNG; 0 — без изменений, > 0 — усиление, < 0 — снижение.
|
||||
- `raster-resampling` ∈ `{'linear', 'nearest'}` — алгоритм
|
||||
масштабирования тайла на пиксели экрана. `'nearest'` даёт
|
||||
«пиксельные» резкие границы.
|
||||
- **UI-минзум hillshade** — порог в `updateHillshadeAvailability`,
|
||||
ниже которого чекбокс «Тени рельефа» disabled. Сейчас 10, после ET-013 — 9.
|
||||
|
||||
## 2. Архитектурные опоры
|
||||
|
||||
ET-013 не вводит новых слоёв, источников, endpoint'ов. Используем:
|
||||
|
||||
- `src/web/app.js`:
|
||||
- константа `TERRAIN_BASE_URL` (~2726) — без изменений.
|
||||
- `onTerrainCheckbox` (~2766) — без изменений сигнатуры; меняются
|
||||
параметры внутри вызовов `applyTerrainLayer`.
|
||||
- `applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom)` (~3316) —
|
||||
расширяется (см. REQ-F-04).
|
||||
- `updateHillshadeAvailability` (~3359) — порог `< 10` → `< 9`.
|
||||
- `restoreTerrainState` (~3379) — без изменений (вызывает onTerrainCheckbox).
|
||||
- `src/web/index.html`:
|
||||
- `#terrain-hillshade-hint` (строка 60) — текст «Зум 10+» → «Зум 9+».
|
||||
- `src/api/main.py:1240` (`terrain_tile`) — **без изменений**.
|
||||
|
||||
ET-013 = **9 правок: 2 в HTML/text, 7 в одном JS-файле**.
|
||||
|
||||
## 3. Требования
|
||||
|
||||
### REQ-F-01 — Снизить UI-минзум hillshade до 9
|
||||
|
||||
Файл `src/web/app.js`, функция `updateHillshadeAvailability`
|
||||
(строка ~3368):
|
||||
|
||||
```js
|
||||
if (zoom < 10) {
|
||||
```
|
||||
заменить на
|
||||
```js
|
||||
if (zoom < 9) { // ET-013: на z9 hillshade уже доступен
|
||||
```
|
||||
|
||||
**Acceptance check.** При `window._map.setZoom(9)` чекбокс
|
||||
`#terrain-hillshade-cb` имеет `disabled === false` и hint
|
||||
`#terrain-hillshade-hint` имеет `display: 'none'`.
|
||||
|
||||
### REQ-F-02 — Снизить minzoom source `terrain-hillshade-source` до 9
|
||||
|
||||
Файл `src/web/app.js`, функция `onTerrainCheckbox` (строка ~2782).
|
||||
Заменить:
|
||||
```js
|
||||
applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png',
|
||||
hillshadeChecked, 0.40, 10, 15);
|
||||
```
|
||||
на:
|
||||
```js
|
||||
// ET-013: hillshade теперь доступен с z9; opacity и contrast — zoom-aware
|
||||
applyTerrainLayer('terrain-hillshade',
|
||||
TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png',
|
||||
hillshadeChecked,
|
||||
HILLSHADE_PAINT, // см. REQ-F-04, REQ-F-05
|
||||
9, 15);
|
||||
```
|
||||
|
||||
**Acceptance check.** В DevTools после включения слоя:
|
||||
```js
|
||||
window._map.getSource('terrain-hillshade-source').minzoom === 9
|
||||
```
|
||||
|
||||
### REQ-F-03 — Снизить minzoom source `terrain-tri-source` остаётся 5
|
||||
|
||||
Файл `src/web/app.js`, строка ~2783. Менять только параметр
|
||||
opacity (см. REQ-F-08). minzoom/maxzoom не трогаем:
|
||||
|
||||
```js
|
||||
applyTerrainLayer('terrain-tri',
|
||||
TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png',
|
||||
triChecked,
|
||||
TRI_PAINT, // см. REQ-F-04, REQ-F-08
|
||||
5, 15);
|
||||
```
|
||||
|
||||
### REQ-F-04 — Расширить `applyTerrainLayer` для поддержки paint-объекта
|
||||
|
||||
Файл `src/web/app.js`, функция `applyTerrainLayer` (строки ~3316-3357).
|
||||
|
||||
Текущая сигнатура:
|
||||
```js
|
||||
function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
|
||||
...
|
||||
paint: { 'raster-opacity': opacity, 'raster-resampling': 'linear' },
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Новая сигнатура (обратно-совместимая):
|
||||
```js
|
||||
/**
|
||||
* @param {string} id - id слоя.
|
||||
* @param {string} tileUrl - URL-шаблон тайлов.
|
||||
* @param {boolean} enabled - показывать ли слой.
|
||||
* @param {number|object} opacityOrPaint - либо число (старый контракт,
|
||||
* станет 'raster-opacity'), либо объект paint-properties целиком.
|
||||
* Если объект — должен содержать как минимум 'raster-opacity'.
|
||||
* @param {number} minzoom
|
||||
* @param {number} maxzoom
|
||||
*/
|
||||
function applyTerrainLayer(id, tileUrl, enabled, opacityOrPaint, minzoom, maxzoom) {
|
||||
const map = window._map;
|
||||
if (!map) return;
|
||||
const sourceId = id + '-source';
|
||||
|
||||
// ET-013: нормализация paint
|
||||
const paint = (typeof opacityOrPaint === 'number')
|
||||
? { 'raster-opacity': opacityOrPaint, 'raster-resampling': 'linear' }
|
||||
: opacityOrPaint;
|
||||
|
||||
if (enabled) {
|
||||
if (!map.getSource(sourceId)) {
|
||||
map.addSource(sourceId, {
|
||||
type: 'raster',
|
||||
tiles: [tileUrl],
|
||||
tileSize: 256,
|
||||
scheme: 'tms',
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom
|
||||
});
|
||||
}
|
||||
if (!map.getLayer(id)) {
|
||||
const firstTrailLayer = map.getStyle().layers.find(l =>
|
||||
l.id.startsWith('trails-') || l.id.startsWith('poi-')
|
||||
);
|
||||
map.addLayer({
|
||||
id: id,
|
||||
type: 'raster',
|
||||
source: sourceId,
|
||||
paint: paint,
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom
|
||||
}, firstTrailLayer ? firstTrailLayer.id : undefined);
|
||||
}
|
||||
} else {
|
||||
if (map.getLayer(id)) map.removeLayer(id);
|
||||
if (map.getSource(sourceId)) map.removeSource(sourceId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Acceptance check.** Unit-тест (см. REQ-F-13):
|
||||
- `applyTerrainLayer(id, url, true, 0.5, 8, 14)` — старый контракт работает.
|
||||
- `applyTerrainLayer(id, url, true, {'raster-opacity': 0.5, 'raster-contrast': 0.3, 'raster-resampling': 'nearest'}, 8, 14)` — paint применён как есть.
|
||||
|
||||
### REQ-F-05 — Hillshade `raster-opacity` zoom-aware
|
||||
|
||||
Файл `src/web/app.js`, после определения `TERRAIN_BASE_URL` (после строки ~2726)
|
||||
добавить блок констант:
|
||||
|
||||
```js
|
||||
// ET-013: zoom-aware paint для слоёв рельефа.
|
||||
// Цель — компенсировать «потерю выразительности» перепадов на z9-z11.
|
||||
// Pre-z9 — hillshade не показывается (UI-минзум). На z9-z11 — максимальный
|
||||
// контраст и opacity, чтобы тени читались как на z8. К z12-z14 — возврат
|
||||
// к исходным значениям (тогда у пользователя есть другие способы
|
||||
// читать рельеф: подложка, грунтовки, POI).
|
||||
|
||||
const HILLSHADE_PAINT = {
|
||||
'raster-opacity': [
|
||||
'interpolate', ['linear'], ['zoom'],
|
||||
9, 0.65,
|
||||
10, 0.60,
|
||||
11, 0.55,
|
||||
12, 0.50,
|
||||
14, 0.40
|
||||
],
|
||||
'raster-contrast': [
|
||||
'interpolate', ['linear'], ['zoom'],
|
||||
9, 0.40,
|
||||
10, 0.35,
|
||||
11, 0.30,
|
||||
12, 0.15,
|
||||
14, 0.00
|
||||
],
|
||||
'raster-resampling': 'nearest'
|
||||
};
|
||||
```
|
||||
|
||||
Stops подобраны так:
|
||||
- z9-z11 — пик opacity (0.65→0.55) и contrast (0.40→0.30). Это
|
||||
компенсация: тени темнее и контрастнее.
|
||||
- z12-z14 — плавный возврат к исходному (opacity 0.40, contrast 0):
|
||||
на крупных зумах пользователь уже видит подложку детально и
|
||||
тени должны «уйти на второй план».
|
||||
- `'nearest'` resampling: подчёркивает 30-метровые границы SRTM,
|
||||
перепады выглядят резко.
|
||||
|
||||
**Acceptance check.**
|
||||
```js
|
||||
const layer = window._map.getLayer('terrain-hillshade');
|
||||
const opacity = window._map.getPaintProperty('terrain-hillshade', 'raster-opacity');
|
||||
Array.isArray(opacity) && opacity[0] === 'interpolate' // true
|
||||
```
|
||||
|
||||
### REQ-F-06 — Hillshade `raster-contrast` (внутри HILLSHADE_PAINT)
|
||||
|
||||
См. REQ-F-05. Constants выносятся в HILLSHADE_PAINT, отдельной правки кода не нужно.
|
||||
|
||||
### REQ-F-07 — Hillshade `raster-resampling: 'nearest'`
|
||||
|
||||
См. REQ-F-05. Часть HILLSHADE_PAINT.
|
||||
|
||||
### REQ-F-08 — TRI `raster-opacity` zoom-aware
|
||||
|
||||
В том же блоке (после HILLSHADE_PAINT, до `function toggleTerrainPopup`):
|
||||
|
||||
```js
|
||||
const TRI_PAINT = {
|
||||
'raster-opacity': [
|
||||
'interpolate', ['linear'], ['zoom'],
|
||||
5, 0.55,
|
||||
7, 0.65,
|
||||
8, 0.70, // регрессия z8: текущее значение
|
||||
9, 0.80,
|
||||
10, 0.85,
|
||||
11, 0.85, // пик на z9-z11
|
||||
12, 0.75,
|
||||
15, 0.70
|
||||
],
|
||||
'raster-resampling': 'nearest'
|
||||
};
|
||||
```
|
||||
|
||||
Stops:
|
||||
- **z5-z7** — мягко (0.55-0.65), на «обзорных» зумах не глушим карту.
|
||||
- **z8** — 0.70 ровно как сейчас (регрессия).
|
||||
- **z9-z11** — пик 0.80-0.85 (целевое улучшение ET-013).
|
||||
- **z12-z15** — спад до 0.70-0.75.
|
||||
|
||||
**Acceptance check.**
|
||||
```js
|
||||
const opacity = window._map.getPaintProperty('terrain-tri', 'raster-opacity');
|
||||
// На z8 — 0.70 ровно (регрессия).
|
||||
// На z10 — 0.85 ровно (целевое поведение).
|
||||
```
|
||||
|
||||
### REQ-F-09 — TRI `raster-resampling: 'nearest'`
|
||||
|
||||
Часть TRI_PAINT, см. REQ-F-08.
|
||||
|
||||
### REQ-F-10 — Обновить UI-hint текст
|
||||
|
||||
Файл `src/web/index.html`, строка ~60:
|
||||
```html
|
||||
<span class="terrain-hint" id="terrain-hillshade-hint" style="display:none">Зум 10+</span>
|
||||
```
|
||||
заменить на
|
||||
```html
|
||||
<span class="terrain-hint" id="terrain-hillshade-hint" style="display:none">Зум 9+</span>
|
||||
```
|
||||
|
||||
### REQ-F-11 — `updateHillshadeAvailability` использует новый порог
|
||||
|
||||
См. REQ-F-01. Никаких других изменений в этой функции не нужно.
|
||||
|
||||
### REQ-F-12 — Сохранить контракт `onTerrainCheckbox`
|
||||
|
||||
Сигнатура и логика persistence в `localStorage` (`terrain-hillshade`,
|
||||
`terrain-tri`) — без изменений. Кнопка `#terrain-toggle` `.active`
|
||||
переключается так же.
|
||||
|
||||
### REQ-F-13 — Unit-тесты paint-выражений
|
||||
|
||||
Файл `tests/unit/test_terrain_paint.js` (новый; если JS-тесты раньше
|
||||
не было — настроить vitest/jest в `package.json` либо использовать
|
||||
существующий тест-раннер; альтернатива — Python-парсер JSON-выражений).
|
||||
|
||||
Реализация в одной из двух форм:
|
||||
|
||||
**Вариант A: JS unit-тест (jest/vitest)**
|
||||
|
||||
```js
|
||||
// tests/unit/test_terrain_paint.test.js
|
||||
import { HILLSHADE_PAINT, TRI_PAINT } from '../../src/web/terrain-paint.js';
|
||||
// Если константы внутри app.js: либо вынести в отдельный модуль,
|
||||
// либо использовать AST-парсер. См. альтернативу B.
|
||||
|
||||
describe('ET-013 terrain paint', () => {
|
||||
test('HILLSHADE_PAINT: raster-opacity is interpolate by zoom', () => {
|
||||
const op = HILLSHADE_PAINT['raster-opacity'];
|
||||
expect(op[0]).toBe('interpolate');
|
||||
expect(op[1][0]).toBe('linear');
|
||||
expect(op[2][0]).toBe('zoom');
|
||||
// stops: ..., 9, 0.65, 10, 0.60, 11, 0.55, 12, 0.50, 14, 0.40
|
||||
const stops = op.slice(3);
|
||||
expect(stops).toContain(9);
|
||||
expect(stops[stops.indexOf(9) + 1]).toBeCloseTo(0.65, 2);
|
||||
expect(stops[stops.indexOf(11) + 1]).toBeCloseTo(0.55, 2);
|
||||
expect(stops[stops.indexOf(14) + 1]).toBeCloseTo(0.40, 2);
|
||||
});
|
||||
|
||||
test('HILLSHADE_PAINT: raster-contrast peak at z9-z11', () => {
|
||||
const c = HILLSHADE_PAINT['raster-contrast'];
|
||||
expect(c[0]).toBe('interpolate');
|
||||
const stops = c.slice(3);
|
||||
expect(stops[stops.indexOf(9) + 1]).toBeGreaterThanOrEqual(0.35);
|
||||
expect(stops[stops.indexOf(14) + 1]).toBeLessThanOrEqual(0.05);
|
||||
});
|
||||
|
||||
test('HILLSHADE_PAINT: resampling nearest', () => {
|
||||
expect(HILLSHADE_PAINT['raster-resampling']).toBe('nearest');
|
||||
});
|
||||
|
||||
test('TRI_PAINT: z8 unchanged (regression)', () => {
|
||||
const op = TRI_PAINT['raster-opacity'];
|
||||
const stops = op.slice(3);
|
||||
expect(stops[stops.indexOf(8) + 1]).toBeCloseTo(0.70, 2);
|
||||
});
|
||||
|
||||
test('TRI_PAINT: peak at z9-z11', () => {
|
||||
const op = TRI_PAINT['raster-opacity'];
|
||||
const stops = op.slice(3);
|
||||
expect(stops[stops.indexOf(10) + 1]).toBeGreaterThanOrEqual(0.80);
|
||||
expect(stops[stops.indexOf(11) + 1]).toBeGreaterThanOrEqual(0.80);
|
||||
});
|
||||
|
||||
test('TRI_PAINT: resampling nearest', () => {
|
||||
expect(TRI_PAINT['raster-resampling']).toBe('nearest');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Вариант B: Python-парсер (если JS-тестов в проекте нет)**
|
||||
|
||||
```python
|
||||
# tests/unit/test_terrain_paint.py
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
APP_JS = Path(__file__).parents[2] / 'src/web/app.js'
|
||||
|
||||
def test_hillshade_paint_exists():
|
||||
txt = APP_JS.read_text(encoding='utf-8')
|
||||
assert 'HILLSHADE_PAINT' in txt
|
||||
assert "'raster-opacity'" in txt
|
||||
assert "'raster-contrast'" in txt
|
||||
assert "'raster-resampling': 'nearest'" in txt
|
||||
|
||||
def test_hillshade_opacity_stops():
|
||||
"""Сверяем stops по grep — недостаточно строго, но удержит регрессию."""
|
||||
txt = APP_JS.read_text(encoding='utf-8')
|
||||
# ищем блок HILLSHADE_PAINT и проверяем stop'ы
|
||||
m = re.search(r"HILLSHADE_PAINT\s*=\s*\{(.+?)\};", txt, re.DOTALL)
|
||||
assert m, "HILLSHADE_PAINT not found"
|
||||
block = m.group(1)
|
||||
assert '9, 0.65' in block or '9, 0.65' in block
|
||||
assert '11, 0.55' in block
|
||||
assert '14, 0.40' in block
|
||||
|
||||
def test_tri_opacity_regression_z8():
|
||||
txt = APP_JS.read_text(encoding='utf-8')
|
||||
m = re.search(r"TRI_PAINT\s*=\s*\{(.+?)\};", txt, re.DOTALL)
|
||||
assert m
|
||||
block = m.group(1)
|
||||
assert '8, 0.70' in block or '8, 0.70' in block, "z8 opacity должна остаться 0.70"
|
||||
assert '10, 0.85' in block
|
||||
```
|
||||
|
||||
**Решение по умолчанию для ET-013:** Вариант B (Python-парсер),
|
||||
т.к. в проекте JS-тестов не существует, а ставить vitest ради ET-013
|
||||
— превышение scope. Опционально разработчик может выбрать Вариант A.
|
||||
|
||||
### REQ-F-14 — Регрессионные тесты
|
||||
|
||||
Файл `tests/unit/test_terrain_paint.py` (тот же файл, что и REQ-F-13):
|
||||
|
||||
- **UT-REG-01.** Проверить, что вызов `applyTerrainLayer` с числовым
|
||||
`opacity` (старый контракт) собирает paint `{raster-opacity: X, raster-resampling: 'linear'}` —
|
||||
на случай, если другой код (POI, halo, scenic) использует ту же
|
||||
функцию. На текущий момент `applyTerrainLayer` вызывается **только**
|
||||
внутри `onTerrainCheckbox` — но контракт должен оставаться обратно-совместимым.
|
||||
|
||||
Реализация — статический grep по `src/web/`:
|
||||
```python
|
||||
import re, glob
|
||||
def test_only_two_callers_of_applyterrainLayer():
|
||||
pattern = re.compile(r'applyTerrainLayer\s*\(')
|
||||
total = 0
|
||||
for f in glob.glob('src/web/*.js'):
|
||||
total += len(pattern.findall(open(f).read()))
|
||||
assert total >= 2 # минимум 2 вызова в onTerrainCheckbox
|
||||
```
|
||||
|
||||
- **UT-REG-02.** `updateHillshadeAvailability` порог = 9
|
||||
(grep по строке `zoom < 9`).
|
||||
|
||||
### REQ-F-15 — Integration smoke-тест: тайлы z9 доступны
|
||||
|
||||
Файл `tests/integration/test_terrain_z9_tiles.py` (новый):
|
||||
|
||||
- **IT-TILE-Z9-01.** При наличии `data/terrain/hillshade/9/`
|
||||
директории — запрос `GET /terrain/hillshade/9/308/158.png`
|
||||
возвращает 200, content-type `image/png`. Если директория
|
||||
не существует — тест **skipped** с пояснением.
|
||||
```python
|
||||
import os, pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from src.api.main import app
|
||||
|
||||
TERRAIN_DIR = os.environ.get(
|
||||
'TERRAIN_DIR', os.path.join(os.path.dirname(__file__), '../../data/terrain')
|
||||
)
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.path.isdir(os.path.join(TERRAIN_DIR, 'hillshade/9')),
|
||||
reason='hillshade z9 tiles not present in CI (PH-6 data not in repo)'
|
||||
)
|
||||
def test_hillshade_z9_tile_returns_200():
|
||||
# Любой существующий тайл из директории
|
||||
z9_dir = os.path.join(TERRAIN_DIR, 'hillshade/9')
|
||||
x = sorted(os.listdir(z9_dir))[0]
|
||||
y_file = sorted(os.listdir(os.path.join(z9_dir, x)))[0]
|
||||
y = y_file.replace('.png', '')
|
||||
r = client.get(f'/terrain/hillshade/9/{x}/{y}.png')
|
||||
assert r.status_code == 200
|
||||
assert r.headers['content-type'] == 'image/png'
|
||||
|
||||
def test_hillshade_invalid_zoom_404():
|
||||
r = client.get('/terrain/hillshade/99/0/0.png')
|
||||
assert r.status_code == 404
|
||||
```
|
||||
|
||||
### REQ-F-16 — UI-тесты Playwright
|
||||
|
||||
См. `04b-ui-test-cases.md`. Ключевые проверки (полный список — там):
|
||||
|
||||
- TC-UI-01-Z9: hillshade доступен на z9, hint скрыт.
|
||||
- TC-UI-02-Z8-REGR: на z8 TRI визуально как до ET-013.
|
||||
- TC-UI-03-Z9-Q: визуальная читаемость перепадов на z9 ≥ z8 (качественно).
|
||||
- TC-UI-04-Z10-Q: то же для z10.
|
||||
- TC-UI-05-Z11-Q: то же для z11.
|
||||
- TC-UI-06-Z14-Q: на z14 hillshade «нормальный», не перегретый.
|
||||
- TC-UI-07-Z9-MOBILE: мобильный viewport, hillshade видим на z9.
|
||||
- TC-UI-08-Z10-SAT-Q: совместимость со спутниковой подложкой.
|
||||
- TC-UI-09-Z10-DARK-Q: совместимость с тёмной темой.
|
||||
- TC-UI-10-PERSIST: localStorage `terrain-hillshade`/`terrain-tri`
|
||||
переживает перезагрузку, паттерн чекбоксов восстанавливается.
|
||||
|
||||
### REQ-F-17 — Persistence без миграции
|
||||
|
||||
Ключи `localStorage`:
|
||||
- `terrain-hillshade` ('1' | '0') — без изменений.
|
||||
- `terrain-tri` ('1' | '0') — без изменений.
|
||||
|
||||
После ET-013 пользователи с включённым hillshade при следующей
|
||||
загрузке на z9 увидят слой автоматически (раньше он был disabled).
|
||||
Это не миграция, а ожидаемое улучшение UX.
|
||||
|
||||
### REQ-F-18 — Не менять API контракт
|
||||
|
||||
`GET /terrain/{layer}/{z}/{x}/{y}.png` — без изменений. Никаких
|
||||
новых query, headers, кодов ответа. `Cache-Control: immutable`
|
||||
сохраняется.
|
||||
|
||||
### REQ-F-19 — Не менять конфиги и стили
|
||||
|
||||
- `src/web/style.json`, `src/web/style-dark.json` — без изменений.
|
||||
- `src/web/app.css` — без изменений (стили чекбоксов не меняются).
|
||||
- `config/*.yaml` — без изменений.
|
||||
|
||||
### REQ-F-20 — Деплой и валидация
|
||||
|
||||
После merge в `main` и деплоя:
|
||||
|
||||
1. **Pre-merge sanity** (на test-среде до деплоя):
|
||||
```bash
|
||||
curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png | head -1
|
||||
```
|
||||
Ожидается `HTTP/1.1 200 OK`. Если 404 — задача останавливается,
|
||||
тайлы z9 нужно догенерировать в рамках PH-6 follow-up.
|
||||
|
||||
2. **Smoke в test-среде**:
|
||||
- Открыть карту, центр над Окой/югом Москвы (`[37.6, 54.5]`).
|
||||
- `window._map.setZoom(9)` — кнопка «Тени рельефа» активна.
|
||||
- Включить «Тени рельефа» и «Перепады».
|
||||
- Скриншот → визуальная приёмка по AC-03..AC-05.
|
||||
3. **Зафиксировать в `14-deploy-log.md`**.
|
||||
|
||||
### REQ-F-21 — Документация
|
||||
|
||||
В `docs/work-items/ET-013/` после Анализа:
|
||||
- `00-business-request.md` (есть)
|
||||
- `01-brd.md`
|
||||
- `02-trz.md` (этот файл)
|
||||
- `03-acceptance-criteria.md`
|
||||
- `04-test-plan.yaml`
|
||||
- `04b-ui-test-cases.md`
|
||||
|
||||
После реализации: `12-review.md`, `13-test-report.md`,
|
||||
`14-deploy-log.md`. ADR опционально (см. BRD §6).
|
||||
|
||||
## 4. Не-функциональные требования
|
||||
|
||||
### NFR-01 — Производительность клиента
|
||||
- Добавление двух `interpolate`-выражений в paint не должно
|
||||
заметно увеличивать render time. MapLibre кэширует
|
||||
скомпилированные style-выражения; разница < 1 мс на frame.
|
||||
- `raster-resampling: 'nearest'` дешевле, чем `'linear'`
|
||||
(без bilinear-фильтрации) — на самом деле небольшое
|
||||
ускорение растеризации.
|
||||
|
||||
### NFR-02 — Производительность сервера
|
||||
Без изменений: endpoint `terrain_tile` отдаёт PNG из файловой системы
|
||||
с `Cache-Control: immutable`.
|
||||
|
||||
### NFR-03 — Сетевой трафик
|
||||
- При снижении UI-минзума hillshade с 10 до 9 пользователь
|
||||
может видеть слой на одной zoom-ступени раньше, что добавляет
|
||||
~25-35% PNG-тайлов на типичную сессию активного зумирования
|
||||
с включённым hillshade.
|
||||
- Browser-кэш + nginx-кэш (`Cache-Control: max-age=31536000,
|
||||
immutable`) поглощают это после первого визита.
|
||||
- Регрессия `M-10`: рост ≤ 35%.
|
||||
|
||||
### NFR-04 — Совместимость
|
||||
- MapLibre 4.7.0 (см. `index.html:10`, `index.html:503`)
|
||||
поддерживает все используемые paint properties и
|
||||
`interpolate`-выражения.
|
||||
- Старые tab'ы (без обновления страницы) продолжают работать
|
||||
с прежним кодом до перезагрузки.
|
||||
|
||||
### NFR-05 — Безопасность
|
||||
Никаких изменений в auth / CSP / валидации.
|
||||
|
||||
### NFR-06 — Логирование
|
||||
Никаких новых лог-сообщений. `uvicorn.access` для `/terrain/*`
|
||||
работает как раньше.
|
||||
|
||||
### NFR-07 — Persistence
|
||||
`localStorage` — без миграции. Существующие ключи интерпретируются
|
||||
как раньше; включённый ранее hillshade автоматически появится на
|
||||
z9 при следующей загрузке.
|
||||
|
||||
## 5. План работ (для разработчика)
|
||||
|
||||
1. **Pre-implementation check**: проверить наличие тайлов z9-z11
|
||||
на test-среде (REQ-F-20 §1). Если 404 — стоп, открыть PH-6
|
||||
follow-up.
|
||||
2. **Frontend constants**: добавить `HILLSHADE_PAINT` и `TRI_PAINT`
|
||||
(REQ-F-05, F-08) после `TERRAIN_BASE_URL`.
|
||||
3. **Frontend `applyTerrainLayer`**: расширить сигнатуру (REQ-F-04).
|
||||
4. **Frontend `onTerrainCheckbox`**: перевести вызовы на константы
|
||||
(REQ-F-02, F-03).
|
||||
5. **Frontend `updateHillshadeAvailability`**: порог `< 10` → `< 9`
|
||||
(REQ-F-01, F-11).
|
||||
6. **HTML hint**: «Зум 10+» → «Зум 9+» (REQ-F-10).
|
||||
7. **Тесты**: `tests/unit/test_terrain_paint.py` (REQ-F-13, F-14).
|
||||
8. **Integration smoke**: `tests/integration/test_terrain_z9_tiles.py`
|
||||
(REQ-F-15) — с `@pytest.mark.skipif` для CI без данных.
|
||||
9. **`make lint` / `make test`** — должны пройти.
|
||||
10. **Code review → merge → deploy в test**.
|
||||
11. **Ручная валидация** (REQ-F-20 §2).
|
||||
12. **Playwright UI-тесты** по `04b-ui-test-cases.md`.
|
||||
13. **Запись в `13-test-report.md` и `14-deploy-log.md`**.
|
||||
|
||||
## 6. Открытые вопросы и решения по умолчанию
|
||||
|
||||
| Вопрос | Решение по умолчанию |
|
||||
|---|---|
|
||||
| Стоит ли понижать UI-минзум hillshade ещё дальше (z8)? | **Нет.** На z8 hillshade-тайлы 256px покрывают ~150 км по широте — крупные тени становятся неразборчивым «шумом». TRI работает лучше. Если будущий BRD захочет — отдельная задача. |
|
||||
| Стоит ли использовать разные paint для тёмной темы (`theme-dark`)? | **Не в MVP.** Если AC-09 (TC-UI-09-Z10-DARK-Q) показывает «слой сливается с подложкой» — добавить ADR-0001 о theme-specific paint в follow-up. |
|
||||
| Стоит ли использовать разные paint для спутниковой подложки? | **Не в MVP.** Hillshade на спутнике пользователю обычно не нужен — он использует подложку как замену рельефу. Если AC-08 (TC-UI-08-Z10-SAT-Q) показывает «глушит подложку» — отдельная итерация. |
|
||||
| Стоит ли добавить `raster-saturation` для TRI? | **Не в MVP.** Сначала смотрим на эффект от `raster-opacity` + `'nearest'`. Если визуально недостаточно ярко — добавить второй раунд калибровки. |
|
||||
| Перегенерировать ли hillshade с z-factor 2.5 для z9-z14? | **Не сейчас.** Отдельная задача в случае, если frontend-калибровка ET-013 не решает проблему (вероятность по моей оценке — низкая). |
|
||||
| Менять ли `raster-resampling` динамически по зуму? | **Нет.** MapLibre не поддерживает `interpolate` для `raster-resampling`. Глобальное `'nearest'` для обоих слоёв — приемлемый компромисс (см. R-5). |
|
||||
| Подключить ли гипсометрию в UI? | **Out of scope.** Hypso тайлы есть, но UI-чекбокса нет. Отдельная задача. |
|
||||
| Делать ли paint-таблицы переменными окружения / config'ом? | **Нет.** Это калибровка, она живёт в коде и меняется коммитом. Конфигурируемость — преждевременная абстракция. |
|
||||
| Стоит ли добавлять `vitest`/`jest` ради JS-unit-тестов? | **Нет в ET-013.** Используем Python-парсер (Вариант B в REQ-F-13). |
|
||||
236
docs/work-items/ET-013/03-acceptance-criteria.md
Normal file
236
docs/work-items/ET-013/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
type: acceptance-criteria
|
||||
work_item_id: ET-013
|
||||
title: "Acceptance Criteria: Перепады высот на z9-z11"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
---
|
||||
|
||||
# Acceptance Criteria — ET-013
|
||||
|
||||
Критерии в Gherkin-стиле. Все обязательные. Задача считается
|
||||
принятой, когда каждый критерий прошёл проверку (автоматическую
|
||||
в CI или ручную в test-среде).
|
||||
|
||||
## AC-01 — UI-минзум hillshade понижен до 9
|
||||
|
||||
**Given** ветка `feature/ET-013-z9-z11-z8` после реализации
|
||||
**When** проверяется код
|
||||
**Then**:
|
||||
- В `src/web/app.js` функция `updateHillshadeAvailability` содержит
|
||||
`if (zoom < 9)` (а не `< 10`).
|
||||
- В `src/web/index.html` элемент `#terrain-hillshade-hint` содержит
|
||||
текст «Зум 9+» (а не «Зум 10+»).
|
||||
|
||||
## AC-02 — Vector-source `terrain-hillshade-source` имеет minzoom=9
|
||||
|
||||
**Given** test-среда после деплоя ET-013, включены оба чекбокса слоёв рельефа
|
||||
**When** в DevTools выполнить
|
||||
```js
|
||||
window._map.getSource('terrain-hillshade-source').minzoom
|
||||
```
|
||||
**Then** результат — `9`.
|
||||
|
||||
## AC-03 — При z=9 hillshade доступен и виден
|
||||
|
||||
**Given** пользователь на test-среде, центр карты над холмистым
|
||||
районом (например, юг Москвы / Ока: `[37.6, 54.5]`)
|
||||
**When** установить `window._map.setZoom(9)`, открыть `#terrain-popup`,
|
||||
включить «Тени рельефа»
|
||||
**Then**:
|
||||
- `#terrain-hillshade-cb` имеет `disabled === false`.
|
||||
- `#terrain-hillshade-hint` имеет `display: 'none'`.
|
||||
- `window._map.getLayoutProperty('terrain-hillshade', 'visibility') === 'visible'`.
|
||||
- На карте видны тени рельефа.
|
||||
|
||||
## AC-04 — Hillshade paint zoom-aware
|
||||
|
||||
**Given** включён hillshade на test-среде
|
||||
**When** в DevTools выполнить
|
||||
```js
|
||||
const op = window._map.getPaintProperty('terrain-hillshade', 'raster-opacity');
|
||||
const ct = window._map.getPaintProperty('terrain-hillshade', 'raster-contrast');
|
||||
const rs = window._map.getPaintProperty('terrain-hillshade', 'raster-resampling');
|
||||
```
|
||||
**Then**:
|
||||
- `Array.isArray(op) && op[0] === 'interpolate'` (zoom-aware opacity).
|
||||
- `Array.isArray(ct) && ct[0] === 'interpolate'` (zoom-aware contrast).
|
||||
- `rs === 'nearest'`.
|
||||
|
||||
## AC-05 — TRI paint zoom-aware
|
||||
|
||||
**Given** включён TRI на test-среде
|
||||
**When** в DevTools
|
||||
```js
|
||||
const op = window._map.getPaintProperty('terrain-tri', 'raster-opacity');
|
||||
const rs = window._map.getPaintProperty('terrain-tri', 'raster-resampling');
|
||||
```
|
||||
**Then**:
|
||||
- `Array.isArray(op) && op[0] === 'interpolate'`.
|
||||
- На z=8 эффективное значение `≈ 0.70` (регрессия).
|
||||
- На z=10 эффективное значение `≥ 0.80`.
|
||||
- `rs === 'nearest'`.
|
||||
|
||||
## AC-06 — Регрессия z8: TRI визуально как было
|
||||
|
||||
**Given** test-среда после деплоя
|
||||
**When** установить `zoom = 8`, включить ТОЛЬКО «Перепады» (без hillshade)
|
||||
**Then**:
|
||||
- Скриншот `et013-z8-tri-regress.png` не отличается визуально
|
||||
заметно от состояния до ET-013 (сравнение оператором).
|
||||
- Hillshade-слой не присутствует в стиле (`!map.getLayer('terrain-hillshade')`).
|
||||
|
||||
## AC-07 — Качественная читаемость z9 (целевой критерий)
|
||||
|
||||
**Given** test-среда, центр над Окой / Кашира / Воробьёвы Горы
|
||||
**When** `zoom = 9`, включены оба слоя «Тени рельефа» и «Перепады»
|
||||
**Then**:
|
||||
- На скриншоте `et013-z9-readable.png` явно видны перепады
|
||||
высот: тени по склонам, цветные пятна TRI выделяют шероховатые
|
||||
зоны.
|
||||
- Оператор подтверждает: «перепады сопоставимы с z8 или лучше».
|
||||
- При отказе — корректировка stops в HILLSHADE_PAINT / TRI_PAINT.
|
||||
|
||||
## AC-08 — Качественная читаемость z10
|
||||
|
||||
**Given** test-среда, аналогично AC-07
|
||||
**When** `zoom = 10`
|
||||
**Then**: то же, что AC-07.
|
||||
|
||||
## AC-09 — Качественная читаемость z11
|
||||
|
||||
**Given** test-среда, аналогично AC-07
|
||||
**When** `zoom = 11`
|
||||
**Then**: то же, что AC-07.
|
||||
|
||||
## AC-10 — Регрессия z14: hillshade не перегрет
|
||||
|
||||
**Given** test-среда
|
||||
**When** `zoom = 14`, включён hillshade
|
||||
**Then**:
|
||||
- Эффективные значения `raster-opacity ≈ 0.40`, `raster-contrast ≈ 0`.
|
||||
- Скриншот `et013-z14-regress.png` не темнее и не контрастнее, чем
|
||||
до ET-013.
|
||||
|
||||
## AC-11 — Hillshade на тёмной теме читается
|
||||
|
||||
**Given** test-среда, `theme-dark` активна
|
||||
**When** `zoom = 10`, включён hillshade
|
||||
**Then**:
|
||||
- Тени видны, не сливаются с тёмной подложкой.
|
||||
- При отказе (тени «съедают» карту) — открыть ADR
|
||||
«theme-specific hillshade paint» и добавить отдельные stops
|
||||
для dark-theme (см. BRD R-2). В рамках MVP ET-013 это
|
||||
не обязательно, но фиксируется в `13-test-report.md`.
|
||||
|
||||
## AC-12 — Hillshade на спутниковой подложке не глушит снимок
|
||||
|
||||
**Given** test-среда, переключена подложка `#base-btn-satellite`
|
||||
**When** `zoom = 10`, включён hillshade
|
||||
**Then**:
|
||||
- На спутниковом снимке видны и детали поверхности (рельеф
|
||||
улавливается уже через тени снимка), и hillshade-оверлей.
|
||||
- Оверлей не превращает снимок в «серую плёнку».
|
||||
- Подтверждается оператором по TC-UI-08-Z10-SAT-Q.
|
||||
|
||||
## AC-13 — Hillshade на мобильном (375×667)
|
||||
|
||||
**Given** Playwright mobile viewport, включён hillshade
|
||||
**When** `zoom = 9`
|
||||
**Then**:
|
||||
- Тени видны, читаемы.
|
||||
- Чекбоксы и hint работают корректно.
|
||||
|
||||
## AC-14 — Persistence не сломан
|
||||
|
||||
**Given** включены оба чекбокса
|
||||
**When** перезагрузить страницу (`location.reload()`)
|
||||
**Then**:
|
||||
- `localStorage.getItem('terrain-hillshade') === '1'`.
|
||||
- `localStorage.getItem('terrain-tri') === '1'`.
|
||||
- После загрузки слои восстановлены, на z=9 hillshade автоматически
|
||||
активен.
|
||||
|
||||
## AC-15 — Unit-тесты paint-выражений зелёные
|
||||
|
||||
**Given** ветка
|
||||
**When** `pytest tests/unit/test_terrain_paint.py -v`
|
||||
**Then** все тесты проходят (UT-PAINT-*, UT-REG-*).
|
||||
|
||||
## AC-16 — Integration smoke z9 тайлов
|
||||
|
||||
**Given** ветка, наличие данных в test-среде или CI fixture
|
||||
**When** `pytest tests/integration/test_terrain_z9_tiles.py -v`
|
||||
**Then**:
|
||||
- При наличии тайлов `data/terrain/hillshade/9/*` — тесты
|
||||
проходят: 200 на существующий тайл, 404 на невалидный zoom.
|
||||
- При отсутствии тайлов в CI — тесты `skipped` с reason.
|
||||
|
||||
## AC-17 — Регрессионные тесты ET-007 / PH-6
|
||||
|
||||
**Given** ветка
|
||||
**When** `pytest tests/unit/ tests/integration/ -v`
|
||||
**Then**:
|
||||
- Все существующие тесты ET-007 (переключатель Схема/Спутник)
|
||||
и PH-6 проходят без регрессий.
|
||||
- Никакие тесты grandfather'ов не отвалились.
|
||||
|
||||
## AC-18 — `make lint` и `make test` зелёные
|
||||
|
||||
**Given** ветка
|
||||
**When** `make lint && make test`
|
||||
**Then** exit-code 0 на обе команды.
|
||||
|
||||
## AC-19 — Pre-deploy проверка наличия тайлов z9-z11
|
||||
|
||||
**Given** ветка готова к merge
|
||||
**When** на test-среде
|
||||
```bash
|
||||
curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png
|
||||
curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png
|
||||
curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/11/1234/635.png
|
||||
```
|
||||
**Then** все три запроса возвращают HTTP 200. Если 404 на любой —
|
||||
merge приостанавливается, открывается PH-6 follow-up (догенерить
|
||||
тайлы).
|
||||
|
||||
## AC-20 — Документация полная
|
||||
|
||||
**Given** репо после слияния ET-013
|
||||
**When** проверка `docs/work-items/ET-013/`
|
||||
**Then** существуют:
|
||||
- `00-business-request.md`
|
||||
- `01-brd.md`
|
||||
- `02-trz.md`
|
||||
- `03-acceptance-criteria.md`
|
||||
- `04-test-plan.yaml`
|
||||
- `04b-ui-test-cases.md`
|
||||
- `12-review.md` (после Review)
|
||||
- `13-test-report.md` (после Тестирования)
|
||||
- `14-deploy-log.md` (после Деплоя)
|
||||
|
||||
## AC-21 — Сетевая регрессия (M-10)
|
||||
|
||||
**Given** test-среда
|
||||
**When** сценарий: открыть карту, центр над Окой, выполнить
|
||||
zoom-последовательность z=8 → z=9 → z=10 → z=11 → z=10 → z=9 → z=8
|
||||
с включёнными обоими слоями
|
||||
**Then**:
|
||||
- Суммарный network-traffic PNG-тайлов рельефа ≤ 135% от того же
|
||||
сценария до ET-013 (зафиксированного как baseline в
|
||||
`13-test-report.md`).
|
||||
- Никаких сторонних запросов (например, 4xx или 5xx) не возникает.
|
||||
|
||||
## AC-22 — Контракт `applyTerrainLayer` обратно-совместим
|
||||
|
||||
**Given** ветка
|
||||
**When** unit-тест UT-PAINT-COMPAT-01
|
||||
**Then**:
|
||||
- Вызов `applyTerrainLayer(id, url, true, 0.5, 8, 14)`
|
||||
(старый контракт — число) собирает paint:
|
||||
`{ 'raster-opacity': 0.5, 'raster-resampling': 'linear' }`.
|
||||
- Вызов с object'ом передаёт paint как есть.
|
||||
336
docs/work-items/ET-013/04-test-plan.yaml
Normal file
336
docs/work-items/ET-013/04-test-plan.yaml
Normal file
@@ -0,0 +1,336 @@
|
||||
---
|
||||
type: test-plan
|
||||
work_item_id: ET-013
|
||||
title: "Test Plan: Перепады высот на z9-z11"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
related:
|
||||
- "PH-6.terrain"
|
||||
- "ET-007"
|
||||
|
||||
scope_note: >
|
||||
ET-013 — frontend-калибровка: понижает UI-минзум hillshade с 10 до 9
|
||||
и переводит paint-параметры (raster-opacity, raster-contrast,
|
||||
raster-resampling) hillshade и TRI в zoom-aware форму. Backend
|
||||
и pipeline растровых тайлов не трогаются. Тест-план фокусируется
|
||||
на:
|
||||
(1) корректности новых zoom-tier paint-выражений;
|
||||
(2) обратной совместимости applyTerrainLayer;
|
||||
(3) визуальной читаемости перепадов на z9-z11;
|
||||
(4) регрессии z8 (TRI не изменился), z14 (hillshade не перегрет);
|
||||
(5) совместимости с тёмной темой и спутниковой подложкой;
|
||||
(6) что network-объём не уплыл больше +35%.
|
||||
|
||||
test_suites:
|
||||
|
||||
- name: unit-terrain-paint
|
||||
type: unit
|
||||
description: "Структура paint-выражений HILLSHADE_PAINT и TRI_PAINT"
|
||||
cases:
|
||||
- id: UT-PAINT-HS-OPACITY
|
||||
name: "HILLSHADE_PAINT: raster-opacity — interpolate с правильными stops"
|
||||
input: |
|
||||
Python-парсер: чтение src/web/app.js, regex по блоку
|
||||
HILLSHADE_PAINT = { ... }; вытаскивание raster-opacity.
|
||||
expected: |
|
||||
Тип: ['interpolate', ['linear'], ['zoom'], ...].
|
||||
Stops содержат: (9, 0.65), (10, 0.60), (11, 0.55),
|
||||
(12, 0.50), (14, 0.40). Допустимо отклонение значений ±0.05
|
||||
(калибровка) — но порядок монотонно убывающий от 9 к 14.
|
||||
|
||||
- id: UT-PAINT-HS-CONTRAST
|
||||
name: "HILLSHADE_PAINT: raster-contrast — пик на z9, 0 на z14"
|
||||
input: |
|
||||
Тот же парсер.
|
||||
expected: |
|
||||
Тип interpolate. Значение на z=9 ≥ 0.30. Значение на z=14
|
||||
≤ 0.10. Монотонно убывает.
|
||||
|
||||
- id: UT-PAINT-HS-RESAMPLING
|
||||
name: "HILLSHADE_PAINT: raster-resampling = 'nearest'"
|
||||
input: |
|
||||
Парсер.
|
||||
expected: |
|
||||
Строка 'nearest' (не 'linear').
|
||||
|
||||
- id: UT-PAINT-TRI-OPACITY-Z8
|
||||
name: "TRI_PAINT: на z8 opacity = 0.70 (регрессия)"
|
||||
input: |
|
||||
Парсер по TRI_PAINT.
|
||||
expected: |
|
||||
Stop (8, 0.70) присутствует ровно (без округления).
|
||||
|
||||
- id: UT-PAINT-TRI-OPACITY-PEAK
|
||||
name: "TRI_PAINT: пик на z9-z11"
|
||||
input: |
|
||||
Парсер.
|
||||
expected: |
|
||||
Stops содержат (10, X) с X ≥ 0.80 и (11, Y) с Y ≥ 0.80.
|
||||
|
||||
- id: UT-PAINT-TRI-RESAMPLING
|
||||
name: "TRI_PAINT: raster-resampling = 'nearest'"
|
||||
input: |
|
||||
Парсер.
|
||||
expected: |
|
||||
'nearest'.
|
||||
|
||||
- id: UT-PAINT-COMPAT-01
|
||||
name: "applyTerrainLayer обратно-совместим с числовым opacity"
|
||||
input: |
|
||||
Вызов с opacity=0.5 (Node + JSDOM-mock карты).
|
||||
expected: |
|
||||
Внутри map.addLayer передан paint:
|
||||
{ 'raster-opacity': 0.5, 'raster-resampling': 'linear' }.
|
||||
notes: |
|
||||
Если запуск JS-теста не настроен — заменить на статический
|
||||
grep по src/web/app.js: проверить ветвление
|
||||
'typeof opacityOrPaint === "number"'.
|
||||
|
||||
- id: UT-PAINT-COMPAT-02
|
||||
name: "applyTerrainLayer принимает paint-объект"
|
||||
input: |
|
||||
Вызов с opacityOrPaint = { 'raster-opacity': 0.4,
|
||||
'raster-contrast': 0.2, 'raster-resampling': 'nearest' }.
|
||||
expected: |
|
||||
Этот объект передан в map.addLayer paint как есть.
|
||||
|
||||
- id: UT-REG-MINZOOM-9
|
||||
name: "updateHillshadeAvailability порог = 9"
|
||||
input: |
|
||||
grep по src/web/app.js: 'if (zoom < 9)' внутри функции
|
||||
updateHillshadeAvailability.
|
||||
expected: |
|
||||
Совпадение найдено; 'if (zoom < 10)' отсутствует.
|
||||
|
||||
- id: UT-REG-HINT-TEXT
|
||||
name: "Hint текст обновлён до 'Зум 9+'"
|
||||
input: |
|
||||
grep по src/web/index.html: '#terrain-hillshade-hint'
|
||||
содержит 'Зум 9+'.
|
||||
expected: |
|
||||
Совпадение найдено; 'Зум 10+' отсутствует.
|
||||
|
||||
- id: UT-REG-CALLERS
|
||||
name: "applyTerrainLayer вызывается ровно дважды в onTerrainCheckbox"
|
||||
input: |
|
||||
regex 'applyTerrainLayer\s*\(' в src/web/*.js — count.
|
||||
expected: |
|
||||
Минимум 2 вызова в src/web/app.js. Все они находятся
|
||||
внутри функции onTerrainCheckbox.
|
||||
|
||||
- name: integration-terrain-tiles
|
||||
type: integration
|
||||
description: "Endpoint /terrain/{layer}/{z}/{x}/{y}.png на z9-z11"
|
||||
cases:
|
||||
- id: IT-TILE-Z9-01
|
||||
name: "Тайл z=9 для hillshade: 200 или skipped если данных нет"
|
||||
input: |
|
||||
Test-среда или CI с TERRAIN_DIR. Найти первый существующий
|
||||
тайл z9 в директории hillshade, выполнить GET.
|
||||
expected: |
|
||||
Если data/terrain/hillshade/9/ существует:
|
||||
status 200, content-type image/png, тело > 0.
|
||||
Иначе:
|
||||
test skipped с reason 'PH-6 data not in repo'.
|
||||
|
||||
- id: IT-TILE-Z10-01
|
||||
name: "Тайл z=10 для hillshade: 200 или skipped"
|
||||
input: |
|
||||
То же, что IT-TILE-Z9-01 для z=10.
|
||||
expected: |
|
||||
status 200 или skipped.
|
||||
|
||||
- id: IT-TILE-Z11-01
|
||||
name: "Тайл z=11 для hillshade: 200 или skipped"
|
||||
input: |
|
||||
То же для z=11.
|
||||
expected: |
|
||||
status 200 или skipped.
|
||||
|
||||
- id: IT-TILE-TRI-Z9
|
||||
name: "TRI на z9 доступен (минзум 5, тайлы должны быть)"
|
||||
input: |
|
||||
GET tiles/9/X/Y.png под TRI.
|
||||
expected: |
|
||||
200 или skipped (если данных нет на CI).
|
||||
|
||||
- id: IT-TILE-INVALID-LAYER
|
||||
name: "Неизвестный layer → 404 (регрессия)"
|
||||
input: |
|
||||
GET /terrain/unknown/9/0/0.png
|
||||
expected: |
|
||||
status 404.
|
||||
|
||||
- id: IT-TILE-MISSING
|
||||
name: "Несуществующий тайл → 404 (регрессия)"
|
||||
input: |
|
||||
GET /terrain/hillshade/9/99999/99999.png
|
||||
expected: |
|
||||
status 404.
|
||||
|
||||
- id: IT-TILE-CACHE-HEADER
|
||||
name: "Cache-Control: immutable сохраняется"
|
||||
input: |
|
||||
GET существующего тайла.
|
||||
expected: |
|
||||
Header 'Cache-Control' содержит 'immutable' и max-age=31536000.
|
||||
|
||||
- name: regression-existing
|
||||
type: regression
|
||||
description: "Регрессия ET-007 / PH-6 / общих unit-тестов"
|
||||
cases:
|
||||
- id: RG-UNIT-ALL
|
||||
name: "Все unit-тесты проекта зелёные"
|
||||
input: "pytest tests/unit/ -v"
|
||||
expected: "exit-code 0"
|
||||
|
||||
- id: RG-INTEG-ALL
|
||||
name: "Все integration-тесты проекта зелёные"
|
||||
input: "pytest tests/integration/ -v"
|
||||
expected: "exit-code 0"
|
||||
|
||||
- id: RG-LINT
|
||||
name: "Линтеры зелёные"
|
||||
input: "make lint"
|
||||
expected: "exit-code 0"
|
||||
|
||||
- name: ui-playwright
|
||||
type: ui
|
||||
description: "Playwright UI-тесты на test-среде"
|
||||
reference: "04b-ui-test-cases.md"
|
||||
cases:
|
||||
- id: UI-LINK-01
|
||||
name: "См. 04b-ui-test-cases.md — TC-UI-01..TC-UI-12"
|
||||
expected: |
|
||||
Каждый TC выполняется; check-visual подтверждается
|
||||
оператором либо визуальным diff-инструментом
|
||||
(baseline до ET-013 vs текущий).
|
||||
|
||||
- name: manual-deploy-validation
|
||||
type: e2e
|
||||
description: "Ручная проверка в test-среде после деплоя"
|
||||
marker: "manual"
|
||||
cases:
|
||||
- id: E2E-PRE-DEPLOY-01
|
||||
name: "Pre-deploy: тайлы z9-z11 на test-среде доступны"
|
||||
steps:
|
||||
- "curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png | head -1"
|
||||
- "curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png | head -1"
|
||||
- "curl -sI https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/11/1234/635.png | head -1"
|
||||
- "Все три — HTTP/1.1 200 OK. При 404 — стоп, открыть PH-6 follow-up."
|
||||
- "Зафиксировать в 14-deploy-log.md."
|
||||
|
||||
- id: E2E-DEPLOY-01
|
||||
name: "Hillshade доступен на z=9"
|
||||
steps:
|
||||
- "Открыть https://openclaw.mva154.duckdns.org/enduro/"
|
||||
- "localStorage.clear(); location.reload()"
|
||||
- "Click #terrain-toggle"
|
||||
- "В Console: window._map.setZoom(9); window._map.setCenter([37.6, 54.5])"
|
||||
- "Wait 2s"
|
||||
- "Кнопка #terrain-hillshade-cb имеет disabled=false"
|
||||
- "Hint #terrain-hillshade-hint имеет display:none"
|
||||
- "Click #terrain-hillshade-cb"
|
||||
- "Wait 3s"
|
||||
- "На карте видны тени"
|
||||
- "Screenshot et013-deploy-z9.png"
|
||||
- "Зафиксировать в 14-deploy-log.md"
|
||||
|
||||
- id: E2E-DEPLOY-02
|
||||
name: "Network-объём: рост ≤ 35%"
|
||||
steps:
|
||||
- "Открыть DevTools Network, фильтр /terrain/"
|
||||
- "Очистить network log"
|
||||
- "В Console: window._map.setZoom(8); ждать 3s; setZoom(9); ждать 3s; setZoom(10); ждать 3s; setZoom(11); ждать 3s"
|
||||
- "Замерить суммарный transferred size в фильтре /terrain/"
|
||||
- "Сравнить с baseline (записан в 13-test-report.md до ET-013): рост ≤ 135%"
|
||||
- "Зафиксировать"
|
||||
|
||||
- id: E2E-DEPLOY-03
|
||||
name: "Регрессия z=8 (TRI выглядит как до ET-013)"
|
||||
steps:
|
||||
- "localStorage.clear(); location.reload()"
|
||||
- "Включить только #terrain-tri-cb (без hillshade)"
|
||||
- "window._map.setZoom(8); setCenter([37.6, 54.5])"
|
||||
- "Screenshot et013-deploy-z8-tri-regress.png"
|
||||
- "Визуально сравнить с baseline из 13-test-report.md до ET-013 — не отличается заметно."
|
||||
|
||||
- id: E2E-DEPLOY-04
|
||||
name: "Регрессия z=14 (hillshade не перегрет)"
|
||||
steps:
|
||||
- "Включить #terrain-hillshade-cb"
|
||||
- "window._map.setZoom(14); setCenter([37.6, 54.5])"
|
||||
- "Screenshot et013-deploy-z14-regress.png"
|
||||
- "Эффективное raster-opacity ≈ 0.40, raster-contrast ≈ 0"
|
||||
- "В Console: window._map.getPaintProperty('terrain-hillshade', 'raster-opacity')"
|
||||
- "(вернёт interpolate-выражение — proof zoom-aware)"
|
||||
|
||||
- id: E2E-DEPLOY-05
|
||||
name: "Спутник + hillshade на z=10 (R-3)"
|
||||
steps:
|
||||
- "Включить hillshade, переключить #base-btn-satellite"
|
||||
- "window._map.setZoom(10); setCenter([37.6, 54.5])"
|
||||
- "Screenshot et013-deploy-z10-sat.png"
|
||||
- "Визуальная приёмка: hillshade видим, не глушит снимок"
|
||||
- "При проблеме — задача отправляется на корректировку stops"
|
||||
|
||||
- id: E2E-DEPLOY-06
|
||||
name: "Тёмная тема + hillshade на z=10 (R-2)"
|
||||
steps:
|
||||
- "Click #btn-theme (переключить в тёмную)"
|
||||
- "window._map.setZoom(10)"
|
||||
- "Screenshot et013-deploy-z10-dark.png"
|
||||
- "Визуальная приёмка: hillshade читается, не сливается с тёмной подложкой"
|
||||
|
||||
- id: E2E-DEPLOY-07
|
||||
name: "Persistence: F5 не теряет состояние"
|
||||
steps:
|
||||
- "Включить оба чекбокса"
|
||||
- "location.reload()"
|
||||
- "Чекбоксы остаются включёнными"
|
||||
- "На текущем zoom оба слоя восстановлены"
|
||||
|
||||
test_data:
|
||||
fixtures_dir: "tests/fixtures/terrain/"
|
||||
fixtures:
|
||||
- name: "hillshade-z9-sample.png"
|
||||
description: |
|
||||
Опционально: один валидный PNG-тайл из data/terrain/hillshade/9/
|
||||
для CI-окружения без полного набора данных. Скопировать любой
|
||||
тайл над ЦФО, переименовать. ~10 KB.
|
||||
- name: "hillshade-z10-sample.png"
|
||||
description: "То же для z10."
|
||||
- name: "tri-z10-sample.png"
|
||||
description: "TRI sample для z10."
|
||||
notes:
|
||||
- "Если на CI нет TERRAIN_DIR с данными — IT-TILE-* тесты skipped (REQ-F-15)."
|
||||
- "Сравнения 'до/после' визуальные — baseline скриншоты лежат в 13-test-report.md и фиксируются до начала ET-013."
|
||||
- "Для unit-тестов paint никаких fixture не нужно — парсинг исходника."
|
||||
|
||||
test_environment:
|
||||
unit:
|
||||
- "Python 3.12, pytest"
|
||||
- "regex-парсер src/web/app.js (Вариант B в TRZ REQ-F-13)"
|
||||
- "Опционально Node + JSDOM, если в проекте появятся JS-тесты"
|
||||
integration:
|
||||
- "FastAPI TestClient против src.api.main:app"
|
||||
- "TERRAIN_DIR через env или skip-if-missing"
|
||||
performance:
|
||||
- "Не требуется специально: NFR-01/02 говорят о невидимом изменении render-time"
|
||||
- "Сетевой объём — ручной замер в DevTools Network (E2E-DEPLOY-02)"
|
||||
e2e:
|
||||
- "Test-среда https://openclaw.mva154.duckdns.org/enduro/"
|
||||
- "Playwright (см. 04b-ui-test-cases.md)"
|
||||
|
||||
ci_gates:
|
||||
- "Unit UT-PAINT-* и UT-REG-* — обязательны (AC-15)"
|
||||
- "Integration IT-TILE-* — обязательны (с skipif для отсутствующих данных) (AC-16)"
|
||||
- "Регрессия RG-UNIT-ALL, RG-INTEG-ALL, RG-LINT — обязательны (AC-17, AC-18)"
|
||||
- "Pre-deploy E2E-PRE-DEPLOY-01 — ручной gate перед merge (AC-19)"
|
||||
- "UI-тесты Playwright — после деплоя, фиксация в 13-test-report.md"
|
||||
- "E2E-DEPLOY-01..07 — ручные шаги в 14-deploy-log.md"
|
||||
---
|
||||
386
docs/work-items/ET-013/04b-ui-test-cases.md
Normal file
386
docs/work-items/ET-013/04b-ui-test-cases.md
Normal file
@@ -0,0 +1,386 @@
|
||||
---
|
||||
type: ui-test-cases
|
||||
work_item_id: ET-013
|
||||
title: "UI Test Cases: Перепады высот на z9-z11"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
related:
|
||||
- "PH-6.terrain"
|
||||
- "ET-007"
|
||||
---
|
||||
|
||||
# UI Test Cases — ET-013: Перепады высот на zoom z9-z11
|
||||
|
||||
Базовый URL: `https://openclaw.mva154.duckdns.org/enduro/`
|
||||
|
||||
ET-013 — frontend-калибровка: hillshade и TRI используют
|
||||
zoom-aware paint, UI-минзум hillshade понижен с 10 до 9. UI-тесты
|
||||
проверяют:
|
||||
|
||||
1. На z9 чекбокс «Тени рельефа» активен, hint скрыт, hillshade виден.
|
||||
2. На z9-z11 перепады «бросаются в глаза» (качественно).
|
||||
3. На z8 регрессии нет (TRI выглядит как было).
|
||||
4. На z14 hillshade не «перегрет» (регрессия).
|
||||
5. Тёмная тема и спутник совместимы.
|
||||
6. Мобильный viewport работает.
|
||||
7. Persistence (localStorage) переживает F5.
|
||||
|
||||
Селекторы (из текущего `index.html`):
|
||||
- `#terrain-toggle` — кнопка попапа слоёв рельефа (правая панель).
|
||||
- `#terrain-popup` — сам попап со списком чекбоксов.
|
||||
- `#terrain-hillshade-cb` — чекбокс «Тени рельефа».
|
||||
- `#terrain-hillshade-hint` — hint «Зум 9+» (ET-013) / «Зум 10+» (до ET-013).
|
||||
- `#terrain-tri-cb` — чекбокс «Перепады».
|
||||
- `#base-btn-satellite` — кнопка спутника.
|
||||
- `#btn-theme` — переключатель тёмная/светлая.
|
||||
- `#map` — карта.
|
||||
|
||||
Все тесты выставляют zoom программно через `page.evaluate`:
|
||||
```js
|
||||
window._map.setZoom(N);
|
||||
window._map.setCenter([37.6, 54.5]); // юг МО / Ока, холмистый район
|
||||
```
|
||||
|
||||
Координата `[37.6, 54.5]` (юг Москвы / Кашира / Ока) выбрана как
|
||||
«заведомо холмистая зона ЦФО» с явным TRI/hillshade.
|
||||
|
||||
Скриншоты складываются в `docs/work-items/ET-013/screenshots/`
|
||||
и пришиваются к `13-test-report.md`. Для качественных AC-07/08/09
|
||||
оператор сравнивает с baseline скриншотами «до ET-013» (тоже в
|
||||
`screenshots/baseline/`).
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-01-Z9 — На z=9 hillshade доступен и виден
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. evaluate: window._map.setZoom(9); window._map.setCenter([37.6, 54.5]);
|
||||
8. wait: 3000
|
||||
9. click: "#terrain-toggle"
|
||||
10. wait: 800
|
||||
11. screenshot: "et013-01-z9-popup"
|
||||
12. check-visual: "В попапе #terrain-popup чекбокс «Тени рельефа» (#terrain-hillshade-cb) НЕ disabled, текст не серый. Hint #terrain-hillshade-hint имеет display:none (текст «Зум 9+» не виден). Чекбокс «Перепады» (#terrain-tri-cb) также доступен."
|
||||
13. click: "#terrain-hillshade-cb"
|
||||
14. click: "#terrain-tri-cb"
|
||||
15. wait: 4000
|
||||
16. screenshot: "et013-01-z9-tracks-visible"
|
||||
17. check-visual: "На карте при zoom=9 виден район юга Москвы / Оки. Поверх подложки нарисованы тени рельефа (hillshade) — тёмные склоны заметны на холмах вдоль реки. TRI («Перепады») рисует цветные пятна шероховатых зон. Оба слоя читаются, рельеф выразительный."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-02-Z8-REGRESS — Регрессия z=8: TRI выглядит как до ET-013
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. wait: 800
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 2000
|
||||
11. evaluate: window._map.setZoom(8); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 4000
|
||||
13. screenshot: "et013-02-z8-tri-regress"
|
||||
14. check-visual: "На z=8 виден слой «Перепады» в опубликованном виде PH-6: opacity ~0.70, ресемпл «жёсткий» (граница 30-метровых клеток SRTM может быть видна, но это норма после ET-013). Слой hillshade выключен. Сравнение с baseline скриншотом 'before-ET-013-z8.png' — визуально близко, без явных регрессий."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-03-Z9-Q — Качественная читаемость перепадов на z=9
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
- условие: оба слоя включены
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. wait: 500
|
||||
9. click: "#terrain-hillshade-cb"
|
||||
10. click: "#terrain-tri-cb"
|
||||
11. wait: 2000
|
||||
12. evaluate: window._map.setZoom(9); window._map.setCenter([37.6, 54.5]);
|
||||
13. wait: 5000
|
||||
14. screenshot: "et013-03-z9-readable"
|
||||
15. check-visual: "На z=9 рельеф читается явно: тени по склонам холмов, цветные пятна TRI выделяют шероховатые зоны (склоны вдоль Оки, овраги). Не должно быть впечатления 'плоской карты'. Оператор сравнивает с baseline 'before-ET-013-z9.png' и подтверждает: 'перепады стали выразительнее' или 'минимум не хуже z8'. При отказе — фиксировать в 13-test-report.md и итеративно корректировать stops в HILLSHADE_PAINT/TRI_PAINT."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-04-Z10-Q — Качественная читаемость на z=10
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 2000
|
||||
11. evaluate: window._map.setZoom(10); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 5000
|
||||
13. screenshot: "et013-04-z10-readable"
|
||||
14. check-visual: "На z=10 в фокусе несколько холмов с явными склонами. Hillshade рисует тени с выраженным контрастом (raster-contrast 0.35 в paint-выражении). TRI выделяет шероховатости. Сравнение с baseline 'before-ET-013-z10.png' — стало явно выразительнее. Подложка под слоями ещё читается (opacity 0.60 + 0.85 не превращают карту в кашу)."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-05-Z11-Q — Качественная читаемость на z=11
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 2000
|
||||
11. evaluate: window._map.setZoom(11); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 5000
|
||||
13. screenshot: "et013-05-z11-readable"
|
||||
14. check-visual: "На z=11 виден небольшой район (несколько км в кадре). Перепады «прорисованы», отдельные склоны различимы. Сравнение с baseline 'before-ET-013-z11.png' — выразительнее. Дороги/грунтовки/POI остаются читаемыми поверх рельефа (z-order: terrain ниже trails/POI, проверено по applyTerrainLayer)."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-06-Z14-REGRESS — Регрессия z=14: hillshade не перегрет
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. wait: 2000
|
||||
10. evaluate: window._map.setZoom(14); window._map.setCenter([37.6, 54.5]);
|
||||
11. wait: 5000
|
||||
12. screenshot: "et013-06-z14-regress"
|
||||
13. check-visual: "На z=14 hillshade выглядит так, как до ET-013: лёгкая «плёнка» теней с opacity ≈ 0.40 и raster-contrast ≈ 0. Никакого перегретого контраста. Подложка отчётливо видна. Сравнение с baseline 'before-ET-013-z14.png' — без отличий."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-07-Z9-MOBILE — Hillshade на мобильном viewport на z=9
|
||||
|
||||
- тип: ui
|
||||
- viewport: mobile
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. evaluate: window._map.setZoom(9); window._map.setCenter([37.6, 54.5]);
|
||||
8. wait: 3000
|
||||
9. click: "#terrain-toggle"
|
||||
10. wait: 800
|
||||
11. screenshot: "et013-07-z9-mobile-popup"
|
||||
12. check-visual: "На мобильном viewport (375×667) попап рельефа открыт, чекбокс «Тени рельефа» доступен, hint скрыт. Чекбокс «Перепады» доступен. Layout не сломан."
|
||||
13. click: "#terrain-hillshade-cb"
|
||||
14. click: "#terrain-tri-cb"
|
||||
15. wait: 4000
|
||||
16. screenshot: "et013-07-z9-mobile-tracks"
|
||||
17. check-visual: "На мобильном на z=9 видны тени рельефа и пятна TRI. Перепады читаются. Layout верхней/нижней панелей не перекрывает карту."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-08-Z10-SAT-Q — Спутник + hillshade на z=10
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#base-btn-satellite"
|
||||
9. wait: 4000
|
||||
10. click: "#terrain-hillshade-cb"
|
||||
11. wait: 2000
|
||||
12. evaluate: window._map.setZoom(10); window._map.setCenter([37.6, 54.5]);
|
||||
13. wait: 5000
|
||||
14. screenshot: "et013-08-z10-sat"
|
||||
15. check-visual: "На спутниковой подложке поверх космоснимка видны тени hillshade. Подложка под ними различима — деревья, реки, поля по-прежнему читаются. Hillshade не превращает снимок в «серую плёнку». При отказе (слой глушит снимок) — открыть итерацию: либо снизить opacity на спутнике через отдельный layer-paint, либо документировать как known issue."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-09-Z10-DARK-Q — Тёмная тема + hillshade на z=10
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: localStorage.setItem('theme', 'dark'); location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 2000
|
||||
11. evaluate: window._map.setZoom(10); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 5000
|
||||
13. screenshot: "et013-09-z10-dark"
|
||||
14. check-visual: "На тёмной теме при z=10 видны и hillshade, и TRI. Тени не сливаются с тёмной подложкой. Цвета TRI читаются. Если визуально слои «съедают карту» — фиксируется как известная проблема для будущей итерации (theme-specific paint, ADR-0001 в follow-up)."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-10-PERSIST — Состояние слоёв переживает F5
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 1500
|
||||
11. evaluate: window._map.setZoom(10); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 4000
|
||||
13. screenshot: "et013-10a-before-reload"
|
||||
14. check-visual: "Оба слоя видны на z=10."
|
||||
15. evaluate: location.reload();
|
||||
16. wait: 6000
|
||||
17. evaluate: window._map.setZoom(10); window._map.setCenter([37.6, 54.5]);
|
||||
18. wait: 4000
|
||||
19. screenshot: "et013-10b-after-reload"
|
||||
20. check-visual: "После reload оба слоя автоматически восстановились (через restoreTerrainState). Чекбоксы в #terrain-popup всё ещё checked. localStorage 'terrain-hillshade'='1', 'terrain-tri'='1'."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-11-NETWORK-Q — Сетевой объём (M-10)
|
||||
|
||||
- тип: ui (network)
|
||||
- viewport: desktop
|
||||
- инструмент: DevTools Network
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. open: DevTools Network, filter "/terrain/"
|
||||
8. clear network log
|
||||
9. click: "#terrain-toggle"
|
||||
10. click: "#terrain-hillshade-cb"
|
||||
11. click: "#terrain-tri-cb"
|
||||
12. evaluate: window._map.setZoom(8); window._map.setCenter([37.6, 54.5]);
|
||||
13. wait: 3500
|
||||
14. evaluate: window._map.setZoom(9);
|
||||
15. wait: 3500
|
||||
16. evaluate: window._map.setZoom(10);
|
||||
17. wait: 3500
|
||||
18. evaluate: window._map.setZoom(11);
|
||||
19. wait: 3500
|
||||
20. record: суммарный transferred size в Network
|
||||
21. check-visual: "Сравнение с baseline 'before-ET-013-network-z8-z11.txt' (записанным до начала ET-013): рост ≤ 135%. Если выше — анализ: какие тайлы добавились, оправдано ли. Фиксация в 13-test-report.md."
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-12-Z9-PAN — Панорамирование на z=9 без лагов
|
||||
|
||||
- тип: ui
|
||||
- viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 5000
|
||||
3. evaluate: localStorage.clear();
|
||||
4. wait: 500
|
||||
5. evaluate: location.reload();
|
||||
6. wait: 5000
|
||||
7. click: "#terrain-toggle"
|
||||
8. click: "#terrain-hillshade-cb"
|
||||
9. click: "#terrain-tri-cb"
|
||||
10. wait: 2000
|
||||
11. evaluate: window._map.setZoom(9); window._map.setCenter([37.6, 54.5]);
|
||||
12. wait: 5000
|
||||
13. evaluate: window._map.panBy([400, 0]);
|
||||
14. wait: 3000
|
||||
15. evaluate: window._map.panBy([0, 400]);
|
||||
16. wait: 3000
|
||||
17. evaluate: window._map.panBy([-400, 0]);
|
||||
18. wait: 3000
|
||||
19. screenshot: "et013-12-z9-pan"
|
||||
20. check-visual: "После трёх pan-шагов карта показывает соседние регионы. Тайлы догружены, нет 'белых дыр' в hillshade/TRI. Возврат к исходному центру — мгновенный (browser cache). UI не блокируется, нет визуальных лагов."
|
||||
|
||||
---
|
||||
|
||||
### Заметки по запуску
|
||||
|
||||
- TC-UI-03..05 (Q-критерии) — качественные. Оператор сравнивает
|
||||
скриншот с baseline («до ET-013»). Baseline записывается **до**
|
||||
начала разработки ET-013 и кладётся в
|
||||
`docs/work-items/ET-013/screenshots/baseline/`.
|
||||
- TC-UI-08 (SAT-Q) и TC-UI-09 (DARK-Q) — допустимо «known issue»
|
||||
с фиксацией в `13-test-report.md`. Если визуальная регрессия
|
||||
обнаружена — открывается follow-up задача по theme/sat-specific paint.
|
||||
- При отказе TC-UI-03/04/05 — корректировка stops в
|
||||
`HILLSHADE_PAINT`/`TRI_PAINT`, новый прогон. Это калибровка, а не баг.
|
||||
- При отказе TC-UI-06 (z14 регрессия) — баг калибровки stops,
|
||||
должен быть исправлен.
|
||||
- TC-UI-11 (NETWORK-Q) — pre/post замеры; baseline записывается
|
||||
до старта работ над ET-013.
|
||||
|
||||
### Координаты для тестов
|
||||
|
||||
| Координаты | Регион | Зачем |
|
||||
|---|---|---|
|
||||
| `[37.6, 54.5]` | юг МО / Кашира / Ока | холмистый, выраженный hillshade и TRI |
|
||||
| `[37.6, 55.7]` | центр Москвы | плоский, контроль «город всё равно читается» (опционально) |
|
||||
| `[38.6, 54.0]` | Тула | холмы юга ЦФО, альтернатива для AC-08 |
|
||||
|
||||
По умолчанию все TC используют `[37.6, 54.5]`.
|
||||
Reference in New Issue
Block a user