Lab061: integrate T2 into the strict receiver

This commit is contained in:
LittleSam129
2026-09-14 11:07:40 +03:00
parent 92b0e0f8dd
commit 25cc89f0c7
20 changed files with 1290 additions and 20 deletions

View File

@@ -2630,3 +2630,34 @@ Lab054. Штатный receiver и production-код не изменялись.
метрики без расхождений. Lab060 не коммитировалась, push не выполнялся.
Следующий этап: STOP и отдельно согласовать решение о внедрении T2 в штатный
receiver; Lab061 автоматически не создавать.
# Запись 055
## Дата
11 сентября 2026 года
## Тема
Lab061 — OFFLINE-интеграция T2 в строгий приёмник
## Выполнено
Lab060 зафиксирована коммитом 92b0e0f8dd726f4420e8ef5d5e7c049357296e7c;
push не выполнялся. T2 встроен в существующий строгий PacketProcessor как явно
выбираемый режим. Legacy остался режимом по умолчанию, автоматической повторной
попытки по результату CRC нет. Коэффициенты и P3 численно совпали с Lab060.
Полный штатный synthetic PHY-пакет 500K проверен на39 случаях в каждом режиме:
13 остаточных частот от−10 до+10Гц и три качества. На всех качествах legacy/T2
дали CRC89,743590/94,871795%; на уровне EVM0,844032 — 9/13 и11/13. Сохранённый
TX дал16/16 в обоих режимах. Lab047 сохранил36/36, Lab054 сохранил0/16, фон
дал0 заголовков и0 пакетов.
Интегрированное ядро T2 воспроизвело Lab056: средняя SER A/B равна
0,017756/3,501780%, срыва сопровождения нет. Проверки причинности, отсутствия
payload truth и конечности при ошибках, fade и шуме прошли. Решение —
READY_BEHIND_EXPLICIT_MODE до отдельного контролируемого RF-теста с полными
500K-пакетами. Оборудование не опрашивалось, RF TX=0. Lab061 фиксируется этим
отдельным коммитом; push выполняется пользователем отдельно. Следующий этап:
STOP; Lab062 автоматически не начинать.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,5 @@
label,symbol_rate,symbol_errors,symbols,symbol_error_rate,evm_rms,matched_component_power,residual_power,matched_residual_db,final_frequency_hz,large_phase_slips,loss_of_lock,bad_blocks,longest_bad_state_seconds,self_recovered,stable_lock
A1,20000,1,2816,0.0003551136363636364,0.31627242306137465,0.9760338688912713,0.09763095554074837,9.998773482861347,-0.03678470540537988,0,False,0,0.0,False,True
B1,500000,3290,101120,0.032535601265822785,0.8090614605506008,0.940782269995664,0.615817678774771,1.840369716972757,-2.3940002814874086,0,False,0,0.0,False,True
A2,20000,0,2816,0.0,0.31568433619231595,1.012966734394142,0.10094882078152588,10.014939332951363,0.0013080327716195816,0,False,0,0.0,False,True
B2,500000,3792,101120,0.0375,0.8744054425662731,0.986059800896635,0.7539264125589239,1.1657429573714726,1.9343992902116445,0,False,0,0.0,False,True
1 label symbol_rate symbol_errors symbols symbol_error_rate evm_rms matched_component_power residual_power matched_residual_db final_frequency_hz large_phase_slips loss_of_lock bad_blocks longest_bad_state_seconds self_recovered stable_lock
2 A1 20000 1 2816 0.0003551136363636364 0.31627242306137465 0.9760338688912713 0.09763095554074837 9.998773482861347 -0.03678470540537988 0 False 0 0.0 False True
3 B1 500000 3290 101120 0.032535601265822785 0.8090614605506008 0.940782269995664 0.615817678774771 1.840369716972757 -2.3940002814874086 0 False 0 0.0 False True
4 A2 20000 0 2816 0.0 0.31568433619231595 1.012966734394142 0.10094882078152588 10.014939332951363 0.0013080327716195816 0 False 0 0.0 False True
5 B2 500000 3792 101120 0.0375 0.8744054425662731 0.986059800896635 0.7539264125589239 1.1657429573714726 1.9343992902116445 0 False 0 0.0 False True

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -0,0 +1,105 @@
dataset,frame,mode,HEADER_ACCEPTED,FEC_STARTED,FEC_PASS,CRC_STARTED,CRC_PASS,PACKET_OUTPUT,reason,initial_frequency_hz
Lab047,1,legacy,True,True,True,True,True,True,CRC_PASS,0.2033868989670703
Lab047,2,legacy,True,True,True,True,True,True,CRC_PASS,0.012450970374705333
Lab047,3,legacy,True,True,True,True,True,True,CRC_PASS,-0.15241868588401689
Lab047,4,legacy,True,True,True,True,True,True,CRC_PASS,0.012628034912381616
Lab047,5,legacy,True,True,True,True,True,True,CRC_PASS,0.28102835180050534
Lab047,6,legacy,True,True,True,True,True,True,CRC_PASS,0.0728617397594959
Lab047,7,legacy,True,True,True,True,True,True,CRC_PASS,0.02153239849549078
Lab047,8,legacy,True,True,True,True,True,True,CRC_PASS,0.130332736445595
Lab047,9,legacy,True,True,True,True,True,True,CRC_PASS,0.08586075134986197
Lab047,10,legacy,True,True,True,True,True,True,CRC_PASS,0.019087882990447214
Lab047,11,legacy,True,True,True,True,True,True,CRC_PASS,0.012581360419676476
Lab047,12,legacy,True,True,True,True,True,True,CRC_PASS,0.18682072928588908
Lab047,13,legacy,True,True,True,True,True,True,CRC_PASS,0.10358464925085982
Lab047,14,legacy,True,True,True,True,True,True,CRC_PASS,-0.2112142577930461
Lab047,15,legacy,True,True,True,True,True,True,CRC_PASS,0.04987013681457267
Lab047,16,legacy,True,True,True,True,True,True,CRC_PASS,-0.018063387420586025
Lab047,17,legacy,True,True,True,True,True,True,CRC_PASS,-0.10920375479998666
Lab047,18,legacy,True,True,True,True,True,True,CRC_PASS,-0.023768436535983758
Lab047,19,legacy,True,True,True,True,True,True,CRC_PASS,-0.12844207213561043
Lab047,20,legacy,True,True,True,True,True,True,CRC_PASS,-0.021131644411390132
Lab047,21,legacy,True,True,True,True,True,True,CRC_PASS,0.0010757873345088082
Lab047,22,legacy,True,True,True,True,True,True,CRC_PASS,0.16827983008186687
Lab047,23,legacy,True,True,True,True,True,True,CRC_PASS,0.030806194252629853
Lab047,24,legacy,True,True,True,True,True,True,CRC_PASS,0.03249681818606507
Lab047,25,legacy,True,True,True,True,True,True,CRC_PASS,-0.1189326326882962
Lab047,26,legacy,True,True,True,True,True,True,CRC_PASS,-0.08475150493996597
Lab047,27,legacy,True,True,True,True,True,True,CRC_PASS,0.05389239025811241
Lab047,28,legacy,True,True,True,True,True,True,CRC_PASS,0.04289420557710872
Lab047,29,legacy,True,True,True,True,True,True,CRC_PASS,-0.23616554750888527
Lab047,30,legacy,True,True,True,True,True,True,CRC_PASS,-0.12603744749648454
Lab047,31,legacy,True,True,True,True,True,True,CRC_PASS,0.0605922709725388
Lab047,32,legacy,True,True,True,True,True,True,CRC_PASS,-0.08349857262528622
Lab047,33,legacy,True,True,True,True,True,True,CRC_PASS,-0.15898835195874458
Lab047,34,legacy,True,True,True,True,True,True,CRC_PASS,0.1138327859367743
Lab047,35,legacy,True,True,True,True,True,True,CRC_PASS,-0.23280446158763507
Lab047,36,legacy,True,True,True,True,True,True,CRC_PASS,-0.2555997057508273
Lab047,1,t2,True,True,True,True,True,True,CRC_PASS,0.15714547272293758
Lab047,2,t2,True,True,True,True,True,True,CRC_PASS,0.04525547988229565
Lab047,3,t2,True,True,True,True,True,True,CRC_PASS,-0.012490675017939984
Lab047,4,t2,True,True,True,True,True,True,CRC_PASS,0.08847136354602585
Lab047,5,t2,True,True,True,True,True,True,CRC_PASS,0.1939244013504176
Lab047,6,t2,True,True,True,True,True,True,CRC_PASS,0.055162738246807015
Lab047,7,t2,True,True,True,True,True,True,CRC_PASS,0.04068198959955451
Lab047,8,t2,True,True,True,True,True,True,CRC_PASS,0.03634649209435207
Lab047,9,t2,True,True,True,True,True,True,CRC_PASS,-0.03437239082431202
Lab047,10,t2,True,True,True,True,True,True,CRC_PASS,-0.004015083453945893
Lab047,11,t2,True,True,True,True,True,True,CRC_PASS,0.08945444080931844
Lab047,12,t2,True,True,True,True,True,True,CRC_PASS,0.20793181274469477
Lab047,13,t2,True,True,True,True,True,True,CRC_PASS,0.07046137666433454
Lab047,14,t2,True,True,True,True,True,True,CRC_PASS,-0.07105944506834289
Lab047,15,t2,True,True,True,True,True,True,CRC_PASS,0.0609663723289962
Lab047,16,t2,True,True,True,True,True,True,CRC_PASS,-0.07015498366163844
Lab047,17,t2,True,True,True,True,True,True,CRC_PASS,-0.11928091623072502
Lab047,18,t2,True,True,True,True,True,True,CRC_PASS,0.04019810967068637
Lab047,19,t2,True,True,True,True,True,True,CRC_PASS,0.015930924737407013
Lab047,20,t2,True,True,True,True,True,True,CRC_PASS,-0.008585663400001449
Lab047,21,t2,True,True,True,True,True,True,CRC_PASS,0.03878383029001333
Lab047,22,t2,True,True,True,True,True,True,CRC_PASS,0.09427373288664782
Lab047,23,t2,True,True,True,True,True,True,CRC_PASS,0.0003722803565952149
Lab047,24,t2,True,True,True,True,True,True,CRC_PASS,-0.008086735812339499
Lab047,25,t2,True,True,True,True,True,True,CRC_PASS,0.02904818250697064
Lab047,26,t2,True,True,True,True,True,True,CRC_PASS,-0.00973707735031404
Lab047,27,t2,True,True,True,True,True,True,CRC_PASS,0.05145139978648825
Lab047,28,t2,True,True,True,True,True,True,CRC_PASS,-0.07130299348886393
Lab047,29,t2,True,True,True,True,True,True,CRC_PASS,-0.04495129868231881
Lab047,30,t2,True,True,True,True,True,True,CRC_PASS,-0.11170196243926128
Lab047,31,t2,True,True,True,True,True,True,CRC_PASS,-0.06762881454676493
Lab047,32,t2,True,True,True,True,True,True,CRC_PASS,-0.05377742451538007
Lab047,33,t2,True,True,True,True,True,True,CRC_PASS,-0.05983507118706565
Lab047,34,t2,True,True,True,True,True,True,CRC_PASS,-0.03974346979852063
Lab047,35,t2,True,True,True,True,True,True,CRC_PASS,-0.04857303052983457
Lab047,36,t2,True,True,True,True,True,True,CRC_PASS,-0.0800518478779996
Lab054,1,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,2,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,3,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,4,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,5,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,6,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,7,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,8,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,9,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,10,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,11,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,12,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,13,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,14,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,15,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,16,legacy,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,1,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,2,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,3,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,4,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,5,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,6,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,7,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,8,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,9,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,10,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,11,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,12,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,13,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,14,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,15,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
Lab054,16,t2,False,False,False,False,False,False,PHY_HEADER_MAGIC_MISMATCH,
1 dataset frame mode HEADER_ACCEPTED FEC_STARTED FEC_PASS CRC_STARTED CRC_PASS PACKET_OUTPUT reason initial_frequency_hz
2 Lab047 1 legacy True True True True True True CRC_PASS 0.2033868989670703
3 Lab047 2 legacy True True True True True True CRC_PASS 0.012450970374705333
4 Lab047 3 legacy True True True True True True CRC_PASS -0.15241868588401689
5 Lab047 4 legacy True True True True True True CRC_PASS 0.012628034912381616
6 Lab047 5 legacy True True True True True True CRC_PASS 0.28102835180050534
7 Lab047 6 legacy True True True True True True CRC_PASS 0.0728617397594959
8 Lab047 7 legacy True True True True True True CRC_PASS 0.02153239849549078
9 Lab047 8 legacy True True True True True True CRC_PASS 0.130332736445595
10 Lab047 9 legacy True True True True True True CRC_PASS 0.08586075134986197
11 Lab047 10 legacy True True True True True True CRC_PASS 0.019087882990447214
12 Lab047 11 legacy True True True True True True CRC_PASS 0.012581360419676476
13 Lab047 12 legacy True True True True True True CRC_PASS 0.18682072928588908
14 Lab047 13 legacy True True True True True True CRC_PASS 0.10358464925085982
15 Lab047 14 legacy True True True True True True CRC_PASS -0.2112142577930461
16 Lab047 15 legacy True True True True True True CRC_PASS 0.04987013681457267
17 Lab047 16 legacy True True True True True True CRC_PASS -0.018063387420586025
18 Lab047 17 legacy True True True True True True CRC_PASS -0.10920375479998666
19 Lab047 18 legacy True True True True True True CRC_PASS -0.023768436535983758
20 Lab047 19 legacy True True True True True True CRC_PASS -0.12844207213561043
21 Lab047 20 legacy True True True True True True CRC_PASS -0.021131644411390132
22 Lab047 21 legacy True True True True True True CRC_PASS 0.0010757873345088082
23 Lab047 22 legacy True True True True True True CRC_PASS 0.16827983008186687
24 Lab047 23 legacy True True True True True True CRC_PASS 0.030806194252629853
25 Lab047 24 legacy True True True True True True CRC_PASS 0.03249681818606507
26 Lab047 25 legacy True True True True True True CRC_PASS -0.1189326326882962
27 Lab047 26 legacy True True True True True True CRC_PASS -0.08475150493996597
28 Lab047 27 legacy True True True True True True CRC_PASS 0.05389239025811241
29 Lab047 28 legacy True True True True True True CRC_PASS 0.04289420557710872
30 Lab047 29 legacy True True True True True True CRC_PASS -0.23616554750888527
31 Lab047 30 legacy True True True True True True CRC_PASS -0.12603744749648454
32 Lab047 31 legacy True True True True True True CRC_PASS 0.0605922709725388
33 Lab047 32 legacy True True True True True True CRC_PASS -0.08349857262528622
34 Lab047 33 legacy True True True True True True CRC_PASS -0.15898835195874458
35 Lab047 34 legacy True True True True True True CRC_PASS 0.1138327859367743
36 Lab047 35 legacy True True True True True True CRC_PASS -0.23280446158763507
37 Lab047 36 legacy True True True True True True CRC_PASS -0.2555997057508273
38 Lab047 1 t2 True True True True True True CRC_PASS 0.15714547272293758
39 Lab047 2 t2 True True True True True True CRC_PASS 0.04525547988229565
40 Lab047 3 t2 True True True True True True CRC_PASS -0.012490675017939984
41 Lab047 4 t2 True True True True True True CRC_PASS 0.08847136354602585
42 Lab047 5 t2 True True True True True True CRC_PASS 0.1939244013504176
43 Lab047 6 t2 True True True True True True CRC_PASS 0.055162738246807015
44 Lab047 7 t2 True True True True True True CRC_PASS 0.04068198959955451
45 Lab047 8 t2 True True True True True True CRC_PASS 0.03634649209435207
46 Lab047 9 t2 True True True True True True CRC_PASS -0.03437239082431202
47 Lab047 10 t2 True True True True True True CRC_PASS -0.004015083453945893
48 Lab047 11 t2 True True True True True True CRC_PASS 0.08945444080931844
49 Lab047 12 t2 True True True True True True CRC_PASS 0.20793181274469477
50 Lab047 13 t2 True True True True True True CRC_PASS 0.07046137666433454
51 Lab047 14 t2 True True True True True True CRC_PASS -0.07105944506834289
52 Lab047 15 t2 True True True True True True CRC_PASS 0.0609663723289962
53 Lab047 16 t2 True True True True True True CRC_PASS -0.07015498366163844
54 Lab047 17 t2 True True True True True True CRC_PASS -0.11928091623072502
55 Lab047 18 t2 True True True True True True CRC_PASS 0.04019810967068637
56 Lab047 19 t2 True True True True True True CRC_PASS 0.015930924737407013
57 Lab047 20 t2 True True True True True True CRC_PASS -0.008585663400001449
58 Lab047 21 t2 True True True True True True CRC_PASS 0.03878383029001333
59 Lab047 22 t2 True True True True True True CRC_PASS 0.09427373288664782
60 Lab047 23 t2 True True True True True True CRC_PASS 0.0003722803565952149
61 Lab047 24 t2 True True True True True True CRC_PASS -0.008086735812339499
62 Lab047 25 t2 True True True True True True CRC_PASS 0.02904818250697064
63 Lab047 26 t2 True True True True True True CRC_PASS -0.00973707735031404
64 Lab047 27 t2 True True True True True True CRC_PASS 0.05145139978648825
65 Lab047 28 t2 True True True True True True CRC_PASS -0.07130299348886393
66 Lab047 29 t2 True True True True True True CRC_PASS -0.04495129868231881
67 Lab047 30 t2 True True True True True True CRC_PASS -0.11170196243926128
68 Lab047 31 t2 True True True True True True CRC_PASS -0.06762881454676493
69 Lab047 32 t2 True True True True True True CRC_PASS -0.05377742451538007
70 Lab047 33 t2 True True True True True True CRC_PASS -0.05983507118706565
71 Lab047 34 t2 True True True True True True CRC_PASS -0.03974346979852063
72 Lab047 35 t2 True True True True True True CRC_PASS -0.04857303052983457
73 Lab047 36 t2 True True True True True True CRC_PASS -0.0800518478779996
74 Lab054 1 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
75 Lab054 2 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
76 Lab054 3 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
77 Lab054 4 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
78 Lab054 5 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
79 Lab054 6 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
80 Lab054 7 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
81 Lab054 8 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
82 Lab054 9 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
83 Lab054 10 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
84 Lab054 11 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
85 Lab054 12 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
86 Lab054 13 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
87 Lab054 14 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
88 Lab054 15 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
89 Lab054 16 legacy False False False False False False PHY_HEADER_MAGIC_MISMATCH
90 Lab054 1 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
91 Lab054 2 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
92 Lab054 3 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
93 Lab054 4 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
94 Lab054 5 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
95 Lab054 6 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
96 Lab054 7 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
97 Lab054 8 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
98 Lab054 9 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
99 Lab054 10 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
100 Lab054 11 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
101 Lab054 12 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
102 Lab054 13 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
103 Lab054 14 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
104 Lab054 15 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH
105 Lab054 16 t2 False False False False False False PHY_HEADER_MAGIC_MISMATCH

View File

@@ -0,0 +1,6 @@
case,finite,max_abs_frequency_hz
single,True,0.0021597810674307996
short,True,0.0021597810674307996
long,True,0.0021597810674307996
amplitude_drop,True,0.0021597810674307996
noise,True,1.6764279120281116
1 case finite max_abs_frequency_hz
2 single True 0.0021597810674307996
3 short True 0.0021597810674307996
4 long True 0.0021597810674307996
5 amplitude_drop True 0.0021597810674307996
6 noise True 1.6764279120281116

View File

@@ -0,0 +1,184 @@
{
"lab": "Lab061",
"status": "COMPLETED",
"scope": "OFFLINE",
"rf_tx": 0,
"hardware_queries": 0,
"legacy_default": true,
"automatic_fallback": false,
"t2_parameters": {
"normalized_bandwidth": 0.0005,
"damping_factor": 0.7071067811865475,
"phase_coefficient": 0.0013324447407406753,
"frequency_coefficient": 8.882964938271169e-07,
"detector_clip_rad": 1.0471975511965976
},
"synthetic": {
"legacy": {
"header_pass_rate": 0.9230769230769231,
"crc_pass_rate": 0.8974358974358975
},
"t2": {
"header_pass_rate": 0.9487179487179487,
"crc_pass_rate": 0.9487179487179487
}
},
"t2_packet_success_by_initial_frequency_error": {
"-10": 0.6666666666666666,
"-5": 1.0,
"-2": 1.0,
"-1": 1.0,
"-0.5": 1.0,
"-0.25": 1.0,
"0": 1.0,
"0.25": 1.0,
"0.5": 1.0,
"1": 1.0,
"2": 1.0,
"5": 0.6666666666666666,
"10": 1.0
},
"real": {
"Lab047": {
"legacy": 36,
"t2": 36
},
"Lab054": {
"legacy": 0,
"t2": 0
}
},
"lab056": [
{
"label": "A1",
"symbol_rate": 20000,
"symbol_errors": 1,
"symbols": 2816,
"symbol_error_rate": 0.0003551136363636364,
"evm_rms": 0.31627242306137465,
"matched_component_power": 0.9760338688912713,
"residual_power": 0.09763095554074837,
"matched_residual_db": 9.998773482861347,
"final_frequency_hz": -0.03678470540537988,
"large_phase_slips": 0,
"loss_of_lock": false,
"bad_blocks": 0,
"longest_bad_state_seconds": 0.0,
"self_recovered": false,
"stable_lock": true
},
{
"label": "B1",
"symbol_rate": 500000,
"symbol_errors": 3290,
"symbols": 101120,
"symbol_error_rate": 0.032535601265822785,
"evm_rms": 0.8090614605506008,
"matched_component_power": 0.940782269995664,
"residual_power": 0.615817678774771,
"matched_residual_db": 1.840369716972757,
"final_frequency_hz": -2.3940002814874086,
"large_phase_slips": 0,
"loss_of_lock": false,
"bad_blocks": 0,
"longest_bad_state_seconds": 0.0,
"self_recovered": false,
"stable_lock": true
},
{
"label": "A2",
"symbol_rate": 20000,
"symbol_errors": 0,
"symbols": 2816,
"symbol_error_rate": 0.0,
"evm_rms": 0.31568433619231595,
"matched_component_power": 1.012966734394142,
"residual_power": 0.10094882078152588,
"matched_residual_db": 10.014939332951363,
"final_frequency_hz": 0.0013080327716195816,
"large_phase_slips": 0,
"loss_of_lock": false,
"bad_blocks": 0,
"longest_bad_state_seconds": 0.0,
"self_recovered": false,
"stable_lock": true
},
{
"label": "B2",
"symbol_rate": 500000,
"symbol_errors": 3792,
"symbols": 101120,
"symbol_error_rate": 0.0375,
"evm_rms": 0.8744054425662731,
"matched_component_power": 0.986059800896635,
"residual_power": 0.7539264125589239,
"matched_residual_db": 1.1657429573714726,
"final_frequency_hz": 1.9343992902116445,
"large_phase_slips": 0,
"loss_of_lock": false,
"bad_blocks": 0,
"longest_bad_state_seconds": 0.0,
"self_recovered": false,
"stable_lock": true
}
],
"background": {
"legacy": {
"headers": 0,
"packets": 0,
"candidates": 13
},
"t2": {
"headers": 0,
"packets": 0,
"candidates": 13
}
},
"saved_tx_control": {
"legacy": {
"headers": 16,
"packets": 16
},
"t2": {
"headers": 16,
"packets": 16
}
},
"robustness": [
{
"case": "single",
"finite": true,
"max_abs_frequency_hz": 0.0021597810674307996
},
{
"case": "short",
"finite": true,
"max_abs_frequency_hz": 0.0021597810674307996
},
{
"case": "long",
"finite": true,
"max_abs_frequency_hz": 0.0021597810674307996
},
{
"case": "amplitude_drop",
"finite": true,
"max_abs_frequency_hz": 0.0021597810674307996
},
{
"case": "noise",
"finite": true,
"max_abs_frequency_hz": 1.6764279120281116
}
],
"payload_truth_leak_test": "PASS: strict API and T2 have no reference payload input; causal prefix test PASS",
"production_decision": "READY_BEHIND_EXPLICIT_MODE",
"physical_packet": {
"bytes": 268,
"symbols": 5724,
"duration_seconds": 0.011532
},
"source_sha256": {
"data\\raw\\lab054\\rx_stream.cs8": "641886879964250c315dd399d469503808aae43fb319bef05e0bf2a78bf14585"
}
}

View File

@@ -0,0 +1,79 @@
quality,noise_sigma,initial_frequency_error_hz,initial_phase_rad,seed,mode,measured_evm_rms,candidates,HEADER_ACCEPTED,FEC_STARTED,FEC_PASS,CRC_STARTED,CRC_PASS,PACKET_OUTPUT
GOOD,2.0,-10,0.37,61000,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-10,0.37,61000,t2,0.01982606424337177,1,True,True,True,True,True,True
GOOD,2.0,-5,0.37,61001,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-5,0.37,61001,t2,0.019746181325301454,1,True,True,True,True,True,True
GOOD,2.0,-2,0.37,61002,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-2,0.37,61002,t2,0.019842442865977007,1,True,True,True,True,True,True
GOOD,2.0,-1,0.37,61003,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-1,0.37,61003,t2,0.019853572202090563,1,True,True,True,True,True,True
GOOD,2.0,-0.5,0.37,61004,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-0.5,0.37,61004,t2,0.01981070684458435,1,True,True,True,True,True,True
GOOD,2.0,-0.25,0.37,61005,legacy,,1,True,True,True,True,True,True
GOOD,2.0,-0.25,0.37,61005,t2,0.01993850807040547,1,True,True,True,True,True,True
GOOD,2.0,0,0.37,61006,legacy,,1,True,True,True,True,True,True
GOOD,2.0,0,0.37,61006,t2,0.019706768331930264,1,True,True,True,True,True,True
GOOD,2.0,0.25,0.37,61007,legacy,,1,True,True,True,True,True,True
GOOD,2.0,0.25,0.37,61007,t2,0.019694517437587915,1,True,True,True,True,True,True
GOOD,2.0,0.5,0.37,61008,legacy,,1,True,True,True,True,True,True
GOOD,2.0,0.5,0.37,61008,t2,0.01985943013101642,1,True,True,True,True,True,True
GOOD,2.0,1,0.37,61009,legacy,,1,True,True,True,True,True,True
GOOD,2.0,1,0.37,61009,t2,0.01973023527181751,1,True,True,True,True,True,True
GOOD,2.0,2,0.37,61010,legacy,,1,True,True,True,True,True,True
GOOD,2.0,2,0.37,61010,t2,0.019705618251859375,1,True,True,True,True,True,True
GOOD,2.0,5,0.37,61011,legacy,,1,True,True,True,True,True,True
GOOD,2.0,5,0.37,61011,t2,0.01945222847829509,1,True,True,True,True,True,True
GOOD,2.0,10,0.37,61012,legacy,,1,True,True,True,True,True,True
GOOD,2.0,10,0.37,61012,t2,0.01990311396663494,1,True,True,True,True,True,True
MEDIUM,24.0,-10,0.37,61100,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-10,0.37,61100,t2,0.19171434819251312,1,True,True,True,True,True,True
MEDIUM,24.0,-5,0.37,61101,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-5,0.37,61101,t2,0.19397897593420543,1,True,True,True,True,True,True
MEDIUM,24.0,-2,0.37,61102,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-2,0.37,61102,t2,0.19306787955568316,1,True,True,True,True,True,True
MEDIUM,24.0,-1,0.37,61103,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-1,0.37,61103,t2,0.19099888656356456,1,True,True,True,True,True,True
MEDIUM,24.0,-0.5,0.37,61104,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-0.5,0.37,61104,t2,0.19250262062488108,1,True,True,True,True,True,True
MEDIUM,24.0,-0.25,0.37,61105,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,-0.25,0.37,61105,t2,0.19221599734407988,1,True,True,True,True,True,True
MEDIUM,24.0,0,0.37,61106,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,0,0.37,61106,t2,0.19404155054144742,1,True,True,True,True,True,True
MEDIUM,24.0,0.25,0.37,61107,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,0.25,0.37,61107,t2,0.19412644854555466,1,True,True,True,True,True,True
MEDIUM,24.0,0.5,0.37,61108,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,0.5,0.37,61108,t2,0.18965910158764945,1,True,True,True,True,True,True
MEDIUM,24.0,1,0.37,61109,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,1,0.37,61109,t2,0.1948138602914489,1,True,True,True,True,True,True
MEDIUM,24.0,2,0.37,61110,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,2,0.37,61110,t2,0.19457771777218613,1,True,True,True,True,True,True
MEDIUM,24.0,5,0.37,61111,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,5,0.37,61111,t2,0.19327098549387706,1,True,True,True,True,True,True
MEDIUM,24.0,10,0.37,61112,legacy,,1,True,True,True,True,True,True
MEDIUM,24.0,10,0.37,61112,t2,0.19404771262916432,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-10,0.37,61200,legacy,,10,False,False,False,False,False,False
LAB056_B_LIKE,105.0,-10,0.37,61200,t2,,10,False,False,False,False,False,False
LAB056_B_LIKE,105.0,-5,0.37,61201,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-5,0.37,61201,t2,0.8603198486837162,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-2,0.37,61202,legacy,,14,True,True,True,True,False,False
LAB056_B_LIKE,105.0,-2,0.37,61202,t2,0.8346182114574645,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-1,0.37,61203,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-1,0.37,61203,t2,0.8258615362415264,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-0.5,0.37,61204,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-0.5,0.37,61204,t2,0.8399635622236176,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-0.25,0.37,61205,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,-0.25,0.37,61205,t2,0.850324698425276,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,0,0.37,61206,legacy,,14,False,False,False,False,False,False
LAB056_B_LIKE,105.0,0,0.37,61206,t2,0.8514425010406356,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,0.25,0.37,61207,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,0.25,0.37,61207,t2,0.859691631568735,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,0.5,0.37,61208,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,0.5,0.37,61208,t2,0.8454644268223601,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,1,0.37,61209,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,1,0.37,61209,t2,0.8408531075244954,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,2,0.37,61210,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,2,0.37,61210,t2,0.8303200529343647,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,5,0.37,61211,legacy,,23,False,False,False,False,False,False
LAB056_B_LIKE,105.0,5,0.37,61211,t2,,23,False,False,False,False,False,False
LAB056_B_LIKE,105.0,10,0.37,61212,legacy,,1,True,True,True,True,True,True
LAB056_B_LIKE,105.0,10,0.37,61212,t2,0.8454912251538359,1,True,True,True,True,True,True
1 quality noise_sigma initial_frequency_error_hz initial_phase_rad seed mode measured_evm_rms candidates HEADER_ACCEPTED FEC_STARTED FEC_PASS CRC_STARTED CRC_PASS PACKET_OUTPUT
2 GOOD 2.0 -10 0.37 61000 legacy 1 True True True True True True
3 GOOD 2.0 -10 0.37 61000 t2 0.01982606424337177 1 True True True True True True
4 GOOD 2.0 -5 0.37 61001 legacy 1 True True True True True True
5 GOOD 2.0 -5 0.37 61001 t2 0.019746181325301454 1 True True True True True True
6 GOOD 2.0 -2 0.37 61002 legacy 1 True True True True True True
7 GOOD 2.0 -2 0.37 61002 t2 0.019842442865977007 1 True True True True True True
8 GOOD 2.0 -1 0.37 61003 legacy 1 True True True True True True
9 GOOD 2.0 -1 0.37 61003 t2 0.019853572202090563 1 True True True True True True
10 GOOD 2.0 -0.5 0.37 61004 legacy 1 True True True True True True
11 GOOD 2.0 -0.5 0.37 61004 t2 0.01981070684458435 1 True True True True True True
12 GOOD 2.0 -0.25 0.37 61005 legacy 1 True True True True True True
13 GOOD 2.0 -0.25 0.37 61005 t2 0.01993850807040547 1 True True True True True True
14 GOOD 2.0 0 0.37 61006 legacy 1 True True True True True True
15 GOOD 2.0 0 0.37 61006 t2 0.019706768331930264 1 True True True True True True
16 GOOD 2.0 0.25 0.37 61007 legacy 1 True True True True True True
17 GOOD 2.0 0.25 0.37 61007 t2 0.019694517437587915 1 True True True True True True
18 GOOD 2.0 0.5 0.37 61008 legacy 1 True True True True True True
19 GOOD 2.0 0.5 0.37 61008 t2 0.01985943013101642 1 True True True True True True
20 GOOD 2.0 1 0.37 61009 legacy 1 True True True True True True
21 GOOD 2.0 1 0.37 61009 t2 0.01973023527181751 1 True True True True True True
22 GOOD 2.0 2 0.37 61010 legacy 1 True True True True True True
23 GOOD 2.0 2 0.37 61010 t2 0.019705618251859375 1 True True True True True True
24 GOOD 2.0 5 0.37 61011 legacy 1 True True True True True True
25 GOOD 2.0 5 0.37 61011 t2 0.01945222847829509 1 True True True True True True
26 GOOD 2.0 10 0.37 61012 legacy 1 True True True True True True
27 GOOD 2.0 10 0.37 61012 t2 0.01990311396663494 1 True True True True True True
28 MEDIUM 24.0 -10 0.37 61100 legacy 1 True True True True True True
29 MEDIUM 24.0 -10 0.37 61100 t2 0.19171434819251312 1 True True True True True True
30 MEDIUM 24.0 -5 0.37 61101 legacy 1 True True True True True True
31 MEDIUM 24.0 -5 0.37 61101 t2 0.19397897593420543 1 True True True True True True
32 MEDIUM 24.0 -2 0.37 61102 legacy 1 True True True True True True
33 MEDIUM 24.0 -2 0.37 61102 t2 0.19306787955568316 1 True True True True True True
34 MEDIUM 24.0 -1 0.37 61103 legacy 1 True True True True True True
35 MEDIUM 24.0 -1 0.37 61103 t2 0.19099888656356456 1 True True True True True True
36 MEDIUM 24.0 -0.5 0.37 61104 legacy 1 True True True True True True
37 MEDIUM 24.0 -0.5 0.37 61104 t2 0.19250262062488108 1 True True True True True True
38 MEDIUM 24.0 -0.25 0.37 61105 legacy 1 True True True True True True
39 MEDIUM 24.0 -0.25 0.37 61105 t2 0.19221599734407988 1 True True True True True True
40 MEDIUM 24.0 0 0.37 61106 legacy 1 True True True True True True
41 MEDIUM 24.0 0 0.37 61106 t2 0.19404155054144742 1 True True True True True True
42 MEDIUM 24.0 0.25 0.37 61107 legacy 1 True True True True True True
43 MEDIUM 24.0 0.25 0.37 61107 t2 0.19412644854555466 1 True True True True True True
44 MEDIUM 24.0 0.5 0.37 61108 legacy 1 True True True True True True
45 MEDIUM 24.0 0.5 0.37 61108 t2 0.18965910158764945 1 True True True True True True
46 MEDIUM 24.0 1 0.37 61109 legacy 1 True True True True True True
47 MEDIUM 24.0 1 0.37 61109 t2 0.1948138602914489 1 True True True True True True
48 MEDIUM 24.0 2 0.37 61110 legacy 1 True True True True True True
49 MEDIUM 24.0 2 0.37 61110 t2 0.19457771777218613 1 True True True True True True
50 MEDIUM 24.0 5 0.37 61111 legacy 1 True True True True True True
51 MEDIUM 24.0 5 0.37 61111 t2 0.19327098549387706 1 True True True True True True
52 MEDIUM 24.0 10 0.37 61112 legacy 1 True True True True True True
53 MEDIUM 24.0 10 0.37 61112 t2 0.19404771262916432 1 True True True True True True
54 LAB056_B_LIKE 105.0 -10 0.37 61200 legacy 10 False False False False False False
55 LAB056_B_LIKE 105.0 -10 0.37 61200 t2 10 False False False False False False
56 LAB056_B_LIKE 105.0 -5 0.37 61201 legacy 1 True True True True True True
57 LAB056_B_LIKE 105.0 -5 0.37 61201 t2 0.8603198486837162 1 True True True True True True
58 LAB056_B_LIKE 105.0 -2 0.37 61202 legacy 14 True True True True False False
59 LAB056_B_LIKE 105.0 -2 0.37 61202 t2 0.8346182114574645 1 True True True True True True
60 LAB056_B_LIKE 105.0 -1 0.37 61203 legacy 1 True True True True True True
61 LAB056_B_LIKE 105.0 -1 0.37 61203 t2 0.8258615362415264 1 True True True True True True
62 LAB056_B_LIKE 105.0 -0.5 0.37 61204 legacy 1 True True True True True True
63 LAB056_B_LIKE 105.0 -0.5 0.37 61204 t2 0.8399635622236176 1 True True True True True True
64 LAB056_B_LIKE 105.0 -0.25 0.37 61205 legacy 1 True True True True True True
65 LAB056_B_LIKE 105.0 -0.25 0.37 61205 t2 0.850324698425276 1 True True True True True True
66 LAB056_B_LIKE 105.0 0 0.37 61206 legacy 14 False False False False False False
67 LAB056_B_LIKE 105.0 0 0.37 61206 t2 0.8514425010406356 1 True True True True True True
68 LAB056_B_LIKE 105.0 0.25 0.37 61207 legacy 1 True True True True True True
69 LAB056_B_LIKE 105.0 0.25 0.37 61207 t2 0.859691631568735 1 True True True True True True
70 LAB056_B_LIKE 105.0 0.5 0.37 61208 legacy 1 True True True True True True
71 LAB056_B_LIKE 105.0 0.5 0.37 61208 t2 0.8454644268223601 1 True True True True True True
72 LAB056_B_LIKE 105.0 1 0.37 61209 legacy 1 True True True True True True
73 LAB056_B_LIKE 105.0 1 0.37 61209 t2 0.8408531075244954 1 True True True True True True
74 LAB056_B_LIKE 105.0 2 0.37 61210 legacy 1 True True True True True True
75 LAB056_B_LIKE 105.0 2 0.37 61210 t2 0.8303200529343647 1 True True True True True True
76 LAB056_B_LIKE 105.0 5 0.37 61211 legacy 23 False False False False False False
77 LAB056_B_LIKE 105.0 5 0.37 61211 t2 23 False False False False False False
78 LAB056_B_LIKE 105.0 10 0.37 61212 legacy 1 True True True True True True
79 LAB056_B_LIKE 105.0 10 0.37 61212 t2 0.8454912251538359 1 True True True True True True

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

View File

@@ -547,6 +547,26 @@
[JSON](../data/processed/lab060/summary.json),
[real CSV](../data/processed/lab060/real_results.csv),
[synthetic CSV](../data/processed/lab060/synthetic_results.csv).
- **STOP:** исходные IQ не изменились, Lab060 не коммитировалась, push не
выполнялся, Lab061 не создана. Следующий этап — отдельно согласовать решение
о внедрении T2 в штатный receiver.
- **Фиксация:** Lab060 зафиксирована коммитом
`92b0e0f8dd726f4420e8ef5d5e7c049357296e7c`; push не выполнялся.
## Lab061 — T2 в строгом приёмнике
- **Запись 055; только offline, RF TX=0.** T2 встроен в существующий
`PacketProcessor` как явный режим; legacy остался значением по умолчанию.
Автоматического CRC-fallback нет, строгие header/FEC/CRC правила не менялись.
- **Полный synthetic PHY 500K:** 39 случаев на режим, CFO от −10 до +10 Гц,
три качества. Header legacy/T2 92,307692/94,871795%, CRC
89,743590/94,871795%. На EVM0,844032 packets 9/13 против11/13.
- **Реальные контроли:** Lab047 legacy/T2 36/36; Lab054 0/16; saved TX 16/16;
background 0 headers и0 packets. Исторический Lab054 `NOT_A` сохранён.
- **Lab056:** средняя SER A/B 0,017756/3,501780%, потери lock нет. Реальная
широкополосная ошибка около3,5% остаётся физическим ограничением.
- **Решение:** `READY_BEHIND_EXPLICIT_MODE` до контролируемого RF-теста с
полными 500K-пакетами. [План](lab061_current_state.md),
[отчёт](lab061_offline_result.md),
[JSON](../data/processed/lab061/summary.json),
[real CSV](../data/processed/lab061/real_results.csv),
[synthetic CSV](../data/processed/lab061/synthetic_packet_results.csv).
- **STOP:** Lab061 фиксируется этим отдельным коммитом; push выполняется
пользователем отдельно, Lab062 не создана.

View File

@@ -0,0 +1,52 @@
# Lab061 — план интеграции T2 в строгий приёмник
Lab061 выполняется только офлайн. `RF TX = 0`; HackRF не опрашивается.
## Фактический путь приёмника
`lab053_streaming_receiver.StreamingReceiver` выполняет независимый поиск
кандидатов и передаёт заранее выбранному `PacketProcessor` IQ-окно. Внутри
`PacketProcessor` последовательно работают RRC-фильтр, временная
синхронизация, оценка несущей, извлечение мягких символов, строгий разбор PHY
header, Viterbi FEC, parser и CRC.
T2 добавляется в этот существующий путь после RRC и timing. Начальное состояние
P3 вычисляется по квадрату всей 1280-символьной преамбулы. После преамбулы T2
видит только текущий символ, собственное BPSK-решение и предыдущее состояние.
Header, payload, CRC и будущие символы ему недоступны.
Среднее DC в T2 считается только до последнего raw-отсчёта, который через
причинную импульсную характеристику RRC способен повлиять на последнюю точку
P3. Отсчёты header и payload не входят и в эту подготовительную оценку.
Режим задаётся до запуска как `carrier_tracking_mode="legacy"` или `"t2"`.
Значение по умолчанию — `legacy`. Повторной попытки после CRC FAIL нет.
## Зафиксированная математика
При нормированной полосе `Bn=0.0005` и затухании `zeta=1/sqrt(2)`:
`theta = Bn / (zeta + 1/(4*zeta))`
`alpha = 4*zeta*theta / (1 + 2*zeta*theta + theta^2)`
`beta = 4*theta^2 / (1 + 2*zeta*theta + theta^2)`
Получаются `alpha=0.0013324447407406753` и
`beta=8.882964938271169e-7` на один символ. Частотное состояние внутри петли
хранится в радианах на символ; для отчёта оно переводится в герцы как
`omega*Rs/(2*pi)`. Коэффициенты Lab060 не перенастраиваются.
## Критерии
- чистый полный штатный PHY-пакет 500 ksymbol/s проходит поиск, header, FEC и CRC;
- сравнение legacy/T2 выполняется при заранее выбранном режиме;
- Lab047 остаётся 36/36 в обоих режимах;
- Lab054 остаётся историческим `NOT_A, 0/16`, если строгий путь его не восстановит;
- background даёт ноль header и packet;
- изменение диагностической reference payload не меняет решения или траекторию T2;
- алгоритм остаётся конечным при ошибках символов, провале амплитуды и шуме.
После завершения и независимой проверки Lab061 фиксируется отдельным коммитом
по разрешению пользователя. Push выполняется пользователем отдельно. Lab062 не
создаётся автоматически.

View File

@@ -0,0 +1,92 @@
# Lab061 — T2 в строгом приёмнике
## Результат простыми словами
T2 перенесён из диагностической Lab060 в тот же строгий путь, который ищет
пакет, синхронизируется, разбирает PHY header, запускает FEC и принимает пакет
только после CRC PASS. Режим включается явно; обычный запуск по-прежнему
использует legacy. Скрытого повторного декодирования нет.
На сохранённом Lab047 оба режима приняли 36 из 36 пакетов. На Lab054 оба дали
0 из 16: исторический результат `NOT_A` не изменён. Чистая сохранённая TX-форма
дала 16/16 в обоих режимах, фон — ноль допустимых заголовков и пакетов.
## Полный синтетический PHY-пакет 500K
Штатный генератор сформировал преамбулу, повторённый header, FEC-кодированное
тело и CRC. Проверено 39 реализаций для каждого режима: 13 начальных ошибок
частоты от −10 до +10 Гц, фиксированная начальная фаза и три уровня шума.
Условное среднее измеренное EVM уровня `LAB056_B_LIKE` равно 0,844032 по 11
полным T2-траекториям из 13; два случая, остановившиеся раньше полного
извлечения, в это среднее не входят.
| Режим | Header | CRC и packet |
|---|---:|---:|
| legacy | 92,307692% | 89,743590% |
| T2 | 94,871795% | 94,871795% |
На качестве около Lab056 B legacy принял 9/13 пакетов, T2 — 11/13. На двух
лучших уровнях оба режима приняли 26/26. По всем трём уровням доля T2 для CFO
−10/−5/−2/−1/−0,5/−0,25/0/+0,25/+0,5/+1/+2/+5/+10 Гц составила
66,67/100/100/100/100/100/100/100/100/100/100/66,67/100%.
Текущий штатный пакет с 256 байтами payload содержит 268 packet bytes и 5724
символа; его сформированная длительность — 11,532 мс. Указанные ранее 204,8 мс
относятся к длинному диагностическому B-burst Lab056 из 102400 символов, а не к
этому штатному пакету. На всём B-burst T2 также проверен: срыва сопровождения
нет.
## Сохранённые данные
Средняя ошибка символов Lab056 после интегрированного ядра T2:
- A: `(1 + 0) / (2816 + 2816) = 0,017756%`;
- B: `(3290 + 3792) / (101120 + 101120) = 3,501780%`.
B1/B2 дали 3,253560% и 3,750000%; A1/A2 — 0,035511% и 0%. Все четыре
траектории сохранили устойчивый внутренний lock без больших фазовых скачков.
Для 36 пакетов Lab047 начальная P3-оценка лежала от −0,119281 до +0,207932 Гц.
Это далеко внутри проверенного диапазона; прежнее ограничение 20 ksymbol/s при
искусственной ошибке до ±10 Гц сохраняется. Отдельная настройка 20K не вводилась.
Lab054 дал в обоих режимах 0 header, 0 FEC, 0 CRC и 0 packet из 16 проверенных
исторических кандидатов. T2 не переопределяет этот отрицательный результат.
## Независимость и отказоустойчивость
T2 не принимает reference payload. Тест причинности меняет последний будущий
символ и подтверждает полное равенство всех предыдущих решений и состояний.
Отдельный тест меняет все raw-отсчёты после причинной границы P3 и подтверждает
равенство начальных частоты и комплексного коэффициента P3 с допуском `1e-8`;
остаток порядка `4e-9 Гц` возникает из-за округления FFT-свёртки.
Изменение диагностической reference меняет только последующее сравнение; путь
приёма и его RX bits от неё не зависят.
Одиночная ошибка, серии из 10 и 80 ошибок, провал амплитуды и шумовой участок
не создали NaN/Inf. Максимальное наблюдаемое частотное состояние составило
1,676428 Гц; дополнительный state clamp не вводился. На реальном фоне оба
режима проверили 13 кандидатов и не приняли ни одного заголовка или пакета.
## Решение
`PRODUCTION DECISION = READY_BEHIND_EXPLICIT_MODE`.
T2 численно совпадает с Lab060, полезен на полном строгом пути и не создаёт
регрессии Lab047, saved TX или background. Его рано делать режимом по умолчанию:
Lab054 остаётся 0/16, а контролируемого RF-сеанса с полноценными 500K-пакетами и
заранее выбранным T2 ещё не было.
После исправления программного сопровождения реальный широкий B-сигнал Lab056
всё равно имеет около 3,5% ошибочных символов при хорошем диагностическом
выравнивании. Следующая физическая задача — проверить энергетику и качество
широкополосного канала на полноценном пакете в одном заранее разрешённом
контролируемом опыте. Это отдельная лаборатория и здесь не выполнялось.
[Сводка JSON](../data/processed/lab061/summary.json),
[реальные результаты](../data/processed/lab061/real_results.csv),
[синтетические пакеты](../data/processed/lab061/synthetic_packet_results.csv),
[траектории](../data/processed/lab061/trajectories.npz).
`RF TX = 0`. Lab061 фиксируется этим отдельным коммитом. Push выполняется
пользователем отдельно. Lab062 не создавалась.

View File

@@ -25,6 +25,7 @@ from scipy.signal import oaconvolve
from experiments import lab052_candidate_detection as detection
from experiments.lab053_viterbi import viterbi_decode_soft, warmup
from protocol import bpsk_carrier_tracking as carrier_tracking
previous = detection.previous
physical = previous.lab045
@@ -83,9 +84,11 @@ def measured(name):
@lru_cache(maxsize=16)
def rotation(length: int) -> np.ndarray:
def rotation(length: int, sample_rate_hz: int = SAMPLE_RATE) -> np.ndarray:
indexes = np.arange(length)
values = np.exp(-2j * np.pi * lab043.CARRIER_OFFSET_HZ * indexes / SAMPLE_RATE).astype(np.complex64)
values = np.exp(
-2j * np.pi * lab043.CARRIER_OFFSET_HZ * indexes / sample_rate_hz
).astype(np.complex64)
values.flags.writeable = False
return values
@@ -97,24 +100,46 @@ class PacketProcessor:
Полное тело по-прежнему запрашивается только после валидного PHY header.
"""
def __init__(self, *, viterbi=viterbi_decode_soft):
def __init__(
self,
*,
viterbi=viterbi_decode_soft,
carrier_tracking_mode: str = "legacy",
sample_rate_hz: int = SAMPLE_RATE,
symbol_rate: int = SYMBOL_RATE,
):
if carrier_tracking_mode not in ("legacy", "t2"):
raise ValueError("carrier_tracking_mode должен быть legacy или t2")
if type(sample_rate_hz) is not int or type(symbol_rate) is not int:
raise TypeError("Fs и Rs должны быть целыми")
if sample_rate_hz <= 0 or symbol_rate <= 0 or sample_rate_hz % symbol_rate:
raise ValueError("Fs должен быть положительным целым кратным Rs")
self.carrier_tracking_mode = carrier_tracking_mode
self.sample_rate_hz = sample_rate_hz
self.symbol_rate = symbol_rate
self.samples_per_symbol = sample_rate_hz // symbol_rate
self.startup = warmup() if viterbi is viterbi_decode_soft else {"backend": "CUSTOM", "warmup_seconds": 0}
constants = {"SAMPLES_PER_SYMBOL": SAMPLES_PER_SYMBOL, "SYMBOL_RATE": SYMBOL_RATE}
constants = {
"SAMPLES_PER_SYMBOL": self.samples_per_symbol,
"SYMBOL_RATE": self.symbol_rate,
}
scale = bind(physical.lab044._scale_and_carrier, **constants)
self.bits = physical.lab044.preamble_bits().copy()
waveform = scale(previous.iq.radio.bpsk_modulate(self.bits))
self.reference = physical.lab044.signed_iq_bytes_to_complex64(lab043.to_cs8_bytes(waveform))
self.taps = bind(lab043._matched_filter_taps, **constants)()
self.expected = previous.iq.radio.bpsk_modulate(self.bits)
self.kernel = np.zeros((len(self.bits) - 1) * SAMPLES_PER_SYMBOL + 1, np.complex64)
self.kernel[::SAMPLES_PER_SYMBOL] = np.conj(self.expected)
self.kernel = np.zeros(
(len(self.bits) - 1) * self.samples_per_symbol + 1, np.complex64
)
self.kernel[::self.samples_per_symbol] = np.conj(self.expected)
self.kernel = self.kernel[::-1].copy()
for values in (self.bits, self.reference, self.taps, self.expected, self.kernel):
values.flags.writeable = False
slow_offsets = bind(lab043._timing_offsets, **constants)
slow_timing = bind(previous.ORIGINAL_TIMING, _timing_offsets=slow_offsets, **constants)
self.timing = measured("timing")(bind(previous.fast_preamble_timing,
lab045=SimpleNamespace(SAMPLES_PER_SYMBOL=SAMPLES_PER_SYMBOL),
lab045=SimpleNamespace(SAMPLES_PER_SYMBOL=self.samples_per_symbol),
timing_kernel=lambda unused: self.kernel, ORIGINAL_TIMING=slow_timing))
estimate = measured("cfo_estimation")(bind(lab043._estimate_cfo_gain, **constants))
self.carrier = measured("phase_polarity")(bind(lab043._evaluate_symbols,
@@ -142,7 +167,20 @@ class PacketProcessor:
@measured("frequency_shift")
def shift(self, segment):
return (segment - np.mean(segment)) * rotation(len(segment))
if self.carrier_tracking_mode == "legacy":
dc_reference = segment
else:
# Последний raw-отсчёт, способный попасть через причинный RRC в
# последнюю P3-точку преамбулы. Header в оценку DC не попадает.
fixed_prefix = (
16 * self.samples_per_symbol
+ self.taps.size
+ (carrier_tracking.PREAMBLE_SYMBOLS - 1) * self.samples_per_symbol
)
dc_reference = segment[:min(len(segment), fixed_prefix)]
return (segment - np.mean(dc_reference)) * rotation(
len(segment), self.sample_rate_hz
)
@measured("matched_rrc_filtering")
def filter(self, values):
@@ -150,22 +188,55 @@ class PacketProcessor:
@measured("soft_symbol_extraction")
def normalize(self, matched, nominal, timing, count, carrier):
symbols = matched[nominal + timing:nominal + timing + count * SAMPLES_PER_SYMBOL:SAMPLES_PER_SYMBOL].astype(np.complex128)
symbols = matched[
nominal + timing:
nominal + timing + count * self.samples_per_symbol:
self.samples_per_symbol
].astype(np.complex128)
if symbols.size != count:
return None
symbol_time = (np.arange(count, dtype=np.float64) + .5) / SYMBOL_RATE
normalized = symbols * np.exp(-2j * math.pi * carrier["cfo_hz"] * symbol_time)
normalized /= carrier["abs_h"] * np.exp(1j * carrier["phase_rad"])
return normalized.real
if self.carrier_tracking_mode == "legacy":
symbol_time = (
np.arange(count, dtype=np.float64) + 0.5
) / self.symbol_rate
normalized = symbols * np.exp(
-2j * math.pi * carrier["cfo_hz"] * symbol_time
)
normalized /= carrier["abs_h"] * np.exp(1j * carrier["phase_rad"])
return normalized.real
if count < carrier_tracking.PREAMBLE_SYMBOLS:
return None
estimate = carrier_tracking.estimate_p3(
symbols[:carrier_tracking.PREAMBLE_SYMBOLS],
self.expected,
self.symbol_rate,
)
carrier.update({
"cfo_hz": estimate.frequency_hz,
"abs_h": float(abs(estimate.gain)),
"phase_rad": float(np.angle(estimate.gain)),
"polarity": "resolved from P3 full preamble",
})
result = carrier_tracking.track_t2(
symbols,
self.symbol_rate,
estimate,
start_index=carrier_tracking.PREAMBLE_SYMBOLS,
)
context = CURRENT.get()
if context is not None:
context.cache.setdefault("carrier_tracking_traces", []).append(result)
return result.corrected.real
@measured("extract_total")
def extract(self, samples, frame_start, symbol_count):
length = (symbol_count + 42) * SAMPLES_PER_SYMBOL
length = (symbol_count + 42) * self.samples_per_symbol
if frame_start < 0 or frame_start + length > samples.size:
return previous.iq.SoftWindow(False, np.empty(0), 0, 0, 0, {}, "TRUNCATED_IQ_WINDOW")
segment = samples[frame_start:frame_start + length]
matched = self.filter(self.shift(segment))
nominal = 16 * SAMPLES_PER_SYMBOL + self.taps.size - 1
nominal = 16 * self.samples_per_symbol + self.taps.size - 1
timing = self.timing(matched, nominal, self.expected)
carrier = self.carrier(matched, nominal, timing, self.bits, len(self.bits))
if carrier["abs_h"] == 0:
@@ -174,7 +245,7 @@ class PacketProcessor:
if values is None:
return previous.iq.SoftWindow(False, np.empty(0), timing, 0, nominal, carrier, "TRUNCATED_SYMBOLS")
return previous.iq.SoftWindow(True, values, int(timing),
int((frame_start + nominal + timing) % SAMPLES_PER_SYMBOL), nominal, carrier, None)
int((frame_start + nominal + timing) % self.samples_per_symbol), nominal, carrier, None)
def decode(self, samples, candidate, number=1):
context = PacketMeasure()
@@ -189,7 +260,11 @@ class PacketProcessor:
"thread_cpu_seconds": time.thread_time() - cpu_started,
"stages": dict(context.seconds), "exclusive_stages": dict(context.exclusive_seconds),
"calls": dict(context.calls), "coarse": candidate.sample_offset,
"refined": outcome.refined_start_sample}
"refined": outcome.refined_start_sample,
"carrier_tracking_mode": self.carrier_tracking_mode,
"carrier_tracking_traces": tuple(
context.cache.get("carrier_tracking_traces", ())
)}
if outcome.frame is not None:
row.update(timing=outcome.frame.timing_offset_samples, cfo_hz=outcome.frame.estimated_cfo_hz,
phase_rad=outcome.frame.estimated_phase_rad, polarity=outcome.frame.bpsk_polarity)
@@ -201,6 +276,7 @@ class PacketProcessor:
def run_functional_tests() -> None:
processor = PacketProcessor()
assert len(processor.bits) == 1280 and len(processor.taps) == 161
assert processor.carrier_tracking_mode == "legacy"
assert not processor.reference.flags.writeable

View File

@@ -0,0 +1,260 @@
"""Lab061: офлайн-интеграция T2 в существующий строгий приёмник."""
from __future__ import annotations
import csv
import hashlib
import json
import math
from pathlib import Path
import sys
if __package__ in (None, ""):
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import matplotlib.pyplot as plt
import numpy as np
from experiments import lab045_hackrf_bpsk_fec as lab045
from experiments import lab049_bpsk_symbol_rate as lab049
from experiments import lab052_candidate_detection as detection
from experiments import lab053_packet_processing as packet
from experiments import lab053_streaming_receiver as streaming
from experiments import lab058_abab_symbol_quality as lab058
from experiments import lab059_preamble_carrier_limits as lab059
from experiments import lab060_decision_directed_carrier_tracking as lab060
from protocol import bpsk_carrier_tracking as tracking
ROOT = Path(__file__).resolve().parents[1]
OUTPUT = ROOT / "data/processed/lab061"
FREQUENCIES = (-10, -5, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 5, 10)
QUALITIES = {"GOOD": 2.0, "MEDIUM": 24.0, "LAB056_B_LIKE": 105.0}
def write_csv(path: Path, rows: list[dict]) -> None:
if not rows:
return
columns = list(rows[0])
with path.open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, columns)
writer.writeheader()
writer.writerows(rows)
def full_packet() -> tuple[np.ndarray, bytes, np.ndarray]:
payload = bytes(range(256))
plan = lab045.build_transmission_plan(payload)
raw = np.frombuffer(
lab049.build_parameterized_tx_cs8(plan, lab049.PhyMode(500_000)), np.int8
)
values = raw[::2].astype(np.float32) + 1j * raw[1::2].astype(np.float32)
expected = lab049.radio.bpsk_modulate(plan.frame_bits)
return values.astype(np.complex64), payload, expected
def stream_decode(values: np.ndarray, mode: str) -> tuple[list, streaming.StreamingReceiver, list[dict]]:
observed: list[dict] = []
receiver = streaming.StreamingReceiver(
processor=packet.PacketProcessor(carrier_tracking_mode=mode),
packet_observer=lambda row, outcome: observed.append({
"reason": outcome.reason,
"accepted": outcome.accepted,
"header": bool(outcome.phy_header_pass),
"fec": bool(outcome.fec_pass),
"crc": outcome.crc == "PASS",
"traces": row["carrier_tracking_traces"],
}),
)
output = []
for start in range(0, len(values), receiver.block_samples):
output.extend(receiver.push_iq_block(values[start:start + receiver.block_samples]))
output.extend(receiver.finish())
return output, receiver, observed
def synthetic_study() -> tuple[list[dict], dict[str, np.ndarray]]:
clean, payload, expected = full_packet()
indexes = np.arange(len(clean))
rows: list[dict] = []
representative: dict[str, np.ndarray] = {}
for quality_number, (quality, noise_sigma) in enumerate(QUALITIES.items()):
for frequency in FREQUENCIES:
seed = 61_000 + quality_number * 100 + FREQUENCIES.index(frequency)
random_generator = np.random.default_rng(seed)
noise = random_generator.normal(0, noise_sigma, len(clean)) + 1j * random_generator.normal(0, noise_sigma, len(clean))
impaired = clean * np.exp(1j * (0.37 + 2 * np.pi * frequency * indexes / 8_000_000)) + noise
values = np.concatenate((np.zeros(20_000), impaired, np.zeros(20_000))).astype(np.complex64)
for mode in ("legacy", "t2"):
output, receiver, observed = stream_decode(values, mode)
header = any(row["header"] for row in observed)
fec = any(row["fec"] for row in observed)
crc = any(row["crc"] for row in observed)
traces = [trace for item in observed for trace in item["traces"]]
full_trace = max(traces, key=lambda item: len(item.corrected)) if traces else None
measured_evm = None
if full_trace is not None and len(full_trace.corrected) == len(expected):
selected = slice(1280, None)
gain = np.mean(full_trace.corrected[selected] * expected[selected])
residual = full_trace.corrected[selected] - gain * expected[selected]
measured_evm = float(np.sqrt(np.mean(abs(residual)**2) / abs(gain)**2))
rows.append({
"quality": quality, "noise_sigma": noise_sigma,
"initial_frequency_error_hz": frequency, "initial_phase_rad": 0.37,
"seed": seed, "mode": mode, "measured_evm_rms": measured_evm,
"candidates": receiver.candidate_count,
"HEADER_ACCEPTED": header, "FEC_STARTED": header,
"FEC_PASS": fec, "CRC_STARTED": fec, "CRC_PASS": crc,
"PACKET_OUTPUT": len(output) == 1 and output[0].payload == payload,
})
if mode == "t2" and quality == "LAB056_B_LIKE" and frequency == 10 and observed:
if traces:
trace = max(traces, key=lambda item: len(item.phase_rad))
representative = {
"phase_rad": trace.phase_rad,
"frequency_hz": trace.frequency_hz,
"detector_error_rad": trace.detector_error_rad,
}
return rows, representative
def decode_known_candidates() -> list[dict]:
lab054_items, lab047_items, lab054_reader, lab047_reader = lab059._packet_sources()
rows: list[dict] = []
for dataset, symbol_rate, items, reader, key in (
("Lab047", 20_000, lab047_items, lab047_reader, "candidate_offset"),
("Lab054", 500_000, lab054_items, lab054_reader, "coarse_start"),
):
for mode in ("legacy", "t2"):
processor = packet.PacketProcessor(carrier_tracking_mode=mode, symbol_rate=symbol_rate)
for number, item in enumerate(items, 1):
start = int(item[key])
low = max(0, start - 4_000)
length = 2_400_000 if symbol_rate == 20_000 else 120_000
values = reader.read(low, min(length, reader.size - low))
candidate = detection.offline.CoarseCandidate(start - low, 1.0)
outcome, detail = processor.decode(values, candidate, number)
rows.append({
"dataset": dataset, "frame": number, "mode": mode,
"HEADER_ACCEPTED": outcome.phy_header_pass,
"FEC_STARTED": outcome.phy_header_pass,
"FEC_PASS": outcome.fec_pass,
"CRC_STARTED": outcome.fec_pass,
"CRC_PASS": outcome.crc == "PASS", "PACKET_OUTPUT": outcome.accepted,
"reason": outcome.reason, "initial_frequency_hz": detail.get("cfo_hz"),
})
return rows
def lab056_study() -> tuple[list[dict], dict[str, np.ndarray]]:
rows, trajectories = [], {}
for item in lab060._lab056_data():
source = item["estimate"]
estimate = tracking.P3CarrierEstimate(
source.frequency_hz, source.gain, source.phase_fit_rmse_rad, source.mean_block_coherence
)
result = tracking.track_t2(item["symbols"], item["symbol_rate"], estimate)
compatible = lab060.TrackResult(
tracker="T2", corrected=result.corrected,
hard_decisions=result.hard_decisions, phase_rad=result.phase_rad,
frequency_hz=result.frequency_hz,
detector_error_rad=result.detector_error_rad,
parameters=lab060.loop_parameters("T2", 0.0005),
initial_frequency_hz=estimate.frequency_hz,
initial_phase_rad=float(np.angle(estimate.gain)),
)
metrics, blocks = lab060.evaluate_tracking(compatible, item["expected"], item["symbol_rate"])
rows.append({"label": item["label"], "symbol_rate": item["symbol_rate"], **{k: v for k, v in metrics.items() if k != "receiver_lock"}, "stable_lock": metrics["receiver_lock"]["stable_lock"]})
trajectories[f"{item['label']}_phase_rad"] = result.phase_rad
trajectories[f"{item['label']}_frequency_hz"] = result.frequency_hz
return rows, trajectories
def background_study() -> dict:
source = json.loads((ROOT / "data/processed/lab060/summary.json").read_text(encoding="utf-8"))
background = source["background"][0]
start, count = int(background["rx_start"]), int(background["samples"])
reader = lab058.Cs8File(ROOT / "data/raw/lab054/rx_stream.cs8")
values = reader.read(start, count)
results = {}
for mode in ("legacy", "t2"):
output, receiver, observed = stream_decode(values, mode)
results[mode] = {"headers": sum(row["header"] for row in observed), "packets": len(output), "candidates": receiver.candidate_count}
return results
def saved_tx_control() -> dict:
reader = lab058.Cs8File(ROOT / "data/raw/lab054/tx_stream.cs8")
values = reader.read(0, reader.size)
results = {}
for mode in ("legacy", "t2"):
output, receiver, observed = stream_decode(values, mode)
results[mode] = {"headers": sum(row["header"] for row in observed), "packets": len(output)}
return results
def robustness() -> list[dict]:
symbols, expected, unused = lab060.clean_rrc_symbols(500_000)
estimate = tracking.estimate_p3(symbols[:1280], expected[:1280], 500_000)
cases = {}
for name, selected in (("single", slice(2000, 2001)), ("short", slice(2500, 2510)), ("long", slice(3000, 3080))):
damaged = symbols.astype(np.complex128)
damaged[selected] *= -1
cases[name] = damaged
fade = symbols.astype(np.complex128); fade[3500:3700] *= 0.02; cases["amplitude_drop"] = fade
noise = symbols.astype(np.complex128); random_generator = np.random.default_rng(61999); noise[4000:4300] = random_generator.normal(size=300) + 1j*random_generator.normal(size=300); cases["noise"] = noise
rows = []
for name, values in cases.items():
result = tracking.track_t2(values, 500_000, estimate)
rows.append({"case": name, "finite": bool(np.all(np.isfinite(result.corrected)) and np.all(np.isfinite(result.frequency_hz))), "max_abs_frequency_hz": float(np.max(abs(result.frequency_hz)))})
return rows
def plots(synthetic: list[dict], representative: dict, real: list[dict], background: dict) -> None:
plt.figure(); plt.plot(np.arange(len(representative["phase_rad"]))/500_000, representative["phase_rad"]); plt.xlabel("Время, с"); plt.ylabel("Фаза T2, рад"); plt.tight_layout(); plt.savefig(OUTPUT/"synthetic_t2_phase.png", dpi=160); plt.close()
plt.figure(); plt.plot(np.arange(len(representative["frequency_hz"]))/500_000, representative["frequency_hz"]); plt.xlabel("Время, с"); plt.ylabel("Частотное состояние, Гц"); plt.tight_layout(); plt.savefig(OUTPUT/"synthetic_t2_frequency.png", dpi=160); plt.close()
for mode in ("legacy", "t2"):
values = [np.mean([r["PACKET_OUTPUT"] for r in synthetic if r["mode"] == mode and r["initial_frequency_error_hz"] == f]) for f in FREQUENCIES]
plt.plot(FREQUENCIES, values, marker="o", label=mode)
plt.xlabel("Начальная ошибка частоты, Гц"); plt.ylabel("Доля пакетов"); plt.legend(); plt.tight_layout(); plt.savefig(OUTPUT/"packet_success_by_initial_cfo.png", dpi=160); plt.close()
for dataset, filename in (("Lab047", "lab047_regression.png"), ("Lab054", "lab054_strict_result.png")):
values = [sum(r["PACKET_OUTPUT"] for r in real if r["dataset"] == dataset and r["mode"] == mode) for mode in ("legacy", "t2")]
plt.figure(); plt.bar(("legacy", "t2"), values); plt.ylabel("CRC PASS пакеты"); plt.tight_layout(); plt.savefig(OUTPUT/filename, dpi=160); plt.close()
plt.figure(); plt.bar(("legacy headers", "legacy packets", "T2 headers", "T2 packets"), (background["legacy"]["headers"], background["legacy"]["packets"], background["t2"]["headers"], background["t2"]["packets"])); plt.ylabel("Количество"); plt.tight_layout(); plt.savefig(OUTPUT/"background_result.png", dpi=160); plt.close()
def main() -> None:
OUTPUT.mkdir(parents=True, exist_ok=True)
synthetic, representative = synthetic_study()
real = decode_known_candidates()
lab056, lab056_trajectories = lab056_study()
background = background_study()
tx_control = saved_tx_control()
robust = robustness()
write_csv(OUTPUT/"synthetic_packet_results.csv", synthetic)
write_csv(OUTPUT/"real_results.csv", real)
write_csv(OUTPUT/"lab056_results.csv", lab056)
write_csv(OUTPUT/"robustness_results.csv", robust)
np.savez_compressed(OUTPUT/"trajectories.npz", **representative, **lab056_trajectories)
plots(synthetic, representative, real, background)
by_mode = {mode: {"header_pass_rate": float(np.mean([r["HEADER_ACCEPTED"] for r in synthetic if r["mode"] == mode])), "crc_pass_rate": float(np.mean([r["CRC_PASS"] for r in synthetic if r["mode"] == mode]))} for mode in ("legacy", "t2")}
by_frequency = {str(f): float(np.mean([r["PACKET_OUTPUT"] for r in synthetic if r["mode"] == "t2" and r["initial_frequency_error_hz"] == f])) for f in FREQUENCIES}
summary = {
"lab": "Lab061", "status": "COMPLETED", "scope": "OFFLINE", "rf_tx": 0,
"hardware_queries": 0, "legacy_default": True, "automatic_fallback": False,
"t2_parameters": vars(tracking.t2_parameters()),
"synthetic": by_mode, "t2_packet_success_by_initial_frequency_error": by_frequency,
"real": {dataset: {mode: sum(r["PACKET_OUTPUT"] for r in real if r["dataset"] == dataset and r["mode"] == mode) for mode in ("legacy", "t2")} for dataset in ("Lab047", "Lab054")},
"lab056": lab056, "background": background, "saved_tx_control": tx_control,
"robustness": robust,
"payload_truth_leak_test": "PASS: strict API and T2 have no reference payload input; causal prefix test PASS",
"production_decision": "READY_BEHIND_EXPLICIT_MODE",
"physical_packet": {"bytes": 268, "symbols": 5724, "duration_seconds": len(full_packet()[0])/8_000_000},
"source_sha256": {str(path.relative_to(ROOT)): hashlib.sha256(path.read_bytes()).hexdigest() for path in (ROOT/"data/raw/lab054/rx_stream.cs8",)},
}
(OUTPUT/"summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2, default=lambda value: value.item()), encoding="utf-8")
print(json.dumps(summary, ensure_ascii=False, indent=2, default=lambda value: value.item()))
print("RF TX = 0")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,230 @@
"""Причинное сопровождение несущей BPSK после известной преамбулы.
Модуль не разбирает заголовок, полезную нагрузку, FEC или CRC. Начальное
состояние P3 оценивается только по переданным в функцию символам преамбулы.
После неё T2 получает текущий комплексный символ, собственное решение и своё
предыдущее состояние.
"""
from __future__ import annotations
from dataclasses import dataclass
import math
import numpy as np
PREAMBLE_SYMBOLS = 1280
P3_BLOCK_SYMBOLS = 20
NORMALIZED_LOOP_BANDWIDTH = 0.0005
DAMPING_FACTOR = 1.0 / math.sqrt(2.0)
DETECTOR_CLIP_RAD = math.pi / 3.0
@dataclass(frozen=True)
class P3CarrierEstimate:
"""Начальное состояние в физических единицах при времени символа 0."""
frequency_hz: float
gain: complex
phase_fit_rmse_rad: float
squared_coherence: float
@dataclass(frozen=True)
class T2Parameters:
"""Коэффициенты петли на один символ."""
normalized_bandwidth: float
damping_factor: float
phase_coefficient: float
frequency_coefficient: float
detector_clip_rad: float
@dataclass(frozen=True)
class T2TrackingResult:
"""Применённое состояние и решения для одного последовательного прохода."""
corrected: np.ndarray
hard_decisions: np.ndarray
phase_rad: np.ndarray
frequency_hz: np.ndarray
detector_error_rad: np.ndarray
initial_estimate: P3CarrierEstimate
parameters: T2Parameters
def t2_parameters(
normalized_bandwidth: float = NORMALIZED_LOOP_BANDWIDTH,
damping_factor: float = DAMPING_FACTOR,
) -> T2Parameters:
"""Перевести нормированную полосу в коэффициенты Lab060 на символ."""
if not np.isfinite(normalized_bandwidth) or normalized_bandwidth <= 0.0:
raise ValueError("Нормированная полоса должна быть положительной")
if not np.isfinite(damping_factor) or damping_factor <= 0.0:
raise ValueError("Коэффициент затухания должен быть положительным")
theta = normalized_bandwidth / (
damping_factor + 0.25 / damping_factor
)
denominator = 1.0 + 2.0 * damping_factor * theta + theta * theta
return T2Parameters(
normalized_bandwidth=float(normalized_bandwidth),
damping_factor=float(damping_factor),
phase_coefficient=float(4.0 * damping_factor * theta / denominator),
frequency_coefficient=float(4.0 * theta * theta / denominator),
detector_clip_rad=DETECTOR_CLIP_RAD,
)
def _huber_phase_line(
block_values: np.ndarray,
block_times_seconds: np.ndarray,
) -> tuple[float, float, float, float]:
"""Повторить зафиксированную в P3 устойчивую прямую фазы."""
values = np.asarray(block_values, dtype=np.complex128)
times = np.asarray(block_times_seconds, dtype=np.float64)
if values.ndim != 1 or times.ndim != 1 or len(values) != len(times) or len(values) < 3:
raise ValueError("Недостаточно фазовых блоков")
phases = np.unwrap(np.angle(values))
base_weights = np.maximum(np.abs(values), 1e-12)
center_time = float(np.sum(base_weights * times) / np.sum(base_weights))
centered = times - center_time
design = np.column_stack((np.ones(len(times)), centered))
weights = base_weights.copy()
coefficients = np.zeros(2, dtype=np.float64)
for _iteration in range(12):
root = np.sqrt(weights / max(float(np.max(weights)), 1e-30))
updated, *_unused = np.linalg.lstsq(
design * root[:, None], phases * root, rcond=None
)
residual = phases - design @ updated
median = float(np.median(residual))
scale = 1.4826 * float(np.median(np.abs(residual - median))) + 1e-9
limit = 1.345 * scale
robust = np.ones_like(residual)
outside = np.abs(residual) > limit
robust[outside] = limit / np.abs(residual[outside])
weights = base_weights * robust
if np.allclose(updated, coefficients, rtol=0.0, atol=1e-12):
coefficients = updated
break
coefficients = updated
residual = phases - design @ coefficients
rmse = float(np.sqrt(np.sum(base_weights * residual**2) / np.sum(base_weights)))
phase_at_zero = float(coefficients[0] - coefficients[1] * center_time)
coherence = float(abs(np.sum(values)) / max(float(np.sum(abs(values))), 1e-30))
return phase_at_zero, float(coefficients[1]), rmse, coherence
def estimate_p3(
preamble_symbols: np.ndarray,
known_preamble_symbols: np.ndarray,
symbol_rate: float,
) -> P3CarrierEstimate:
"""Оценить P3 только по полной 1280-символьной преамбуле."""
received = np.asarray(preamble_symbols, dtype=np.complex128)
known = np.asarray(known_preamble_symbols, dtype=np.complex128)
if received.shape != (PREAMBLE_SYMBOLS,) or known.shape != received.shape:
raise ValueError("P3 требуется ровно 1280 принятых и известных символов")
if not np.isfinite(symbol_rate) or symbol_rate <= 0.0:
raise ValueError("Скорость символов должна быть положительной")
if not np.all(np.isfinite(received)) or not np.all(np.isfinite(known)):
raise ValueError("P3 не принимает NaN или Inf")
indexes = np.arange(PREAMBLE_SYMBOLS, dtype=np.int64)
block_count = PREAMBLE_SYMBOLS // P3_BLOCK_SYMBOLS
block_values = np.mean(
(received**2).reshape(block_count, P3_BLOCK_SYMBOLS), axis=1
)
block_times = np.mean(
((indexes + 0.5) / symbol_rate).reshape(block_count, P3_BLOCK_SYMBOLS),
axis=1,
)
_phase0, squared_slope, rmse, coherence = _huber_phase_line(
block_values, block_times
)
frequency_hz = float(squared_slope / (4.0 * math.pi))
times = (indexes + 0.5) / symbol_rate
gain = complex(np.mean(
received
* np.conj(known)
* np.exp(-2j * math.pi * frequency_hz * times)
))
if not np.isfinite(gain.real) or not np.isfinite(gain.imag) or abs(gain) == 0.0:
raise ValueError("P3 получил нулевой или неконечный коэффициент")
return P3CarrierEstimate(frequency_hz, gain, rmse, coherence)
def normalized_phase_detector(
corrected_symbol: complex,
detector_clip_rad: float = DETECTOR_CLIP_RAD,
) -> tuple[int, float]:
"""Вернуть собственное решение BPSK и ограниченную фазовую ошибку."""
value = complex(corrected_symbol)
if not np.isfinite(value.real) or not np.isfinite(value.imag):
raise ValueError("Фазовый детектор не принимает NaN или Inf")
hard = 1 if value.real >= 0.0 else -1
error = math.atan2(value.imag * hard, value.real * hard)
return hard, float(np.clip(error, -detector_clip_rad, detector_clip_rad))
def track_t2(
symbols: np.ndarray,
symbol_rate: float,
initial_estimate: P3CarrierEstimate,
*,
start_index: int = PREAMBLE_SYMBOLS,
parameters: T2Parameters | None = None,
) -> T2TrackingResult:
"""Последовательно применить T2; будущие символы не используются."""
values = np.asarray(symbols, dtype=np.complex128)
if values.ndim != 1 or not len(values) or not np.all(np.isfinite(values)):
raise ValueError("T2 требуется конечная одномерная последовательность")
if not np.isfinite(symbol_rate) or symbol_rate <= 0.0:
raise ValueError("Скорость символов должна быть положительной")
if not 0 <= start_index <= len(values):
raise ValueError("Некорректная граница начала сопровождения")
if abs(initial_estimate.gain) == 0.0:
raise ValueError("Начальный коэффициент P3 не может быть нулевым")
selected = t2_parameters() if parameters is None else parameters
corrected = np.empty(len(values), dtype=np.complex128)
decisions = np.empty(len(values), dtype=np.int8)
phases = np.empty(len(values), dtype=np.float64)
frequencies = np.empty(len(values), dtype=np.float64)
detector = np.empty(len(values), dtype=np.float64)
omega = 2.0 * math.pi * initial_estimate.frequency_hz / symbol_rate
phase0 = float(np.angle(initial_estimate.gain))
phase = phase0 + omega * (start_index + 0.5)
amplitude = abs(initial_estimate.gain)
for index, symbol in enumerate(values):
current_phase = phase0 + omega * (index + 0.5) if index < start_index else phase
value = symbol * np.exp(-1j * current_phase) / amplitude
hard, error = normalized_phase_detector(value, selected.detector_clip_rad)
corrected[index] = value
decisions[index] = hard
phases[index] = current_phase
frequencies[index] = omega * symbol_rate / (2.0 * math.pi)
detector[index] = error
if index >= start_index:
phase = phase + omega + selected.phase_coefficient * error
omega = omega + selected.frequency_coefficient * error
for array in (corrected, decisions, phases, frequencies, detector):
array.flags.writeable = False
return T2TrackingResult(
corrected=corrected,
hard_decisions=decisions,
phase_rad=phases,
frequency_hz=frequencies,
detector_error_rad=detector,
initial_estimate=initial_estimate,
parameters=selected,
)

View File

@@ -0,0 +1,130 @@
"""Быстрые проверки интеграции T2 в строгий приёмник Lab061."""
from __future__ import annotations
import numpy as np
import pytest
from experiments import lab045_hackrf_bpsk_fec as lab045
from experiments import lab049_bpsk_symbol_rate as lab049
from experiments import lab053_packet_processing as packet
from experiments import lab053_streaming_receiver as streaming
from experiments import lab059_preamble_carrier_limits as lab059
from experiments import lab060_decision_directed_carrier_tracking as lab060
from protocol import bpsk_carrier_tracking as tracking
def full_packet(payload: bytes = bytes(range(256))) -> np.ndarray:
plan = lab045.build_transmission_plan(payload)
mode = lab049.PhyMode(500_000)
raw = np.frombuffer(lab049.build_parameterized_tx_cs8(plan, mode), np.int8)
return raw[::2].astype(np.float32) + 1j * raw[1::2].astype(np.float32)
def receive(values: np.ndarray, mode: str):
receiver = streaming.StreamingReceiver(
processor=packet.PacketProcessor(carrier_tracking_mode=mode)
)
output = []
for start in range(0, len(values), receiver.block_samples):
output.extend(receiver.push_iq_block(values[start:start + receiver.block_samples]))
output.extend(receiver.finish())
return output, receiver
def impaired(cfo_hz: float, seed: int = 61001) -> np.ndarray:
clean = full_packet()
indexes = np.arange(len(clean))
signal = clean * np.exp(1j * (0.37 + 2 * np.pi * cfo_hz * indexes / 8_000_000))
random_generator = np.random.default_rng(seed)
noise = random_generator.normal(0, 2.0, len(clean)) + 1j * random_generator.normal(0, 2.0, len(clean))
return np.concatenate((np.zeros(20_000, np.complex64), signal + noise, np.zeros(20_000, np.complex64))).astype(np.complex64)
def test_t2_coefficients_and_frequency_units():
parameters = tracking.t2_parameters()
assert parameters.phase_coefficient == pytest.approx(0.0013324447407406753)
assert parameters.frequency_coefficient == pytest.approx(8.882964938271169e-7)
def test_p3_and_t2_are_numerically_equivalent_to_lab060():
symbols, expected, metadata = lab060.clean_rrc_symbols(500_000)
p3 = lab059.estimate_from_burst(symbols, 500_000, "P3")
production = tracking.P3CarrierEstimate(
p3.frequency_hz, p3.gain, p3.phase_fit_rmse_rad, p3.mean_block_coherence
)
actual = tracking.track_t2(symbols, 500_000, production)
reference = lab060.track_symbols(symbols, 500_000, p3.frequency_hz, p3.gain, "T2", lab060.loop_parameters("T2", 0.0005))
assert metadata["symbols"] == len(symbols)
assert np.allclose(actual.corrected, reference.corrected, rtol=0, atol=3e-15)
assert np.array_equal(actual.hard_decisions, reference.hard_decisions)
def test_default_is_explicit_legacy_and_invalid_mode_is_rejected():
assert packet.PacketProcessor().carrier_tracking_mode == "legacy"
with pytest.raises(ValueError):
packet.PacketProcessor(carrier_tracking_mode="automatic")
@pytest.mark.parametrize("mode", ["legacy", "t2"])
def test_clean_full_500k_packet_passes_header_fec_crc(mode):
output, receiver = receive(impaired(0.0), mode)
assert len(output) == receiver.packet_count == 1
assert output[0].payload == bytes(range(256))
@pytest.mark.parametrize("cfo_hz", [-10.0, -2.0, 2.0, 10.0])
def test_t2_full_packet_with_positive_and_negative_residual_frequency(cfo_hz):
output, receiver = receive(impaired(cfo_hz), "t2")
assert len(output) == receiver.packet_count == 1
def test_t2_is_causal_and_payload_truth_is_not_an_input():
symbols, expected, unused = lab060.clean_rrc_symbols(500_000)
p3 = tracking.estimate_p3(symbols[:1280], expected[:1280], 500_000)
first = tracking.track_t2(symbols, 500_000, p3)
changed = symbols.copy()
changed[-1] *= -1
second = tracking.track_t2(changed, 500_000, p3)
assert np.array_equal(first.hard_decisions[:-1], second.hard_decisions[:-1])
assert np.array_equal(first.phase_rad[:-1], second.phase_rad[:-1])
def test_p3_frontend_does_not_read_raw_header_or_payload():
processor = packet.PacketProcessor(carrier_tracking_mode="t2")
original = full_packet().astype(np.complex128)
changed = original.copy()
boundary = 16 * processor.samples_per_symbol + processor.taps.size + 1279 * processor.samples_per_symbol
changed[boundary:] += 10.0 + 7.0j
def p3(values):
matched = processor.filter(processor.shift(values))
nominal = 16 * processor.samples_per_symbol + processor.taps.size - 1
received = matched[nominal:nominal + 1280 * processor.samples_per_symbol:processor.samples_per_symbol]
return tracking.estimate_p3(received, processor.expected, processor.symbol_rate)
first, second = p3(original), p3(changed)
assert first.frequency_hz == pytest.approx(second.frequency_hz, abs=1e-8)
assert first.gain == pytest.approx(second.gain, abs=1e-8)
def test_error_bursts_fade_and_noise_remain_finite():
symbols, expected, unused = lab060.clean_rrc_symbols(500_000)
p3 = tracking.estimate_p3(symbols[:1280], expected[:1280], 500_000)
damaged = symbols.astype(np.complex128)
damaged[2000] *= -1
damaged[2600:2610] *= -1
damaged[3200:3280] *= -1
damaged[4000:4200] *= 0.02
random_generator = np.random.default_rng(61002)
damaged[4500:4700] = random_generator.normal(size=200) + 1j * random_generator.normal(size=200)
result = tracking.track_t2(damaged, 500_000, p3)
assert np.all(np.isfinite(result.corrected))
assert np.all(np.isfinite(result.frequency_hz))
assert np.max(np.abs(result.frequency_hz)) < 100.0
def test_background_produces_no_header_or_packet_and_seed_is_deterministic():
one = np.random.default_rng(61003).normal(size=180_000).astype(np.float32).astype(np.complex64)
two = np.random.default_rng(61003).normal(size=180_000).astype(np.float32).astype(np.complex64)
assert np.array_equal(one, two)
output, receiver = receive(one, "t2")
assert output == [] and receiver.packet_count == 0