From 22ca7cc24f5347defd1deeca89299c2441caffc1 Mon Sep 17 00:00:00 2001 From: Andrew Pimenov Date: Thu, 18 Jun 2026 03:47:42 +0300 Subject: [PATCH] document profile mode, new flags, and tunable constants --- ARCHITECTURE.md | 22 ++++++++++++++++++++-- ARCHITECTURE.ru.md | 22 ++++++++++++++++++++-- README.md | 14 ++++++++++++-- README.ru.md | 14 ++++++++++++-- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3168a0e..caec867 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -17,7 +17,8 @@ telemetry/ report_technique.py Driving report (corners, straights, cost, trace). report_setup.py Car report (setup, balance, tyres, brakes, suspension, phases). report_compare.py Two+ laps side by side, per-corner and cumulative delta. - report_race.py A folder of laps: lap times (with position), stints, wear, thermals, ERS. + report_race.py A folder of laps: lap times (with position), stints, wear, thermals, ERS. Dedups duplicate lap numbers. + report_profile.py A folder of laps: cheap cross-lap corner tendencies (median per corner, per dominant compound) + auto-picked deep-dive laps. No trace. rename.py Insert session type + lap number (P1_L7) into filenames; with races_dir, sort into races/// and recurse subdirectories. gui.py tkinter window; per-mode browse memory; runs generation/rename on a worker thread. prompts/ LLM analysis prompts, ru/ and en/ subdirectories, one file per mode. Auto-appended to every report. @@ -30,7 +31,7 @@ tests/ pytest suite + fixture laps. 2. **`parser.load_lap`** reads the file as UTF-8 and splits it into four meta blocks (session, track, setup) plus the 108-channel telemetry table. Channel names are normalised by stripping the ` [unit]` suffix. Every column that is entirely zero is dropped — the game writes many dead channels (see Gotchas). 3. **`segments.detect_corners`** walks the samples, marks each as "cornering" when `|Steer| > STEER_ON` or `Brake > BRAKE_ON`, merges adjacent zones closer than `MERGE_GAP_M`, drops zones shorter than `MIN_LEN_M`, and numbers what's left T1..Tn by distance. For each zone it calls **`corner_metrics.corner_from_zone`**, which extracts the braking point, apex, gear, full-throttle point, peak brake/gLat, relative lock%/spin%, coast distance, and rear tyre temperature peak. `straights` fills the gaps between corners. 4. **`resample.adaptive_points`** builds a distance grid that is dense inside (and on approach to) corners and sparse on straights — the step sizes default to the lap's own median sample spacing via `auto_steps`, so denser raw telemetry yields a denser trace. **`sample_at`** interpolates each channel onto that grid (nearest-sample for discrete channels like Gear/DRS). -5. The **`report_.generate(…, lang)`** function assembles Markdown from `report_common` helpers (`header_block`, `md_table`, `legend`) and the data above. It then calls **`load_prompt(mode, lang)`** to read the matching prompt from `prompts//.md` and appends it after a `---` separator. Finally **`write_report`** writes atomically (temp file + `os.replace`) and returns `(abs_path, tokens)`. +5. The **`report_.generate(…, lang, include_prompt=True)`** function assembles Markdown from `report_common` helpers (`header_block`, `md_table`, `legend`) and the data above. When `include_prompt` is set it calls **`load_prompt(mode, lang)`** to read the matching prompt from `prompts//.md` and appends it after a `---` separator (CLI `--no-prompt` / GUI checkbox turn this off). Finally **`write_report`** writes atomically (temp file + `os.replace`) and returns `(abs_path, tokens)`. Before generating, `__main__` skips the run via **`is_fresh(report, sources)`** when an up-to-date report exists (override with `--force`). 6. **`__main__`** prints that path and `~N tokens (budget 60k)`. The **GUI** shows the same tuple in its log instead of printing. ## Adding things @@ -41,6 +42,23 @@ tests/ pytest suite + fixture laps. **A new corner metric.** Add a field to the `Corner` dataclass in `segments.py` and compute it in `corner_metrics.corner_from_zone`. Because `Corner` is frozen, set it through the constructor. +## Assumptions & tunables + +Two kinds of constants. **Detection** thresholds are in telemetry units and uniform across F1 22 — they decide *where* a boundary is, not the conclusion, so they are not track-tuned. **Interpretation** constants encode racing judgment that genuinely varies; reports always print the raw number next to any flag/verdict, so a debatable threshold never hides the data. + +| Constant | File | Kind | Basis / sensitivity | +|----------|------|------|---------------------| +| `STEER_ON 6`, `BRAKE_ON 5`, `BRAKE_MARK 20`, `FULL_THROTTLE 98` | `segments` / `corner_metrics` | detection | percent of input range, game-uniform | +| `MERGE_GAP_M 40` + `merge_gap_for` (clamp 28–60) | `segments` | detection | **adaptive**: scales with `Tracklen` around the Zandvoort anchor so chicanes/long circuits self-tune | +| `MIN_LEN_M 15`, `_SAFE_SPEED_FLOOR 30`, `0.3 s` throttle window | `segments` / `corner_metrics` | detection | geometry / divide guard / debounce | +| `resample` dense=median, sparse=8×median | `resample` | detection | self-tunes to sample rate | +| `LOCKUP −10 %`, `SPIN +8 %` | `segments` | interpretation | severe-only flags; raw `lock%`/`spin%` always shown | +| `FUEL 0.03–0.05 s/kg` | `report_compare` | interpretation | track-varying; emitted as a **range** with the rate printed | +| `diff ±0.5` US/OS | `report_setup` | interpretation | relative to the lap's slip baseline; raw front/rear slip shown | +| profile `*_REL` multipliers + `*_FLOOR` / `TR` margins | `report_profile` | interpretation | multipliers are **relative to the session median**; floors are noise gates; medians shown in the table | + +If a future track needs a different corner grouping, `merge_gap_for` already adapts by length; only a genuinely unusual layout would want a manual nudge there. + ## Gotchas **Files are UTF-8, the OS default here is cp1251.** `parser.load_lap` always passes `encoding='utf-8'`; opening a telemetry file with the default codec raises `UnicodeDecodeError` on the first non-ASCII byte. `rename.read_lap_number` reads only the first five lines but must use the same encoding. diff --git a/ARCHITECTURE.ru.md b/ARCHITECTURE.ru.md index 1413140..336933d 100644 --- a/ARCHITECTURE.ru.md +++ b/ARCHITECTURE.ru.md @@ -17,7 +17,8 @@ telemetry/ report_technique.py Отчёт о пилотировании (повороты, прямые, расход, трасса). report_setup.py Отчёт о машине (сетап, баланс, шины, тормоза, подвеска, фазы). report_compare.py Два+ круга рядом, дельта по поворотам и накопленная. - report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. + report_race.py Папка кругов: лаптаймы (с позицией), стинты, износ, тепло, ERS. Дедуп дублей номеров кругов. + report_profile.py Папка кругов: дешёвые тенденции по поворотам (медиана на поворот, по доминирующему составу) + авто-выбор кругов для разбора. Без трассы. rename.py Вставка типа сессии + номера круга (P1_L7) в имя; с races_dir — сортировка в races/// и рекурсия по подпапкам. gui.py Окно tkinter; память последней папки по режиму; генерация/переименование в потоке. prompts/ Промпты LLM, подпапки ru/ и en/, по одному файлу на режим. Добавляются в конец каждого отчёта. @@ -30,7 +31,7 @@ tests/ Набор pytest + фикстуры-круги. 2. **`parser.load_lap`** читает файл как UTF-8 и делит на четыре мета-блока (session, track, setup) плюс таблицу телеметрии на 108 каналов. Имена каналов нормализуются срезанием суффикса ` [unit]`. Каждая колонка, целиком состоящая из нулей, отбрасывается — игра пишет много мёртвых каналов (см. «Подводные камни»). 3. **`segments.detect_corners`** проходит по сэмплам, помечает сэмпл как «в повороте», если `|Steer| > STEER_ON` или `Brake > BRAKE_ON`, сливает соседние зоны с зазором меньше `MERGE_GAP_M`, отбрасывает зоны короче `MIN_LEN_M` и нумерует оставшиеся T1..Tn по дистанции. Для каждой зоны вызывается **`corner_metrics.corner_from_zone`**, которая извлекает точку торможения, апекс, передачу, точку полного газа, пик тормоза/gLat, относительные lock%/spin%, дистанцию наката и пик температуры задней шины. `straights` заполняет промежутки между поворотами. 4. **`resample.adaptive_points`** строит сетку по дистанции — плотную внутри поворотов (и на подходе) и редкую на прямых; шаги по умолчанию берутся из медианного интервала сэмплов круга через `auto_steps`, так что более плотная сырая телеметрия даёт более плотную трассу. **`sample_at`** интерполирует каждый канал на эту сетку (ближайший сэмпл для дискретных вроде Gear/DRS). -5. Функция **`report_<режим>.generate(…, lang)`** собирает Markdown из помощников `report_common` (`header_block`, `md_table`, `legend`) и данных выше. Затем вызывает **`load_prompt(mode, lang)`**, которая читает промпт из `prompts//.md` и добавляет его после разделителя `---`. Финальный текст пишется через **`write_report`** атомарно (временный файл + `os.replace`), возвращается `(abs_path, tokens)`. +5. Функция **`report_<режим>.generate(…, lang, include_prompt=True)`** собирает Markdown из помощников `report_common` (`header_block`, `md_table`, `legend`) и данных выше. При `include_prompt` вызывает **`load_prompt(mode, lang)`**, которая читает промпт из `prompts//.md` и добавляет его после разделителя `---` (CLI `--no-prompt` / чекбокс GUI это отключают). Финальный текст пишется через **`write_report`** атомарно (временный файл + `os.replace`), возвращается `(abs_path, tokens)`. Перед генерацией `__main__` пропускает прогон через **`is_fresh(report, sources)`**, если свежий отчёт уже есть (override — `--force`). 6. **`__main__`** печатает путь и `~N tokens (budget 60k)`. **GUI** показывает тот же кортеж в логе вместо печати. ## Как добавлять @@ -41,6 +42,23 @@ tests/ Набор pytest + фикстуры-круги. **Новая метрика поворота.** Добавь поле в dataclass `Corner` в `segments.py` и посчитай его в `corner_metrics.corner_from_zone`. Поскольку `Corner` — frozen, задавай через конструктор. +## Допущения и тюнящиеся константы + +Два рода констант. **Детекция** — пороги в единицах телеметрии, одинаковы во всей F1 22: решают, *где* граница, а не вывод, поэтому не тюнингуются под трассу. **Интерпретация** — доменное суждение, которое реально варьируется; в отчётах рядом с любым флагом/вердиктом всегда печатается сырое число, так что спорный порог не прячет данные. + +| Константа | Файл | Род | Основа / чувствительность | +|-----------|------|-----|---------------------------| +| `STEER_ON 6`, `BRAKE_ON 5`, `BRAKE_MARK 20`, `FULL_THROTTLE 98` | `segments` / `corner_metrics` | детекция | % от диапазона ввода, одинаково в игре | +| `MERGE_GAP_M 40` + `merge_gap_for` (clamp 28–60) | `segments` | детекция | **адаптивно**: масштабируется по `Tracklen` вокруг якоря Zandvoort — шиканы/длинные трассы самотюнятся | +| `MIN_LEN_M 15`, `_SAFE_SPEED_FLOOR 30`, окно газа `0.3 с` | `segments` / `corner_metrics` | детекция | геометрия / защита деления / дебаунс | +| `resample` dense=median, sparse=8×median | `resample` | детекция | самонастройка под частоту данных | +| `LOCKUP −10 %`, `SPIN +8 %` | `segments` | интерпретация | флаги severe-only; сырые `lock%`/`spin%` всегда показаны | +| `FUEL 0.03–0.05 с/кг` | `report_compare` | интерпретация | зависит от трассы; выдаётся **диапазоном** с печатью ставки | +| `diff ±0.5` US/OS | `report_setup` | интерпретация | относительно baseline скольжения круга; сырое front/rear показано | +| profile `*_REL` множители + `*_FLOOR` / `TR` поля | `report_profile` | интерпретация | множители **относительно медианы сессии**; полы — шумовые гейты; медианы в таблице | + +Если будущая трасса потребует другой группировки поворотов — `merge_gap_for` уже адаптируется по длине; ручная правка нужна лишь на совсем нестандартной конфигурации. + ## Подводные камни **Файлы в UTF-8, а кодировка ОС здесь — cp1251.** `parser.load_lap` всегда передаёт `encoding='utf-8'`; открытие файла телеметрии дефолтным кодеком падает с `UnicodeDecodeError` на первом не-ASCII байте. `rename.read_lap_number` читает только первые пять строк, но обязан использовать ту же кодировку. diff --git a/README.md b/README.md index eb6756d..ae91235 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,23 @@ Download or clone the repository. Keep your telemetry CSVs in the parent folder ## What it does -Five modes. Four produce reports, one renames files. +Six modes. Five produce reports, one renames files. | Mode | Input | Use it to analyse | |------|-------|-------------------| | `technique` | one lap | **how** you drive — braking points, apex speeds, throttle application, lockups, resource cost | | `setup` | one lap | **what** is set — balance (US/OS), tyre temps, brakes, suspension rake & roll | -| `compare` | two+ laps | **where** one lap is faster — per-corner and cumulative delta | +| `compare` | two+ laps | **where** one lap is faster — per-corner and cumulative delta, plus a deterministic `Verdict` (fuel-corrected) | | `race` | a folder of laps | the whole race — pace, stint degradation, tyre wear, ERS per lap | +| `profile` | a folder of laps | **cheap** (~400 tokens) cross-lap driver tendencies per tyre compound (median coast/lock/spin per corner, flagged relative to the session) + auto-picked laps to deep-dive. Track-agnostic: corners are clustered by apex distance, not index | | `rename` | files/folder | insert session type and lap number into each filename (`..._74.074_...` → `..._P1_L7_74.074_...`) | Reports include a legend explaining every column and have the analysis prompt auto-appended at the end — just drop the file into an LLM. Prompts come in RU and EN; pick with `--lang en` on the CLI or the language radio in the GUI. +**Flags:** `--no-prompt` omits the embedded analysis prompt (smaller files for agent pipelines; in the GUI it's the **Include prompt** checkbox). `--force` regenerates even when an up-to-date report already exists (runs are otherwise skipped as `cached`). + +> Typical optimized flow: run `profile` first to see chronic tendencies and which laps matter, then generate `technique`/`setup`/`compare` only for those laps with `--no-prompt`. + ### Folder layout ``` @@ -66,10 +71,15 @@ python -m telemetry technique races/Zandvoort/Practice/lap.csv python -m telemetry setup races/Zandvoort/Practice/lap.csv python -m telemetry compare races/Zandvoort/Practice/lap_a.csv races/Zandvoort/Practice/lap_b.csv python -m telemetry race races/Zandvoort/Race +python -m telemetry profile races/Zandvoort/Race # cheap cross-lap tendencies + lap picks python -m telemetry rename races/Zandvoort/Practice # or individual files # choose prompt language (default: ru) python -m telemetry technique races/Zandvoort/Practice/lap.csv --lang en + +# agent pipeline: drop the prompt, reuse fresh reports +python -m telemetry technique races/Zandvoort/Practice/lap.csv --no-prompt +python -m telemetry technique races/Zandvoort/Practice/lap.csv --force # ignore cache ``` Every report run prints the output path and a token estimate: diff --git a/README.ru.md b/README.ru.md index e8ae43a..d617798 100644 --- a/README.ru.md +++ b/README.ru.md @@ -21,18 +21,23 @@ python -m telemetry technique "races/Zandvoort/Практика/zandvoort_P1_L7_ ## Что умеет -Пять режимов. Четыре строят отчёты, один переименовывает файлы. +Шесть режимов. Пять строят отчёты, один переименовывает файлы. | Режим | Вход | Для анализа | |-------|------|-------------| | `technique` | один круг | **как** едешь — точки торможения, скорости в апексах, работа газом, блокировки, расход ресурсов | | `setup` | один круг | **что** настроено — баланс (US/OS), температуры шин, тормоза, rake и крен подвески | -| `compare` | два+ круга | **где** один круг быстрее — дельта по поворотам и накопленная | +| `compare` | два+ круга | **где** один круг быстрее — дельта по поворотам и накопленная, плюс детерминированный `Verdict` (с топливной поправкой) | | `race` | папка кругов | вся гонка — темп, деградация в стинтах, износ резины, ERS по кругам | +| `profile` | папка кругов | **дёшево** (~400 токенов) тенденции пилота по каждому составу (медиана coast/lock/spin на поворот, флаги относительно сессии) + авто-выбор кругов. Универсально: повороты кластеризуются по дистанции апекса, не по индексу | | `rename` | файлы/папка | вставить тип сессии и номер круга в имя файла (`..._74.074_...` → `..._P1_L7_74.074_...`) | Отчёты содержат легенду с пояснением каждой колонки, а промпт анализа добавляется в конец файла автоматически — достаточно скинуть файл в LLM. Промпты доступны на RU и EN; выбирается флагом `--lang en` в CLI или переключателем языка в GUI. +**Флаги:** `--no-prompt` убирает встроенный промпт (меньше файлы для агентных конвейеров; в GUI — чекбокс **Включить промпт**). `--force` перегенерирует, даже если свежий отчёт уже есть (иначе прогон пропускается как `cached`). + +> Оптимальный порядок: сначала `profile` — увидеть хронические тенденции и какие круги важны, затем `technique`/`setup`/`compare` только по этим кругам с `--no-prompt`. + ### Структура папок ``` @@ -66,10 +71,15 @@ python -m telemetry technique races/Zandvoort/Практика/lap.csv python -m telemetry setup races/Zandvoort/Практика/lap.csv python -m telemetry compare races/Zandvoort/Практика/lap_a.csv races/Zandvoort/Практика/lap_b.csv python -m telemetry race races/Zandvoort/Гонка +python -m telemetry profile races/Zandvoort/Гонка # дешёвые тенденции по всем кругам + авто-выбор python -m telemetry rename races/Zandvoort/Практика # или отдельные файлы # выбор языка промпта (по умолчанию: ru) python -m telemetry technique races/Zandvoort/Практика/lap.csv --lang en + +# агентный конвейер: убрать промпт, переиспользовать свежие отчёты +python -m telemetry technique races/Zandvoort/Практика/lap.csv --no-prompt +python -m telemetry technique races/Zandvoort/Практика/lap.csv --force # игнорировать кэш ``` Каждый прогон печатает путь к отчёту и оценку токенов: