Confiança no recetor: avaliar um registo de um recetor GNSS real
kshana receiver-trust lê um registo de um recetor real de sistema global de navegação por satélite (GNSS), executa monitores de confiança sobre ele época a época, e indica quando e porquê a posição do recetor deixou de ser fiável. Pode também pontuar o registo face a eventos e previsões que indique antes da execução: se cada evento foi detetado a tempo, e se uma queda modelada da densidade de portadora-ruído (C/N0) concorda com o que o recetor mediu.
As observáveis são as do próprio recetor; os monitores e os seus limiares são os do motor e os seus. Cada resultado traz essa etiqueta, o SHA-256 do registo e todos os limiares com que foi executado, de modo que uma execução possa ser repetida e auditada.
Para uma embarcação em andamento, com uma pontuação em direto e uma barreira opcional à frente de um sistema de cartas, consulte MARITIME-TRUST.md.
Execute-o#
kshana receiver-trust examples/receiver-trust/session.toml
Isto escreve session.result.json, session.trust.csv (uma linha por época) e session.trust.svg (C/N0 médio ao longo do tempo com o estado de confiança por baixo) junto ao cenário, e imprime um veredicto de uma linha. O registo de exemplo é sintético, feito para mostrar o formato; aponte path para o registo do seu próprio recetor.
A figura acima é a demonstração de embarcação de MARITIME-TRUST.md: um registo inventado, uma pontuação consultiva.
O mesmo cenário executa-se a partir de Python (kshana.receiver_trust(toml)), a partir do pacote WebAssembly (receiver_trust(toml), com o registo em linha) e a partir da ferramenta assess_receiver_log do servidor Model Context Protocol (MCP).
Registos que lê#
format |
Fonte | O que é lido |
|---|---|---|
ubx |
binário u-blox | NAV-SAT C/N0 por satélite, elevação e azimute, posição NAV-PVT, controlo automático de ganho (AGC) MON-RF e o indicador de jamming jamInd, semana NAV-TIMEGPS, e os indicadores do próprio recetor: NAV-STATUS spoofDetState e SEC-SIG (self_report, ver RECEIVER-PROTOCOLS.md) |
rinex |
ficheiro de observação RINEX 3 | códigos S (C/N0) por satélite e sinal; com um ficheiro de navegação de difusão (nav), as pseudodistâncias para a posição calculada pelo próprio motor |
android |
CSV do GnssLogger do Android | linhas Raw: C/N0 e AgcDb por satélite; linhas Fix: posição |
nmea |
texto NMEA 0183 | relação sinal-ruído GSV por satélite (dB-Hz, com ou sem o identificador de sinal 4,10) e a sua elevação e azimute, diluição da precisão e satélites usados GSA, hora e posição GGA e RMC |
sbf |
binário Septentrio (0,38, apenas leitura) | posição PVTGeodetic, C/N0 MeasEpoch por satélite e sinal, contagem de bandas RFStatus; ver RECEIVER-PROTOCOLS.md |
rtklib-pos |
ficheiro de solução RTKLIB (.pos) |
hora, posição (latitude/longitude ou ECEF, convertida) e satélites usados por época; os ficheiros em graus-minutos-segundos são recusados com uma mensagem |
novatel |
registos NovAtel OEM7 ASCII ou binários | posição BESTPOS (apenas solução calculada), C/No RANGE por satélite e sinal; os indicadores de emissor de jamming RXSTATUS tornam-se o self_report da época (estado de jamming 2 quando o recetor assinala um emissor de jamming, 1 quando não; não existe indicador de spoofing no RXSTATUS), confrontados com os alarmes do Kshana pela comparação receiver-self-report |
gnss-sdr |
Fluxos GNSS-SDR Monitor e PVT-monitor como linhas JSON | C/N0 por canal, posição calculada, satélites usados, HDOP, azimute e elevação; produzido por integrations/gnss-sdr/relay.py |
Pormenores, fontes e limites destes três: MACHINE-INPUTS.md.
Cada época que os leitores produzem traz também source_span, desvios em bytes [start, end) na entrada: o menor intervalo contíguo que contém os registos que contribuíram para ela, de modo que a fatia bruta exata possa ser calculada em hash para uma janela de evento. NMEA: todas as linhas lidas enquanto a época está atual (um superconjunto quando uma linha não relacionada se encontra entre as suas frases). UBX: as tramas da época. RINEX 3: os registos da época desde cada linha > até à seguinte. Android: as linhas Raw da época (uma linha Fix apenas para uma época sem linhas Raw). Ausente para um fluxo em direto e sempre que não está bem definido (RINEX 2).
O ficheiro entra por path (resolvido relativamente à pasta do próprio cenário), ou em linha como text ou base64 (as únicas opções num navegador).
Um registo IMU como segundo sensor#
[log.imu] acrescenta um registo inercial no mesmo relógio:
[log.imu]
format = "csv" # or "euroc"
path = "imu.csv" # or text / base64
heading0_deg = 90.0 # heading at the first IMU sample
yaw_axis_down = true # body z down (NED/FRD); false for z up (ENU/FLU)
time_offset_s = 0.0 # added to the IMU clock to reach the log's t_s
A velocidade de guinada é integrada a partir de heading0_deg e preenche o rumo sempre que o registo do recetor não o contém, pelo que o monitor heading-course (que precisa de heading_sensor = true na plataforma) compara o rumo do giroscópio com o rumo sobre o fundo. Um giroscópio deriva: o rumo é tão bom quanto a janela de integração, e hdg_cog_tol_deg decide o que conta como discordância. O formato csv está documentado em MACHINE-INPUTS.md. Os bytes do IMU fazem parte do hash do cenário e do hash do cenário de um pacote de evidências. Em linha (text/base64) funciona em todas as superfícies; um path é recusado onde os ficheiros não são lidos.
Plataforma: estática ou embarcação#
Os monitores acima assumem uma antena fixa. Um cenário declara a plataforma antes da execução numa tabela [platform] de nível superior; sem ela, a plataforma é static e nada muda.
[platform]
kind = "vessel" # "static" (default) or "vessel"
max_speed_kn = 30.0 # default 30
max_accel_mps2 = 0.5 # default 0.5
max_turn_rate_dps = 6.0 # default 6
antenna_height_m = 18.0 # no default: without it the sea-level monitor does not run
heading_sensor = false # true: the stream carries a gyro heading (HDT or THS)
| Chave | Predefinição | Porque esse valor |
|---|---|---|
max_speed_kn |
30 | Acima da velocidade de serviço dos cascos mercantes e da maioria das embarcações de passageiros, pelo que uma embarcação real nunca a atinge; muito abaixo da velocidade que um arrastamento de posição implica. Uma embarcação mais lenta deve declarar um limite mais baixo: quanto mais baixo o limite, mais cedo o arrastamento é detetado. |
max_accel_mps2 |
0.5 | Um navio grande altera a velocidade bem abaixo de 0,1 m/s²; uma embarcação pequena e rápida atinge algumas décimas numa mudança brusca de aceleração. |
max_turn_rate_dps |
6 | Um navio grande vira bem abaixo de 1 deg/s e uma embarcação pequena numa curva fechada a poucos. |
antenna_height_m |
none | Uma propriedade da instalação; um palpite seria um limiar oculto. |
heading_sensor |
false | Declarado true e ausente do registo é um erro, de modo que um sensor em falta não possa passar por uma verificação limpa. |
As chaves de embarcação são rejeitadas em kind = "static". Para uma embarcação, o monitor position-jump não é executado, porque mede a distância à média de calibração, o que é errado numa antena em movimento; o monitor_config do resultado regista a plataforma e os limites usados.
Os fluxos NMEA sintéticos de kshana nmea-scenario, feitos para formação de tripulações, são aceites pelo leitor NMEA e podem ser executados por este tipo de cenário como qualquer registo NMEA.
Monitores#
Cada monitor precisa dos seus dados; um monitor cujos dados não estão no registo não é executado e não consta de monitors_run. Os primeiros calibration_s segundos (predefinição 60) definem a linha de base e nunca dão alarme.
| Monitor | Dá alarme quando | Precisa de |
|---|---|---|
cn0-drop |
o C/N0 médio dos satélites comuns com a linha de base desce cn0_drop_db ou mais (predefinição 6 dB) |
C/N0 |
loss-of-lock |
os satélites seguidos ficam sats_lost ou mais abaixo da mediana da linha de base (predefinição 4) |
C/N0 |
agc |
a leitura do AGC afasta-se da sua linha de base mais de agc_k_sigma desvios-padrão (predefinição 5) |
UBX MON-RF ou AgcDb do Android |
jam-ind |
o jamInd do u-blox excede jam_ind_threshold (predefinição 80 de 255) |
UBX MON-RF |
position-jump |
a posição calculada pelo próprio recetor está a mais de position_jump_m da sua média de calibração (predefinição 50 m) |
uma posição comunicada |
raim |
a monitorização autónoma da integridade pelo recetor (RAIM) por paridade, sobre a solução de ponto único do próprio motor, rejeita o conjunto de medições | registo RINEX com nav |
solve-failure |
a solução de ponto único do motor falha com cinco ou mais satélites | registo RINEX com nav |
clock |
o relógio do recetor afasta-se da sua reta prevista mais do que o desvio mínimo detetável do seu ruído de calibração | registo RINEX com nav |
Perfil calibrado (opcional). [monitors] profile = "jammertest-2024-calibrated" substitui os três limiares de interferência por valores calibrados no conjunto de dados público JammerTest 2024 (um u-blox ZED-F9P por cenário; Zenodo 15911589): cn0_drop_db = 1.5, agc_k_sigma = 3.5, jam_ind_threshold = 29. A calibração foi pré-registada (xval/jammertest-trust/PREREGISTRATION.md) e o perfil é MODELLED, ajustado numa única campanha pública: o seu limiar cn0_drop_db não atingiu o limiar de falsos alarmes fixado antecipadamente sobre o conjunto reservado (0,0231 contra 0,02); os limiares do AGC e do indicador de jamming e o limiar da união (falsos alarmes 0,033, deteção 0,81) foram satisfeitos (xval/jammertest-trust/RESULTS.md). Vale para esse tipo de recetor nesse evento, face a janelas de ataque programadas, e foi calibrado nos registos CSV do conjunto de dados (os seus registos UBX dão mais falsos alarmes em duas pastas). Um resultado executado com o perfil repete os limiares que aplicou e traz uma profile_note que diz tudo isto. Sem o perfil aplicam-se as predefinições acima e todas as saídas ficam inalteradas.
Para uma plataforma vessel, os monitores de plataforma em movimento também são executados (limiares numa tabela [maritime] de nível superior, cada predefinição documentada em src/receiver_trust/maritime.rs); cada um precisa dos seus dados e só consta de monitors_run quando chegou a decidir:
| Monitor | Dá alarme quando | Precisa de |
|---|---|---|
kinematic |
a variação de posição discorda da velocidade e do rumo comunicados, ou implica uma velocidade, aceleração ou taxa de viragem acima dos limites da embarcação | posições calculadas; velocidade e rumo (RMC ou VTG) para a parte de navegação à estima |
heading-course |
o rumo do giroscópio e o rumo sobre o fundo diferem mais de hdg_cog_tol_deg (velocidade acima de min_speed_kn) |
rumo HDT, THS ou VHW, heading_sensor = true |
speed-log |
a velocidade na água e a velocidade sobre o fundo diferem mais de stw_sog_tol_kn |
VHW ou VBW, e velocidade sobre o fundo |
sea-level |
a altitude GGA difere de antenna_height_m mais de sea_level_tol_m |
altitude GGA, antenna_height_m |
cn0-spread |
a dispersão do C/N0 entre satélites desce para cn0_spread_frac da sua mediana de calibração |
GSV |
cn0-rise |
o C/N0 dos satélites comuns subiu cn0_rise_db em média e cn0_rise_frac deles subiram |
GSV |
time-consistency |
as horas de épocas consecutivas avançam de forma irregular, recuam ou abandonam o relógio do anfitrião | hora NMEA; relógio do anfitrião para a última parte |
osnma |
o recetor comunica uma falha de autenticação OSNMA (é lido um estado comunicado; nada é verificado; para verificar o OSNMA a partir das próprias páginas I/NAV, consulte docs/OSNMA.md) | frase de estado $PKSOS |
Uma época é não fiável quando o RAIM, o monitor do relógio, uma falha de resolução ou um salto de posição dá alarme, ou quando a queda de C/N0 atinge o dobro do seu limiar; degradada com qualquer outro alarme; e nominal nos restantes casos.
O caminho do motor (raim, solve-failure, clock) é o mesmo cálculo que a referência independente de spoofing do JammerTest 2024 em tests/jammertest_spoof_oracle_support: tests/receiver_trust_jammertest.rs verifica que, nos oito inícios publicados, o primeiro alarme ocorre na mesma época e é do mesmo tipo.
Pontuação de confiança (plataforma embarcação)#
Numa embarcação, cada época após a calibração recebe também uma pontuação de 0 a 100 e as razões pelas quais não é 100. O mapeamento é determinístico e fixado antes de qualquer registo ser pontuado: sem aprendizagem, nada ajustado a eventos. Cada monitor reduz a sua evidência a uma razão statistic / threshold (um alarme em 1 ou mais); uma razão custa à pontuação
points = weight * clamp((ratio - onset_ratio) / (full_ratio - onset_ratio), 0, 1)
score = round_to_0.1( clamp(100 - sum of points, 0, 100) )
de modo que um monitor não custa nada enquanto estiver abaixo de onset_ratio do seu limiar (predefinição 0,5), custa metade do seu peso no seu limiar, e o peso todo a full_ratio vezes o limiar (predefinição 1,5); a pontuação é não crescente em cada estatística. Uma fonte que não comunica numa época mantém a sua última estatística durante evidence_hold_s (predefinição 10 s: dois ciclos de uma frase de C/N0 enviada a cada 5 s), tanto como alarme como como dedução.
O arredondamento é por excesso a partir de meio (floor(x * 10 + 0.5) / 10), a banda é a da pontuação arredondada, e o limite é aplicado antes do arredondamento. Só os monitores cujos pontos são superiores a zero constam das deduções, com a sua razão e pontos não arredondados, primeiro os de mais pontos; as deduções com pontos iguais constam pela ordem dos nomes dos monitores cn0-drop, agc, jam-ind, loss-of-lock, position-jump, raim, clock, solve-failure, kinematic, heading-course, speed-log, sea-level, cn0-spread, cn0-rise, time-consistency, osnma, geometry. A um monitor cujo teste é passa ou falha (uma resolução falhada, uma autenticação falhada) é atribuída a razão 1,5, o peso total. Os pontos indicados são os anteriores ao limite em 0.
[score]
nominal_min = 90.0 # score >= 90: nominal
degraded_min = 55.0 # 55 <= score < 90: degraded; below 55: untrusted
onset_ratio = 0.5
full_ratio = 1.5
evidence_hold_s = 10.0
[score.weights] # overrides; a monitor not listed keeps its default
sea-level = 30.0
| Peso (pontos) | Monitores | Porquê |
|---|---|---|
| 70 | osnma |
uma falha de autenticação é uma afirmação, não uma estatística |
| 60 | kinematic, raim, clock, position-jump, time-consistency |
uma posição falsificada tem de contradizer a física ou as verificações do próprio recetor, e o tempo não pode andar para trás; uma violação em força total (1,5 vezes o seu limiar: 60 pontos, pontuação 40) é não fiável, e no seu limiar (30 pontos, pontuação 70) é degradada |
| 40 | heading-course, speed-log, solve-failure |
um sensor independente em desacordo é degradado; dois em força total são não fiáveis |
| 30 | sea-level, cn0-spread, cn0-rise, cn0-drop |
o ambiente de sinal, ou uma verificação pouco informativa |
| 25 | loss-of-lock, agc, jam-ind |
o ambiente é hostil; só isso não mostra que a posição está errada |
até 15, [geometry] |
geometry (0.36) |
não é um peso nesta rampa: nada, a menos que o céu seja fraco, e depois até cap_points (15); a geometria sozinha atinge o estado degradado, nunca o não fiável, e não dá alarme (ver "Geometria" abaixo) |
Os limites das bandas decorrem dos pesos: o monitor mais leve que dá alarme custa 12,5 pontos no seu limiar, pelo que qualquer alarme faz sair da banda nominal; nenhum monitor isolado com menos de 60 pontos pode atingir sozinho a banda não fiável (vários em conjunto podem). O resultado regista o modelo de pontuação (score_model) e cada época traz score.deductions: que monitores deduziram, as suas razões e os seus pontos. Numa embarcação, o estado da época é a banda da sua pontuação.
Geometria: degradação natural distinta da inconsistência semelhante a um ataque#
Um recetor junto a uma grua de cais, numa eclusa ou sob um casco alto perde satélites e a sua posição calculada piora por uma razão que não é um ataque. A partir de 0,36, segundo a nota de projeto de geometria (docs/design/2026-10-12-geometry-and-skyline-design.md, secção 4), cada época traz a sua geometria de satélites, a pontuação de uma embarcação tem um monitor de geometria, e cada dedução traz uma classe: geometry (o céu), environment (o ambiente de sinal) ou inconsistency (evidência semelhante a um ataque).
De onde vem a geometria. A elevação e o azimute de cada satélite seguido (NMEA GSV, UBX NAV-SAT), a partir dos quais a diluição da precisão (DOP) é derivada com o único núcleo de DOP do motor (orbit::dop, VALIDATED face a gnss_lib_py): source = derived. Na falta disso, o DOP e os satélites usados que o recetor indica (NMEA GSA): source = reported. As posições no céu e o GSA contam durante o evidence_hold_s da pontuação (predefinição 10 s), porque muitos barramentos enviam GSV de poucos em poucos segundos; um conjunto GSV incompleto numa época (o fim de um excerto, uma frase perdida) não é usado. Um registo sem nenhum dos dois, como NMEA só com GGA e RMC, ou os registos Android e RINEX atualmente, recebe o estado unknown com a nota "geometry unknown": o motor não adivinha uma geometria que não lhe foi dada, e o monitor de geometria não é executado.
geometry.status |
Significado |
|---|---|
good |
satélites suficientes e DOP dentro dos limiares |
degraded |
uma ou mais razões: few-satellites, high-pdop, high-hdop, e (informativo, não incluído na pontuação) sky-blocked quando todos os satélites seguidos estão numa metade do horizonte |
unknown |
o registo não dá geometria de satélites para esta época |
O estado é descritivo e o que conta para a pontuação é a etiqueta (abaixo): degraded inclui o caso informativo sky-blocked, que pode surgir com DOP bom ou razoável, e só uma etiqueta poor é pontuada.
A etiqueta (sempre comunicada, nunca pontuada). O geometry.label de cada época é good (HDOP no máximo hdop_good 2, PDOP no máximo pdop_good 3 e pelo menos used_good 6 satélites), poor (HDOP acima de hdop_max 4, PDOP acima de pdop_max 6 ou menos de min_used 5 satélites), fair entre os dois, ou unknown. A nota da época abre sempre com a etiqueta e os números, por exemplo "geometry fair: HDOP 1,105, PDOP 1,831, VDOP 1,410, 8 satellites; not scored (the score deducts only above HDOP 4, PDOP 6 or below 5 satellites)", e acrescenta ", signal loss" quando esta se verifica.
O monitor de geometria (pontuação da embarcação). Não deduz nada a menos que a geometria seja fraca: a sua razão é max(hdop / hdop_max, pdop / pdop_max, min_used / n_used), e enquanto esta for no máximo 1 (geometria boa ou razoável, incluindo um céu aberto só com GPS de 8 satélites) não custa nada, pelo que um registo assim é pontuado exatamente como em 0,35. Acima de 1 a dedução cresce linearmente até cap_points (15) em full_ratio (1,5 vezes os limiares: HDOP 6, PDOP 9, 3 satélites): 7,5 pontos na razão 1,25 (HDOP 5, ou 4 satélites), 15 a partir de 1,5 (pontuação 85, degradada). Por si só, nunca pode levar a pontuação abaixo de floor (predefinição: o degraded_min da pontuação, 55), pelo que a geometria sozinha atinge no máximo a banda degradada e nunca a não fiável: o limite em vigor é min(cap_points, 100 - floor), pelo que uma tabela [score] que aumente degraded_min (uma de 0,35 com degraded_min 90, por exemplo) reduz o limite para 100 - degraded_min em vez de ser recusada, e um floor definido abaixo de degraded_min é recusado. O monitor não dá alarme; consta em último lugar de monitors_run quando decidiu em alguma época (uma embarcação cujo registo dá a geometria), e não é contado na identidade com 0,35; não é uma entrada de [score.weights] (defini-la é recusado), e o limite e o mínimo em vigor ficam registados em geometry_model.cap_in_force e geometry_model.floor_in_force. A nota de uma época fraca diz porquê, por exemplo "geometry poor: HDOP 5,200, PDOP 7,900, VDOP 4,100, 7 satellites; degraded by geometry: HDOP 5,200 above 4 (derived from 7 logged satellites); not an inconsistency".
Classes. Cada dedução (score.deductions[].class):
| Classe | Monitores |
|---|---|
geometry |
geometry (geometria fraca); loss-of-lock quando todos os satélites perdidos face à linha de base de calibração estavam abaixo de low_elev_deg no seu último comunicado |
environment |
cn0-drop, cn0-spread, cn0-rise, agc, jam-ind, loss-of-lock nos restantes casos; geometry durante perda de sinal (abaixo) |
inconsistency |
kinematic, heading-course, speed-log, sea-level, time-consistency, clock, raim, position-jump, osnma, solve-failure; e geometry quando a geometria é fraca e o HDOP indicado discorda do céu indicado (abaixo) |
A proteção contra um céu fraco forjado. Um spoofer poderia enviar um GSV escasso, ou comunicar um HDOP elevado, para obter tolerância. O motor etiqueta e não perdoa: nenhuma outra verificação é alargada por uma geometria fraca, e o HDOP que o recetor indica (GSA dentro do período de retenção, senão GGA) é comparado com o HDOP derivado do céu que ele indica. Quando diferem mais de max(reported_vs_derived_tol, reported_vs_derived_rel × derived HDOP) (1,0, ou 25 % do HDOP derivado, o que for maior), a época traz reported_derived_mismatch: true e, quando a geometria é fraca, a sua dedução é classificada como inconsistency. Com geometria boa ou razoável, a discordância é comunicada e não pontuada: nada é deduzido a menos que a geometria seja fraca. Esta é uma escolha deliberada (0,36): "zero, a menos que seja fraca" vale para todas as épocas, pelo que a geometria boa e razoável é pontuada exatamente como em 0,35, e um HDOP forjado nunca passa em silêncio. O indicador (reported_derived_mismatch) está em todas as épocas do resultado e em todas as linhas em direto, a nota diz "…beyond the tolerance: reported, not scored (the geometry is not poor)" ou "…: the geometry deduction is classed an inconsistency", geometry_summary.hdop_mismatch conta as épocas, e a página de resumo das evidências mostra a contagem, incluindo zero. Os 25 % são uma escolha de engenharia REPRESENTATIVE, não um valor medido nem normalizado: um recetor que resolve um relógio por constelação, ou com um subconjunto dos satélites que segue (uma máscara de elevação, um corte de C/N0, uma exclusão RAIM), indica um HDOP que difere do HDOP de relógio único de todos os satélites seguidos numa medida que cresce com o HDOP, pelo que uma tolerância fixa sozinha leria uma geometria fraca natural como uma inconsistência. Uma diferença maior continua a ser lida como tal. Uma dedução classificada como geometry é uma afirmação sobre o céu, não uma conclusão de que não ocorreu nenhum ataque.
Perda de sinal não é o céu. Quando satélites que o recetor lista à vista (acima de low_elev_deg) não são seguidos, o motor deriva também o DOP de todos os satélites à vista. Se esse DOP à vista estiver dentro dos limiares e ou nada for seguido ou os satélites não seguidos estiverem espalhados pelo horizonte (não confinados a uma metade dele), o céu estava lá e o sinal não: a época traz signal_loss: true, a nota "satellites in view but not tracked: signal environment, not geometry", e a dedução de geometria é classificada como environment. Os satélites não seguidos confinados a uma metade do horizonte continuam a ser lidos como um céu bloqueado (uma parede, uma falésia, um cais). Uma obstrução bilateral (um desfiladeiro ou um fiorde estreito) é lida como perda de sinal até ser configurada uma linha de horizonte. Os valores sentinela de ausência de posição do recetor não são DOP: um HDOP ou PDOP indicado de 99 ou mais, qualidade GGA 0 e modo GSA 1 não dão DOP indicado.
chave [geometry] |
Predefinição | Proveniência |
|---|---|---|
hdop_max |
4 | escolhido (nota de projeto 4,2): com sigma_uere_m 1,5 m, um HDOP 4 é um 2drms de 12 m, o kin_pos_tol_m da verificação cinemática. A IEC 61108-1 é habitualmente citada como indicando os seus requisitos de exatidão para HDOP ≤ 4 (ou PDOP ≤ 6): recordado a partir da literatura secundária sobre recetores GNSS, sem consulta de nenhuma fonte secundária específica e sem leitura da norma; a verificar face ao texto da norma |
pdop_max |
6 | o limiar de disponibilidade de PDOP de scenarios/polar-arctic-leo-coverage.toml; GPS SPS Performance Standard, 5.ª ed. (2020), PDOP ≤ 6, como citado em tests/constellation_availability_published_spec_oracle.rs; a verificar face ao texto da norma |
min_used |
5 | mais um do que uma posição calculada precisa, para que uma única perda não termine a posição calculada; o mínimo com uma medição redundante para o RAIM |
sigma_uere_m |
1.5 | uma entrada declarada para a ligação 2drms de hdop_max; defina o valor do próprio recetor |
reported_vs_derived_tol |
1,0 HDOP | escolhido (nota de projeto 4,2): a parte absoluta da tolerância; uma discordância para além da tolerância não é geometria |
reported_vs_derived_rel |
0.25 | escolha de engenharia REPRESENTATIVE: a parte relativa, para recetores com relógios por constelação ou soluções por subconjunto |
low_elev_deg |
15 | nota de projeto 4,3: os satélites perdidos abaixo dele perdem-se para o céu; uma linha de horizonte (item 1 de 0,36) alargará isto |
azimuth_gap_deg |
180 | geométrico, informativo: todos os satélites numa metade do horizonte |
cap_points |
15 | nota de projeto 4,2: a geometria sozinha move a pontuação de nominal para degradada e nunca para não fiável; deduzido apenas quando a geometria é fraca (decisão do fundador B1, 0,36) |
full_ratio |
1.5 | o full_ratio próprio da pontuação: a rampa atinge cap_points a 1,5 vezes os limiares de geometria fraca |
floor |
o degraded_min da pontuação (55) |
a geometria sozinha nunca atinge a banda não fiável; o limite em vigor é min(cap_points, 100 - floor); um mínimo abaixo de degraded_min é recusado |
hdop_good |
2 | REPRESENTATIVE, só para a etiqueta: metade de hdop_max (a razão de início da pontuação de 0,35) |
pdop_good |
3 | REPRESENTATIVE, só para a etiqueta: metade de pdop_max |
used_good |
6 | REPRESENTATIVE, só para a etiqueta: mais um do que min_used, pelo que uma única perda ainda deixa uma medição redundante |
O que mudou nas saídas, e o que não mudou. Nenhuma pontuação muda, a menos que a geometria seja fraca; a etiqueta é sempre comunicada. Chaves acrescentadas: epochs[].geometry (status, reasons, source, n_used, hdop, pdop, vdop arredondado a 0,001, open_fraction (nulo até ser configurada uma linha de horizonte), max_azimuth_gap_deg, age_s, reported_hdop, reported_derived_mismatch, note, in_view (satélites listados acima de low_elev_deg), signal_loss, label), class em cada dedução, e no fim geometry_model (limiares, período de retenção, o limite e o mínimo em vigor, proveniência, regras de classe) e geometry_summary (épocas por estado, por etiqueta e por classe de dedução, e as épocas com uma discordância do HDOP indicado); score_model.weights é como em 0,35. Dois testes mantêm isto ao byte face à linha de base "0,36 portable" depois de removidas essas chaves (os hashes de 0.35.0, rebaseados quando o motor passou para matemática portátil e mantidos nos testes como registo do que a 0.35.0 publicou: docs/revisions/STUDIO15-portable-math-rebaseline.md). tests/geometry_trust_no_change.rs abrange todas as saídas de ambas as sessões de exemplo (JSON de resultado, CSV, SVG, veredicto, linhas em direto reproduzidas, $PKSHT, Prometheus, CEF, LEEF). tests/geometry_corpus_identity.rs abrange o resultado em lote, o resumo, as linhas em direto e o $PKSHT de cada corpus NMEA de embarcação submetido (a demonstração e os ficheiros de teste de referência marítimos, cada um também como céu aberto só com GPS com 8 satélites, e os cenários de formação de tripulações gerados a partir das suas sementes); nos dois cenários de formação em que o jamming torna a geometria fraca, todas as épocas que não são fracas são idênticas e todas as fracas diferem apenas pela dedução de geometria. Um registo cuja geometria é fraca perde até 15 pontos que não perdia em 0,35; essa é a alteração pretendida. Na demonstração, o arrastamento lê-se como inconsistency; tests/geometry_trust.rs edita o registo para mascarar o céu ocidental (razoável: etiquetado, não pontuado), para o mascarar e a dois satélites orientais (fraca: deduções de geometria, perda de bloqueio environment porque os satélites altos também desapareceram), para perder apenas satélites abaixo de 15° (perda de bloqueio geometry), e para indicar um HDOP que o céu não explica (a dedução de geometria fraca passa a inconsistency). Os limiares são MODELLED: escolhidos, não ajustados a nenhum registo; nada aqui afirma com que frequência a degradação natural e os ataques são distinguidos em dados reais.
Declarar eventos e previsões#
[[events]]
label = "jammer on"
kind = "jamming" # jamming, spoofing or other
onset_s = 120.0 # seconds since the first epoch of the log
end_s = 240.0 # optional; otherwise onset_s + compare.horizon_s
predicted_cn0_drop_db = 12.0 # optional, e.g. from a `jamming` scenario's link budget
[compare]
detect_tol_s = 10.0 # an alarm this soon after onset counts as a detection
cn0_tol_db = 3.0 # predicted and observed drops agree within this
Para cada evento, o resultado dá os falsos alarmes entre a calibração e o início, o primeiro alarme e os seus monitores, a latência, o desfecho (detected, late, missed ou not-evaluable), e a queda mediana de C/N0 observada face à previsão (agree, disagree, no-prediction ou not-evaluable). A discordância é comunicada, nunca ajustada para desaparecer: as tolerâncias estão no cenário, e o cenário está no hash do resultado.
O que isto não afirma#
Os monitores de confiança são MODELLED: os seus limiares são entradas, e o seu desempenho de deteção em interferência real é o que a pontuação de eventos mede, não algo que este tipo de cenário afirme. Nas sessões de spoofing do JammerTest 2024, os monitores do caminho do motor apanham alguns inícios em 10 s e chegam tarde noutros; consulte a linha "Clock-aided χ², RAIM, AGC, SQM fused per-epoch security FoM" em VERIFICATION-MATRIX.md para os números. Uma previsão que concorda com um registo é um ponto de dados, não uma validação.