Files
SDR-Rover/docs/lab046_current_state.md
2026-09-01 14:36:51 +03:00

310 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lab046: текущее состояние
## Статус
**LAB046 SOFTWARE OBJECT TRANSPORT = COMPLETE.**
**CONTINUOUS STREAM VALIDATION = PASS (NORMALIZED SOFT-SYMBOL LEVEL).**
**ПРОГРАММНАЯ ПРОВЕРКА НЕПРЕРЫВНОГО СЫРОГО IQ-ПОТОКА = PASS.**
**RF TX = 0.**
**АППАРАТНАЯ ПРОВЕРКА = НЕ ВЫПОЛНЯЛАСЬ.**
Lab046 — только программная лаба. HackRF и `hackrf_transfer` не
запускались, эфирной передачи нет. Ни JPEG, ни видео, ни QPSK.
## Назначение
Lab046 переходит от одиночной 256-байтной полезной нагрузки к передаче
произвольного бинарного объекта последовательностью независимо защищённых
радиокадров. Два уровня строго разделены и не смешиваются:
- **Уровень 1 (внутри одного кадра) — Lab045.** Свёрточный код R=1/2 K=7,
перемежитель, мягкий декодер Витерби, CRC пакета. Исправляет неизвестные
битовые ошибки внутри принятого кадра.
- **Уровень 2 (большой объект) — существующий `protocol/image_fragments`.**
Фрагментация, нумерация фрагментов, идентификатор объекта, сборка. Собирает
объект из нескольких независимо принятых кадров.
Новый код в `experiments/lab046_bpsk_object_transport.py` минимален: он
связывает два готовых уровня и добавляет fail-closed сборщик объекта.
Отдельный модуль формата фрагмента НЕ создавался: переиспользован 12-байтовый
заголовок `image_fragments` (`>IHHI`: object_id u32, fragment_index u16,
fragment_count u16, original_size u32).
## Аудит: что переиспользовано, чего не хватало, минимальный новый слой
1. **Переиспользовано.** `protocol/image_fragments` уже даёт нужную семантику
фрагмента: идентификатор объекта, индекс, число фрагментов, общий размер,
а также fail-closed `reassemble_image` (несовпадение id/count/size,
конфликтующие дубликаты, недостающие фрагменты, финальная проверка длины).
Слой Lab045 даёт кадр целиком: `build_transmission_plan`,
`analyze_post_preamble_soft`, `simulate_soft_channel`, диагностику pre/post-FEC.
2. **Чего не хватало.** Не было (а) упаковки фрагмента в 256-байтную нагрузку
Lab045 с обрезкой добивки на приёме по длине из заголовка и (б) сборщика,
который принимает по одному независимо декодированному кадру и **никогда не
бросает** на плохом входе, возвращая `object_complete = False` вместо
фиктивного объекта.
3. **Минимальный новый слой.** `recover_fragment_from_payload` (fail-closed
декод + обрезка добивки) и класс `ObjectAssembler` (offer/missing/complete/
recovered). Erasure-код 12+3 GF(256) (`protocol/packet_erasure_fec`) НЕ
использован и НЕ изменён — он остаётся будущим расширением для потери целых
фрагментов, а не для битовых ошибок (это работа свёрточного кода).
После этого Lab046A закрыла оставшийся пробел: до неё Lab046 передавала каждый
кадр отдельным вызовом и выбирала его из TX-плана по известному индексу. Функции
поиска нескольких кадров в одном потоке не было. Теперь
`scan_continuous_soft_stream` получает только один массив soft-символов,
самостоятельно ищет PRBS7 после монотонного cursor, проверяет защищённый PHY
header, вычисляет длину coded body из принятого packet length, выполняет готовые
FEC/CRC Lab045 и передаёт принятый fragment в `ObjectAssembler`.
TX offsets, список длин кадров, ожидаемое число 68, порядок/индексы TX,
исходный объект, TX packets и coded bits scanner-у не передаются. TX-side
`frame_starts` остаётся только в генераторе потока и используется тестом для
сравнения уже после независимого receive.
## Граница доказательства Lab046A
Lab046A работает на уровне **одного непрерывного нормализованного soft BPSK
stream** (`float32`). Это разрешённый интерфейс после уже проверенного в Lab045
sample-level front-end: PRBS7 detection, matched filtering, timing, CFO и
phase/polarity recovery. Внутри нового длинного теста эти sample-level операции
заново не моделируются для каждого кадра. Поэтому результат доказывает
самостоятельный поиск границ, header-driven длину, FEC/CRC и сборку объекта в
непрерывном soft stream, но не является аппаратной или непрерывной IQ-проверкой.
Каждый фрагмент содержит полный активный Lab045 PHY frame: 1280 бит PRBS7,
144 бита защищённого header и 4300 coded bits. Точная полная sample-level форма
с guard и хвостом ФРК учитывается как 2 306 400 отсчётов на кадр, однако все
156 835 200 IQ-отсчётов одновременно в памяти не создаются. Это намеренное
ограничение памяти, а не использование известных границ приёмником.
Scanner fail-closed:
- cursor всегда растёт, после принятого кадра переносится за вычисленный конец;
- все PRBS7 candidates выше порога рассматриваются в порядке stream; после
принятого кадра cursor переносится за вычисленный конец и не даёт
декодировать этот кадр повторно;
- плохой header, усечённое body, ошибка FEC/CRC отвергают candidate, но поиск
следующего продолжается;
- `MAX_FRAMES` и `MAX_SEARCH_ITERATIONS` дают явную причину остановки, а не
частичный объект, объявленный полным;
- scan продолжается до конца capture и не останавливается на числе 68.
## Сценарии continuous stream
| Сценарий | Получено |
|---|---|
| Идеальный, кадры подряд | candidates 68, valid headers 68, FEC PASS 68, CRC PASS 68/68, fragments 68, complete=True, SHA совпал, 0 байт-ошибок |
| Фон до/между/после | 37/37/43 soft-символа, valid frames 68, complete=True, SHA совпал |
| Непрерывный AWGN, sigma=0.40, seed=46001 | frames 68, pre-FEC 1791, post-FEC 0, CRC 68/0, complete=True, SHA совпал |
| Valid-looking preamble + bad protected header | candidates 71, rejected 3, последующие valid frames 68, complete=True, SHA совпал |
Фон между кадрами добавлен только после кадров 7, 23 и 51: 11, 19 и 7
soft-символов, всего 37. В baseline дополнительного inter-frame gap нет.
Отдельный тест с валидным header для 12-байтного packet и повреждённым body
проверяет, что scanner берёт длину именно из принятого header, отвергает этот
candidate и затем находит настоящий кадр.
Verifier нашёл опасный вариант прежнего глобального non-maximum suppression:
более сильный ложный PRBS7 peak мог удалить более поздний настоящий старт
кадра. Поэтому suppression убран из списка кандидатов, а добавленный
регрессионный тест проверяет, что ложный peak не скрывает настоящий кадр.
## Lab046B: непрерывный сырой IQ-поток
Lab046B закрывает только программный sample-level пробел Lab046A. Новый
`experiments/lab046b_continuous_iq.py` формирует пять полных Lab045 waveform с
PRBS7, защищённым PHY header, interleaved convolutional body, BPSK, RRC и
guards, размещает их в одном `complex64` массиве и передаёт scanner-у только
этот массив. TX start offsets, длины TX waveform, номера кадров, payload,
packet и coded bits в `scan_continuous_iq` не передаются.
Переиспользованы существующие функции Lab043-Lab045: BPSK/RRC waveform,
PRBS7 reference, normalized sliding correlation, локальное уточнение sample
start, matched filter, preamble-only timing, non-data-aided CFO и комплексный
gain, `normalized.real`, защищённый PHY header, deinterleave, soft Viterbi и
protocol CRC. Новый слой только перечисляет локальные IQ-кандидаты, вызывает
этот тракт на окне кандидата и монотонно продолжает поиск.
Глобальное требование «главный пик значительно выше второго» не используется.
В идеальном пятикадровом опыте peak/second для настоящих кадров лежит около
0 dB, поскольку вторым пиком является другой настоящий кадр; тем не менее все
пять кадров приняты. Все локальные максимумы выше порога сохраняются, а после
CRC PASS cursor переносится за вычисленный по принятому header конец кадра.
После плохого кандидата cursor также строго растёт, но следующий кандидат не
скрывается. Выход ограничен `MAX_FRAMES` и `MAX_SEARCH_ITERATIONS`.
### Фактические сценарии Lab046B
| Сценарий | Получено |
|---|---|
| Идеальный continuous IQ | PRBS7 candidates 32, processed 5, accepted 5, rejected 0, PHY/FEC/CRC 5/5/5, CRC FAIL 0, sample offset error 0 для всех |
| CFO + phase + amplitude | внесено 0.2 Hz / 0.7 rad / 0.65; медианная оценка 0.200003768 Hz / 0.699634288 rad; CRC 5/0 |
| Комплексный AWGN | sigma=0.17, seed=46002; accepted 5, pre-FEC 0, post-FEC 0, CRC 5/0 |
| PRBS7 + неверный PHY header перед настоящим кадром | candidates 11, processed 8, rejected 7, следующий настоящий кадр найден, CRC 1/0 |
Начало первого waveform: sample 137, то есть не sample 0 и не граница сетки
400 samples/symbol. Дополнительные gaps: 211/353/587/809 samples. Найденные
фазы символьной выборки: 137/348/301/88/97 samples modulo 400. Точные TX
offsets используются только постфактум после CRC PASS; ошибки обнаруженного
start во всех пяти случаях равны нулю.
Шумовая точка зафиксирована честно. Первый разумный однокадровый запуск при
sigma=0.12 дал CRC PASS, но 0 pre-FEC ошибок. Выполнена одна разрешённая
обоснованная корректировка до sigma=0.17; она также дала 0 pre-FEC ошибок и
CRC PASS. Дальнейший подбор не выполнялся. Поэтому этот сценарий доказывает
устойчивый многокадровый IQ receive с комплексным детерминированным шумом, но
не является новым доказательством реального исправления ошибок FEC; такое
доказательство уже относится к аппаратной Lab045.
Основной массив содержит 11 534 330 `complex64` отсчётов и занимает
92 274 640 байт (88.0 MiB). Пять waveform занимают 11 532 000 samples, остальное
— leading/gaps/trailing. Полные CFO/phase-corrected копии не создаются:
первичный поиск работает с decimated stream, а matched filtering и soft
extraction выполняются на локальном окне одного кандидата. Это размер именно
основного IQ-массива, а не peak RSS процесса: RRC/FFT, локальный matched stream
и временные массивы SciPy требуют дополнительную рабочую память.
## Память continuous stream
Основной 68-кадровый soft stream содержит 389 232 значения `float32` и занимает
1 556 928 байт (1,485 MiB). Массив correlation scores занимает 1 551 812 байт,
накопленная энергия `float64` — 3 113 864 байта. FFT и denominator создают
временное рабочее пространство, размер которого зависит от SciPy, но стратегия
остается в пределах десятков MiB, а не нескольких GiB. Для сравнения, один
материализованный continuous `complex64` IQ stream из 156 835 200 отсчётов
занял бы около 1,17 GiB ещё до временных копий.
## Детерминированный объект
- Размер **16384 байта (16 KiB)**. Это не JPEG, а воспроизводимый
псевдослучайный поток `numpy.random.default_rng(seed).integers(0,256,...)`.
- **Выбор размера.** 16 KiB — компромисс: достаточно велик, чтобы кадров было
много (68) и учёт фрагментации/сборки был содержательным, но достаточно мал,
чтобы программный прогон 68 кадров с мягким Витерби считался секунды. Ровный
размер (не кратный 244) специально оставляет короткий последний фрагмент.
- Seed объекта по умолчанию `46`, идентификатор объекта `0x4C303436` («L046»).
- SHA-256 объекта по умолчанию:
`748013b5f670c82bd52650be5454555355dbd8528538753df4558f1c3408d515`.
- Объект не сохраняется в Git (крупный генерируемый артефакт); при
необходимости он детерминированно восстанавливается по seed.
## Раскладка нагрузки пакета
Полезная нагрузка Lab045 остаётся 256 байт (доказанная рабочая точка):
[12 байт метаданных фрагмента][244 байта данных фрагмента]
`FRAGMENT_DATA_SIZE = 256 − 12 = 244`. Последний фрагмент короче; его нагрузка
добивается нулями до 256 байт перед Lab045 и обрезается на приёме по длине,
вычисленной **только из полей заголовка** (`expected_fragment_data_length`).
Для 16384 байт последний, 68-й фрагмент несёт 36 байт данных (16384 − 67·244).
## Fail-closed сборка (без оракула)
Приёмник независим: переданный объект и TX-биты не используются ни для приёма
пакета, ни для FEC, ни для восстановления метаданных, ни для решения о
завершении объекта. Сравнение с оригиналом делается только в диагностике,
**после** независимого CRC-решения и независимой сборки.
`ObjectAssembler.offer(fragment)` возвращает `accepted/duplicate/conflicting/
rejected` и никогда не бросает. Отвергаются: чужой object_id, несогласованные
count/original_size, невозможный индекс, конфликтующий дубликат (тот же индекс,
другие данные), malformed/усечённые метаданные. Идентичный дубликат
игнорируется. Пока не собраны все индексы, `object_complete()` = False, а
`recovered_object()` = None — фиктивный объект не создаётся. Поле original_size
проверяется на обе границы (`(count−1)·244 < original_size ≤ count·244`), чтобы
абсурдное значение не управляло размером аллокаций.
## Программные сценарии (seed фиксирован)
| Сценарий | Условие | Ожидалось | Получено |
|---|---|---|---|
| A. Идеальный канал | шума нет | объект точен | complete=True, sha совпал, 0 байт-ошибок, CRC 68/0 |
| B. Распределённые битовые ошибки | гауссов шум sigma=0.40 | pre-FEC>0, post-FEC=0, все CRC PASS, объект точен | pre-FEC 1805, post-FEC 0, CRC 68/0, complete=True, sha совпал |
| C. Приём не по порядку | обратный порядок кадров | объект точен | complete=True, sha совпал |
| D. Дубликаты фрагментов | повтор кадров 0,2,2 | объект точен, дубликаты учтены | duplicates≥1, complete=True, sha совпал |
| E. Один потерянный фрагмент | кадр 1 не дошёл | OBJECT COMPLETE = NO | complete=False, 1 в missing, recovered SHA = None |
| F. Повреждённый пакет | инверсия coded-потока кадра 0 | CRC FAIL, объекта нет | crc_fail≥1, complete=False, recovered SHA = None |
Сценарий B — честная точка: уровень sigma выбран так, чтобы мягкий Витерби
исправлял **все** 68 кадров (при sigma=0.45+ отдельный кадр не декодируется, и
без erasure-FEC объект оказывается неполным — это по сути сценарий E, а не
подгон). Числа не подкручивались ради прохождения: до FEC присутствуют 1805
реальных ошибок кодированных бит, после мягкого Витерби — ноль, объект
побитово точен.
## Метрики (объект 16384 байта, идеальный канал)
| Величина | Значение |
|---|---:|
| Размер объекта | 16384 байта |
| SHA-256 объекта | `748013b5…3408d515` |
| Размер метаданных фрагмента | 12 байт/пакет |
| Размер данных фрагмента | 244 байта (последний 36) |
| Число фрагментов | 68 |
| Размер протокольного пакета | 268 байт (256 нагрузка + 8 header + 4 CRC) |
| Число BPSK-кадров | 68 |
| Всего информационных BPSK-символов | 389 232 (5724 × 68) |
| Всего отсчётов формы сигнала | 156 835 200 (2 306 400 × 68) |
| Baseline: дополнительный inter-frame gap | 0 отсчётов |
| Baseline: полная длительность | 19,604400 с |
| Baseline: полезный goodput | 835,7 байт/с = 6,685846 кбит/с |
| Фоновый тест: leading/inter-frame/trailing | 14 800 / 14 800 / 17 200 отсчётов |
| Фоновый тест: полная длина и длительность | 156 882 000 отсчётов, 19,610250 с |
| Фоновый тест: фактический goodput | 6,683852 кбит/с |
| Полезных бит на символ | ≈ 0,337 |
Учёт кадра совпадает с Lab045: 2144 информационных бита, 6 tail-бит, 4300
кодированных бит (R=1/2), перемежитель 4300, PHY header 48→144 бит (×3),
преамбула PRBS7 1280 бит, итого 5724 информационных символа кадра. Форма
сигнала добавляет 42 символо-времени (32 нулевых guard + 10 хвост ФРК,
`shape_frame_like_lab042`, режим `full`): 5766 символо-времён = 2 306 400
отсчётов = 288,3 мс на кадр при 8 MS/s.
### Разбор накладных расходов
- **Фрагментация (уровень 2):** 12 байт метаданных на пакет × 68 = 816 байт.
- **Протокольный header+CRC (уровень 1):** 12 байт на пакет × 68 = 816 байт.
- **Свёрточный код R=1/2:** удваивает число кодированных бит (2144→4300 с tail).
- **PHY преамбула и защищённый header:** 1280 + 144 = 1424 бита на кадр.
- **Guard/фильтр:** 42 символо-времени на кадр (32 guard + 10 хвост ФРК).
- **Добивка последнего фрагмента:** 208 нулевых байт (только 68-й кадр).
Goodput 6,69 кбит/с — это честная baseline при 20 ксимв/с с полной
преамбулой и guard на каждый кадр; в реальной серии кадров преамбула на кадр
доминирует в накладных расходах, и это направление для будущей оптимизации,
а не дефект.
## Фактические программные результаты
- Новый набор `tests/test_lab046_bpsk_object_transport.py`: все проверки
проходят (фрагмент туда-обратно, короткий последний фрагмент, чужой
object_id, невозможный индекс, несогласованный счётчик, усечённые и абсурдные
метаданные без крупной аллокации, полный тракт PHY для сценариев A–F,
отсутствие оракула, последовательный continuous scan, rejected candidate,
MAX_FRAMES/MAX_SEARCH_ITERATIONS, регрессия против удаления настоящего кадра
ложным PRBS7 peak, детерминизм и SHA объекта).
- Полный тракт `объект → фрагменты → кадр Lab045 → канал → приём → сборка`
проверен для всех шести сценариев значениями выше.
## Software gate
- `python tools/quick_gate.py`: PASS; 468 тестов ядра, 7 функциональных
проверок лабораторий выполнено, 13 штатно пропущено.
- `python -m pytest tests/ -q`: 468 passed, 0 failed, 0 errors, 0 skipped.
Единственное предупреждение связано с отсутствием прав на создание
`.pytest_cache`; на выполнение и результат тестов оно не повлияло.
- `git diff --check`: чист.
## Ограничения и следующий шаг
RF TX = 0, аппаратная проверка не проводилась. Erasure-код 12+3 GF(256) не
задействован (будущее восстановление потери целых фрагментов). Оптимизация
преамбулы/guard на серию кадров, QPSK, JPEG и видео в этой лабе не делались.
Следующий этап определяется после анализа (Lab046 первым смотрит ChatGPT).