# Спецификация 04: настройки поля и вибрации Источники: module_reqs разд. 1 (единицы 6–7), Ф-2.2 (границы зон — настройки поля), Ф-2.4 errata (длительность отдыха — настройка, умолчание `Rest.DefaultSec`), решение гейта 1.3 № 3 (sideload живёт на умолчаниях) и № 5 (красная зона процентная). Единицы 6–7. ## 1. Properties (resources\settings\settings.xml) | Свойство | Тип | Диапазон редактора | Умолчание | Смысл | |---|---|---|---|---| | `restSec` | numeric | 30–600 | 120 | длительность автономного таймера отдыха, с (= `Rest.DefaultSec` телефона) | | `hrMaxBpm` | numeric | 0–250 | 0 | макс. ЧСС для зон; 0 — расчёт по формуле Танаки от `age` | | `age` | numeric | 14–100 | 45 | возраст для формулы Танаки (208 − 0,7 × возраст) | - подписи/описания — ru/en (строки в settings.xml + strings); - значения читаются `FieldSettings` при `initialize()`; смена настроек применяется при следующем запуске активности (поле перезапускается платформой); - правка значений на устройстве — только через Garmin Connect Mobile/Express для приложений из магазина CIQ (решение 3 гейта 1.3): sideload-сборка живёт на умолчаниях, смена поведения = пересборка. ## 2. `FieldSettings` (клампы — защита от кривых значений) Чистый класс: чтение property → кламп → поле: - `restSec`: вне 30–600 или не число → 120; - `hrMaxBpm`: вне 0–250 → 0 (авто); - `age`: вне 14–100 → 45. `maxBpm()` delegирует в `ZoneCalc.maxBpmFor(hrMaxBpm, age)`. ## 3. Вибрации (`Vibe.mc`) `Attention.vibrate([...])`, до 8 `VibeProfile(duty, мс)`: | Событие | Паттерн | Вызов | |---|---|---| | конец отдыха (RestTimer.onEnd == true) | `[VibeProfile(25, 300)]` — однократная лёгкая | из `compute` | | красная зона (Vibe-гистерезис сработал) | `[VibeProfile(75, 200), VibeProfile(0, 150), VibeProfile(75, 200)]` — двойная заметная | из `compute` | - вибрация различается паттерном (ui-ux: паттерны различаются — отдых/предупреждение); - недоступность вибрации на модели — вызов молча пропускается (проверка наличия/`try-catch`; инвариант 4, Ф-2.8); - гистерезис красной зоны: повторная двойная не раньше 60 000 мс; выход в GREEN/BLUE/NONE сбрасывает (спека 02 §4); - вибрации не стекаются: не чаще одной причины за проход `compute`. ## 4. Тесты (Toybox.Test, дополняют спеку 02) `FieldSettings` (чистые клампы): 1. `clampRest(10) == 120`, `clampRest(600) == 600`, `clampRest(30) == 30`; 2. `clampRest(null) == 120` (property отсутствует); 3. `clampHr(-5) == 0`, `clampHr(250) == 250`; 4. `clampAge(200) == 45`, `clampAge(14) == 14`; 5. `maxBpm()`: hrMaxBpm 190 → 190; hrMaxBpm 0 + age 45 → 177. `mmss` (форматирование строки состояния, спека 03 §2): 6. `mmss(0) == "0:00"`, `mmss(59_999) == "0:59"`, `mmss(60_000) == "1:00"`, `mmss(65_000) == "1:05"`, `mmss(600_000) == "10:00"`.