# 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).