Confianza del receptor: evaluación de un registro real de receptor GNSS
kshana receiver-trust lee un registro de un receptor real del sistema global de navegación por satélite (GNSS), ejecuta monitores de confianza sobre él época a época e indica cuándo y por qué la solución del receptor dejó de ser fiable. También puede puntuar el registro frente a eventos y predicciones que usted declare antes de la ejecución: si cada evento se detectó a tiempo y si una caída modelada de la relación portadora/densidad de ruido (C/N0) concuerda con lo que midió el receptor.
Los observables son los del propio receptor; los monitores y sus umbrales son los del motor y los suyos. Cada resultado lleva esa etiqueta, el SHA-256 del registro y todos los umbrales con los que se ejecutó, de modo que una ejecución puede repetirse y auditarse.
Para una embarcación en navegación, con una puntuación en vivo y una puerta opcional delante de un sistema de cartas, véase MARITIME-TRUST.md.
Ejecútelo#
kshana receiver-trust examples/receiver-trust/session.toml
Esto escribe session.result.json, session.trust.csv (una fila por época) y session.trust.svg (C/N0 medio a lo largo del tiempo con el estado de confianza debajo) junto al escenario, e imprime un veredicto de una línea. El registro de ejemplo es sintético, hecho para mostrar el formato; apunte path al registro de su propio receptor.
La figura anterior es la demostración de embarcación de MARITIME-TRUST.md: un registro inventado, una puntuación orientativa.
El mismo escenario se ejecuta desde Python (kshana.receiver_trust(toml)), desde el paquete WebAssembly (receiver_trust(toml), con el registro en línea) y desde la herramienta assess_receiver_log del servidor del Model Context Protocol (MCP).
Registros que lee#
format |
Fuente | Qué se lee |
|---|---|---|
ubx |
Binario de u-blox | NAV-SAT C/N0, elevación y acimut por satélite, posición de NAV-PVT, control automático de ganancia (AGC) de MON-RF y el indicador de jamming jamInd, semana de NAV-TIMEGPS y los indicadores propios del receptor: NAV-STATUS spoofDetState y SEC-SIG (self_report, véase RECEIVER-PROTOCOLS.md) |
rinex |
Archivo de observación RINEX 3 | códigos S (C/N0) por satélite y señal; con un archivo de navegación de radiodifusión (nav), las pseudodistancias para la solución propia del motor |
android |
CSV de Android GnssLogger | filas Raw: C/N0 y AgcDb por satélite; filas Fix: posición |
nmea |
Texto NMEA 0183 | GSV relación señal/ruido por satélite (dB-Hz, con o sin el identificador de señal 4,10) y su elevación y acimut, GSA dilución de la precisión y satélites usados, GGA y RMC hora y posición |
sbf |
Binario de Septentrio (0,38, solo lectura) | posición de PVTGeodetic, C/N0 de MeasEpoch por satélite y señal, recuento de bandas de RFStatus; véase RECEIVER-PROTOCOLS.md |
rtklib-pos |
Archivo de solución de RTKLIB (.pos) |
hora, posición (latitud/longitud o ECEF, convertida) y satélites usados por época; los archivos en grados-minutos-segundos se rechazan con un mensaje |
novatel |
Registros ASCII o binarios de NovAtel OEM7 | posición de BESTPOS (solo si se calculó solución), C/No de RANGE por satélite y señal; los indicadores de jammer de RXSTATUS pasan a ser el self_report de la época (estado de jamming 2 cuando el receptor señala un jammer, 1 cuando no; en RXSTATUS no existe indicador de spoofing), contrapuestos a las alarmas de Kshana mediante la comparación receiver-self-report |
gnss-sdr |
Flujos Monitor y PVT-monitor de GNSS-SDR como líneas JSON | C/N0 por canal, solución, satélites usados, HDOP, acimut y elevación; generado por integrations/gnss-sdr/relay.py |
Detalles, fuentes y límites de estos tres: MACHINE-INPUTS.md.
Cada época que producen los lectores lleva además source_span, desplazamientos de bytes [start, end) en la entrada: el menor rango contiguo que contiene los registros que contribuyeron a ella, de modo que el fragmento bruto exacto pueda someterse a hash para una ventana de eventos. NMEA: cada línea leída mientras la época está en curso (un superconjunto cuando una línea no relacionada se sitúa entre sus sentencias). UBX: las tramas de la época. RINEX 3: los registros de época desde cada línea > hasta la siguiente. Android: las filas Raw de la época (una fila Fix solo para una época sin filas Raw). Ausente en un flujo en vivo y donde no está bien definido (RINEX 2).
El archivo se introduce mediante path (resuelto respecto a la propia carpeta del escenario) o en línea como text o base64 (las únicas opciones en un navegador).
Un registro de IMU como segundo sensor#
[log.imu] añade un registro inercial sobre el mismo reloj:
[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
La velocidad de guiñada se integra a partir de heading0_deg y rellena el rumbo allí donde el registro del receptor no lleva ninguno, de modo que el monitor heading-course (que necesita heading_sensor = true en la plataforma) compara el rumbo del giróscopo con el rumbo sobre el fondo. Un giróscopo deriva: el rumbo es tan bueno como la ventana de integración, y hdg_cog_tol_deg decide qué cuenta como discrepancia. El formato csv está documentado en MACHINE-INPUTS.md. Los bytes de la IMU forman parte del hash del escenario y del hash del escenario de un paquete de evidencias. La forma en línea (text/base64) funciona en todas las superficies; un path se rechaza donde no se leen archivos.
Plataforma: estática o embarcación#
Los monitores anteriores suponen una antena fija. Un escenario declara la plataforma antes de la ejecución en una tabla [platform] de nivel superior; sin ella, la plataforma es static y nada cambia.
[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)
| Clave | Valor por defecto | Por qué ese valor |
|---|---|---|
max_speed_kn |
30 | Por encima de la velocidad de servicio de los cascos mercantes y de la mayoría de las embarcaciones de pasaje, de modo que una embarcación real nunca la alcanza; muy por debajo de la velocidad que implica un arrastre de posición. Una embarcación más lenta debería declarar un límite menor: cuanto menor es el límite, antes se detecta un arrastre. |
max_accel_mps2 |
0.5 | Un buque grande cambia de velocidad en mucho menos de 0,1 m/s²; una embarcación pequeña y rápida alcanza unas décimas con un cambio brusco de aceleración. |
max_turn_rate_dps |
6 | Un buque grande gira a mucho menos de 1 deg/s y una embarcación pequeña en un giro cerrado, a unos pocos. |
antenna_height_m |
ninguna | Una propiedad de la instalación; una estimación sería un umbral oculto. |
heading_sensor |
false | Declarado true y ausente del registro es un error, de modo que un sensor ausente no pueda pasar por una comprobación limpia. |
Las claves de embarcación se rechazan bajo kind = "static". Para una embarcación, el monitor position-jump no se ejecuta, porque mide la distancia a la media de calibración, lo cual es erróneo en una antena en movimiento; el monitor_config del resultado registra la plataforma y los límites usados.
Los flujos NMEA sintéticos de kshana nmea-scenario, hechos para la formación de la tripulación, son aceptados por el lector NMEA y pueden ejecutarse con este tipo de escenario como cualquier registro NMEA.
Monitores#
Cada monitor necesita sus datos; un monitor cuyos datos no están en el registro no se ejecuta y no figura en monitors_run. Los primeros calibration_s segundos (por defecto 60) fijan la línea base y nunca dan alarma.
| Monitor | Da alarma cuando | Necesita |
|---|---|---|
cn0-drop |
el C/N0 medio de los satélites comunes con la línea base cae cn0_drop_db o más (por defecto 6 dB) |
C/N0 |
loss-of-lock |
los satélites seguidos caen sats_lost o más por debajo de la mediana de la línea base (por defecto 4) |
C/N0 |
agc |
la lectura del AGC se aparta de su línea base en más de agc_k_sigma desviaciones típicas (por defecto 5) |
UBX MON-RF o AgcDb de Android |
jam-ind |
el jamInd de u-blox supera jam_ind_threshold (por defecto 80 de 255) |
UBX MON-RF |
position-jump |
la solución del propio receptor dista más de position_jump_m de su media de calibración (por defecto 50 m) |
una posición informada |
raim |
la monitorización autónoma de la integridad en el receptor (RAIM) de paridad sobre la solución monopunto del propio motor rechaza el conjunto de mediciones | registro RINEX con nav |
solve-failure |
la solución monopunto del motor falla con cinco o más satélites | registro RINEX con nav |
clock |
el reloj del receptor se aparta de su recta predicha en más del desfase mínimo detectable de su ruido de calibración | registro RINEX con nav |
Perfil calibrado (opcional). [monitors] profile = "jammertest-2024-calibrated" sustituye los tres umbrales de interferencia por valores calibrados con el conjunto de datos público JammerTest 2024 (un u-blox ZED-F9P por escenario; Zenodo 15911589): cn0_drop_db = 1.5, agc_k_sigma = 3.5, jam_ind_threshold = 29. La calibración se preregistró (xval/jammertest-trust/PREREGISTRATION.md) y el perfil es MODELLED, ajustado sobre una campaña pública: su umbral cn0_drop_db no satisfizo el umbral preregistrado de falsas alarmas del conjunto reservado (0,0231 frente a 0,02); los umbrales de AGC y de indicador de jamming y el umbral de la unión (falsas alarmas 0,033, detección 0,81) se satisficieron (xval/jammertest-trust/RESULTS.md). Es válido para ese tipo de receptor en ese evento, frente a franjas de ataque programadas, y se calibró con los registros CSV del conjunto de datos (sus registros UBX dan más falsas alarmas en dos carpetas). Un resultado ejecutado con el perfil repite los umbrales que aplicó y lleva un profile_note que dice todo esto. Sin el perfil se aplican los valores por defecto anteriores y toda salida permanece sin cambios.
Para una plataforma vessel también se ejecutan los monitores de plataforma en movimiento (umbrales en una tabla [maritime] de nivel superior; cada valor por defecto está documentado en src/receiver_trust/maritime.rs); cada uno necesita sus datos y figura en monitors_run solo cuando llegó a decidir:
| Monitor | Da alarma cuando | Necesita |
|---|---|---|
kinematic |
el cambio de posición discrepa de la velocidad y el rumbo informados, o implica una velocidad, una aceleración o una velocidad de giro por encima de los límites de la embarcación | soluciones; velocidad y rumbo (RMC o VTG) para la parte de navegación por estima |
heading-course |
el rumbo del giróscopo y el rumbo sobre el fondo difieren en más de hdg_cog_tol_deg (velocidad superior a min_speed_kn) |
rumbo HDT, THS o VHW, heading_sensor = true |
speed-log |
la velocidad respecto al agua y la velocidad sobre el fondo difieren en más de stw_sog_tol_kn |
VHW o VBW, y velocidad sobre el fondo |
sea-level |
la altitud de GGA difiere de antenna_height_m en más de sea_level_tol_m |
altitud de GGA, antenna_height_m |
cn0-spread |
la dispersión del C/N0 entre satélites cae a cn0_spread_frac de su mediana de calibración |
GSV |
cn0-rise |
el C/N0 de los satélites comunes subió cn0_rise_db de media y subió cn0_rise_frac de ellos |
GSV |
time-consistency |
los tiempos de épocas consecutivas avanzan de forma irregular, retroceden o se apartan del reloj del anfitrión | hora de NMEA; reloj del anfitrión para la última parte |
osnma |
el receptor informa de un fallo de autenticación OSNMA (se lee un estado informado; no se verifica nada; para verificar OSNMA a partir de las propias páginas I/NAV, véase docs/OSNMA.md) | sentencia de estado $PKSOS |
Una época es no fiable cuando dan alarma RAIM, el monitor de reloj, un fallo de resolución o un salto de posición, o cuando la caída de C/N0 alcanza el doble de su umbral; degradada con cualquier otra alarma; y nominal en caso contrario.
La ruta del motor (raim, solve-failure, clock) es el mismo cálculo que la referencia independiente de spoofing de JammerTest 2024 de tests/jammertest_spoof_oracle_support: tests/receiver_trust_jammertest.rs comprueba que, en los ocho inicios publicados, la primera alarma cae en la misma época y es del mismo tipo.
Puntuación de confianza (plataforma de embarcación)#
Para una embarcación, cada época posterior a la calibración recibe además una puntuación de 0 a 100 y los motivos por los que no es 100. La correspondencia es determinista y está fijada antes de puntuar cualquier registro: sin aprendizaje, nada ajustado a eventos. Cada monitor reduce su evidencia a una razón statistic / threshold (una alarma con 1 o más); una razón le cuesta a la puntuación
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 un monitor no cuesta nada mientras está por debajo de onset_ratio de su umbral (por defecto 0,5), cuesta la mitad de su peso en su umbral y todo su peso con full_ratio veces el umbral (por defecto 1,5); la puntuación no es creciente en ningún estadístico. Una fuente que no informa en una época conserva su último estadístico durante evidence_hold_s (por defecto 10 s: dos ciclos de una sentencia de C/N0 enviada cada 5 s), tanto como alarma como como deducción.
El redondeo es de medio hacia arriba (floor(x * 10 + 0.5) / 10), la banda es la de la puntuación redondeada y el recorte va antes del redondeo. Solo los monitores cuyos puntos superan cero figuran en las deducciones, con su razón y sus puntos sin redondear, primero los de más puntos; las deducciones con igual número de puntos se enumeran en el orden de los nombres de monitor 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 un monitor cuya prueba es de pasa o falla (una resolución fallida, una autenticación fallida) se le asigna la razón 1,5, el peso completo. Los puntos enumerados son los anteriores al recorte en 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 (puntos) | Monitores | Por qué |
|---|---|---|
| 70 | osnma |
un fallo de autenticación es una declaración, no un estadístico |
| 60 | kinematic, raim, clock, position-jump, time-consistency |
una posición falsificada tiene que contradecir la física o las comprobaciones del propio receptor, y el tiempo no puede ir hacia atrás; una infracción con plena intensidad (1,5 veces su umbral: 60 puntos, puntuación 40) es no fiable, y en su umbral (30 puntos, puntuación 70) es degradada |
| 40 | heading-course, speed-log, solve-failure |
un sensor independiente en desacuerdo es degradado; dos con plena intensidad son no fiables |
| 30 | sea-level, cn0-spread, cn0-rise, cn0-drop |
el entorno de señal, o una comprobación poco informativa |
| 25 | loss-of-lock, agc, jam-ind |
el entorno es hostil; eso por sí solo no demuestra que la solución sea errónea |
hasta 15, [geometry] |
geometry (0.36) |
no es un peso en esta rampa: nada salvo que el cielo sea malo, y entonces hasta cap_points (15); la geometría por sí sola llega a degradada, nunca a no fiable, y no da ninguna alarma (véase «Geometría» más abajo) |
Los bordes de las bandas se deducen de los pesos: el monitor más ligero que da alarma cuesta 12,5 puntos en su umbral, de modo que cualquier alarma saca la puntuación de la banda nominal; ningún monitor individual por debajo de 60 puntos puede alcanzar por sí solo la banda no fiable (varios juntos sí). El resultado registra el modelo de puntuación (score_model) y cada época lleva score.deductions: qué monitores dedujeron, sus razones y sus puntos. Para una embarcación, el estado de la época es la banda de su puntuación.
Geometría: degradación natural distinta de la incoherencia propia de un ataque#
Un receptor junto a una grúa de muelle, en una esclusa o bajo un casco alto pierde satélites y su solución empeora por un motivo que no es un ataque. Desde 0,36, según la nota de diseño de geometría (docs/design/2026-10-12-geometry-and-skyline-design.md, sección 4), cada época lleva su geometría de satélites, la puntuación de una embarcación tiene un monitor de geometría y cada deducción lleva una clase: geometry (el cielo), environment (el entorno de señal) o inconsistency (evidencia propia de un ataque).
De dónde sale la geometría. De la elevación y el acimut de cada satélite seguido (NMEA GSV, UBX NAV-SAT), a partir de los cuales se deriva la dilución de la precisión (DOP) con el único núcleo de DOP del motor (orbit::dop, VALIDATED frente a gnss_lib_py): source = derived. En su defecto, del DOP y de los satélites usados que declara el receptor (NMEA GSA): source = reported. Las posiciones del cielo y GSA cuentan durante el evidence_hold_s de la puntuación (por defecto 10 s), porque muchos buses envían GSV cada pocos segundos; un conjunto GSV incompleto en una época (el final de un extracto, una sentencia perdida) no se usa. Un registro sin ninguno de los dos, como un NMEA con solo GGA y RMC, o hoy los registros de Android y RINEX, recibe el estado unknown con la nota «geometry unknown»: el motor no adivina una geometría que no se le dio, y el monitor de geometría no se ejecuta.
geometry.status |
Significado |
|---|---|
good |
satélites suficientes y DOP dentro de los umbrales |
degraded |
uno o más motivos: few-satellites, high-pdop, high-hdop y (informativo, no entra en la puntuación) sky-blocked cuando todos los satélites seguidos están en una mitad del horizonte |
unknown |
el registro no da geometría de satélites para esta época |
El estado es descriptivo y lo que importa para la puntuación es la etiqueta (más abajo): degraded incluye el caso informativo sky-blocked, que puede darse con un DOP bueno o aceptable, y solo una etiqueta poor puntúa.
La etiqueta (siempre informada, nunca puntuada). El geometry.label de cada época es good (HDOP como máximo hdop_good 2, PDOP como máximo pdop_good 3 y al menos used_good 6 satélites), poor (HDOP superior a hdop_max 4, PDOP superior a pdop_max 6 o menos de min_used 5 satélites), fair entre ambos, o unknown. La nota de la época siempre empieza con la etiqueta y las cifras, por ejemplo «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)», y añade «, signal loss» cuando se da.
El monitor de geometría (puntuación de embarcación). No deduce nada salvo que la geometría sea mala: su razón es max(hdop / hdop_max, pdop / pdop_max, min_used / n_used), y mientras esta sea como máximo 1 (geometría buena o aceptable, incluido un cielo abierto solo con GPS de 8 satélites) no cuesta nada, de modo que un registro así puntúa exactamente igual que en 0,35. Por encima de 1 la deducción crece linealmente hasta cap_points (15) en full_ratio (1,5 veces los umbrales: HDOP 6, PDOP 9, 3 satélites): 7,5 puntos con razón 1,25 (HDOP 5, o 4 satélites), 15 a partir de 1,5 (puntuación 85, degradada). Por sí solo nunca puede bajar la puntuación de floor (por defecto: el degraded_min de la puntuación, 55), de modo que la geometría por sí sola llega como mucho a la banda degradada y nunca a la no fiable: el tope en vigor es min(cap_points, 100 - floor), así que una tabla [score] que suba degraded_min (una de 0,35 con degraded_min 90, por ejemplo) rebaja el tope a 100 - degraded_min en lugar de rechazarse, y un floor fijado por debajo de degraded_min se rechaza. El monitor no da ninguna alarma; figura al final de monitors_run cuando decidió en alguna época (una embarcación cuyo registro da la geometría), y no se cuenta en la identidad con 0,35; no es una entrada de [score.weights] (fijarla se rechaza), y el tope y el suelo en vigor se registran en geometry_model.cap_in_force y geometry_model.floor_in_force. La nota de una época mala dice por qué, por ejemplo «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».
Clases. Cada deducción (score.deductions[].class):
| Clase | Monitores |
|---|---|
geometry |
geometry (geometría mala); loss-of-lock cuando todos los satélites perdidos respecto a la línea base de calibración estaban por debajo de low_elev_deg en su último informe |
environment |
cn0-drop, cn0-spread, cn0-rise, agc, jam-ind, loss-of-lock en caso contrario; geometry durante la pérdida de señal (más abajo) |
inconsistency |
kinematic, heading-course, speed-log, sea-level, time-consistency, clock, raim, position-jump, osnma, solve-failure; y geometry cuando la geometría es mala y el HDOP declarado discrepa del cielo declarado (más abajo) |
La salvaguarda contra un cielo malo falsificado. Un spoofer podría enviar un GSV escaso, o informar de un HDOP alto, para ganar tolerancia. El motor etiqueta y no perdona: ninguna otra comprobación se ensancha por una geometría mala, y el HDOP que declara el receptor (GSA dentro de la retención, si no GGA) se compara con el HDOP derivado del cielo que declara. Cuando difieren en más de max(reported_vs_derived_tol, reported_vs_derived_rel × derived HDOP) (1,0, o el 25 % del HDOP derivado, el que sea mayor), la época lleva reported_derived_mismatch: true y, cuando la geometría es mala, su deducción se clasifica como inconsistency. Con geometría buena o aceptable, la discrepancia se informa y no se puntúa: no se deduce nada salvo que la geometría sea mala. Es una decisión deliberada (0,36): «cero salvo que sea mala» rige para cada época, de modo que la geometría buena y la aceptable puntúan exactamente igual que en 0,35, y un HDOP falsificado nunca pasa en silencio. El indicador (reported_derived_mismatch) está en cada época del resultado y en cada línea en vivo, la nota dice «…beyond the tolerance: reported, not scored (the geometry is not poor)» o «…: the geometry deduction is classed an inconsistency», geometry_summary.hdop_mismatch cuenta las épocas, y la página de resumen de evidencias muestra el recuento, incluido el cero. El 25 % es una elección de ingeniería REPRESENTATIVE, no una cifra medida ni normalizada: un receptor que resuelve un reloj por constelación, o con un subconjunto de los satélites que sigue (una máscara de elevación, un corte de C/N0, una exclusión por RAIM), declara un HDOP que difiere del HDOP de reloj único de todos los satélites seguidos en una cantidad que crece con el HDOP, de modo que una tolerancia fija por sí sola leería como incoherencia una geometría mala natural. Una diferencia mayor sigue leyéndose como tal. Una deducción clasificada como geometry es una afirmación sobre el cielo, no la constatación de que no hubo ataque.
La pérdida de señal no es el cielo. Cuando los satélites que el receptor lista a la vista (por encima de low_elev_deg) no están siendo seguidos, el motor deriva además el DOP de todos los satélites a la vista. Si ese DOP a la vista está dentro de los umbrales y o bien no se sigue ninguno o bien los satélites no seguidos están repartidos por el horizonte (no confinados a una mitad), el cielo estaba y la señal no: la época lleva signal_loss: true, la nota «satellites in view but not tracked: signal environment, not geometry», y la deducción de geometría se clasifica como environment. Los satélites no seguidos confinados a una mitad del horizonte siguen leyéndose como un cielo bloqueado (un muro, un acantilado, un muelle). Una obstrucción por ambos lados (un cañón o un fiordo estrecho) se lee como pérdida de señal hasta que se configura un horizonte. Los centinelas de ausencia de solución del receptor no son DOP: un HDOP o PDOP declarado de 99 o más, una calidad GGA de 0 y un modo GSA de 1 no dan ningún DOP declarado.
Clave [geometry] |
Valor por defecto | Procedencia |
|---|---|---|
hdop_max |
4 | elegido (nota de diseño 4,2): con sigma_uere_m 1,5 m, un HDOP de 4 es un 2drms de 12 m, el kin_pos_tol_m de la comprobación cinemática. Suele citarse que IEC 61108-1 expresa sus requisitos de exactitud para HDOP ≤ 4 (o PDOP ≤ 6): recordado de bibliografía secundaria sobre receptores GNSS, sin consultar ninguna fuente secundaria concreta y sin haber leído la norma; debe comprobarse frente al texto de la norma |
pdop_max |
6 | el umbral de disponibilidad de PDOP de scenarios/polar-arctic-leo-coverage.toml; GPS SPS Performance Standard, 5.ª ed. (2020), PDOP ≤ 6, tal como se cita en tests/constellation_availability_published_spec_oracle.rs; debe comprobarse frente al texto de la norma |
min_used |
5 | uno más de los que necesita una solución, de modo que una sola pérdida no acabe con la solución; el mínimo con una medición redundante para RAIM |
sigma_uere_m |
1.5 | una entrada declarada para el enlace 2drms de hdop_max; fije la cifra del propio receptor |
reported_vs_derived_tol |
1,0 de HDOP | elegido (nota de diseño 4,2): la parte absoluta de la tolerancia; una discrepancia superior a la tolerancia no es geometría |
reported_vs_derived_rel |
0.25 | elección de ingeniería REPRESENTATIVE: la parte relativa, para receptores con relojes por constelación o soluciones con subconjuntos |
low_elev_deg |
15 | nota de diseño 4,3: los satélites perdidos por debajo de él se pierden para el cielo; un horizonte (0,36, elemento 1) ampliará esto |
azimuth_gap_deg |
180 | geométrico, informativo: todos los satélites en una mitad del horizonte |
cap_points |
15 | nota de diseño 4,2: la geometría por sí sola lleva la puntuación de nominal a degradada y nunca a no fiable; solo se deduce cuando la geometría es mala (decisión del fundador B1, 0,36) |
full_ratio |
1.5 | el full_ratio propio de la puntuación: la rampa llega a cap_points con 1,5 veces los umbrales de geometría mala |
floor |
el degraded_min de la puntuación (55) |
la geometría por sí sola nunca llega a la banda no fiable; el tope en vigor es min(cap_points, 100 - floor); un suelo por debajo de degraded_min se rechaza |
hdop_good |
2 | REPRESENTATIVE, solo para la etiqueta: la mitad de hdop_max (la razón de inicio de la puntuación de 0,35) |
pdop_good |
3 | REPRESENTATIVE, solo para la etiqueta: la mitad de pdop_max |
used_good |
6 | REPRESENTATIVE, solo para la etiqueta: uno más que min_used, de modo que una sola pérdida siga dejando una medición redundante |
Qué cambió en las salidas y qué no. Ninguna puntuación cambia salvo que la geometría sea mala; la etiqueta siempre se informa. Claves añadidas: epochs[].geometry (status, reasons, source, n_used, hdop, pdop, vdop redondeado a 0,001, open_fraction (nulo hasta que se configura un horizonte), max_azimuth_gap_deg, age_s, reported_hdop, reported_derived_mismatch, note, in_view (satélites listados por encima de low_elev_deg), signal_loss, label), class en cada deducción y, al final, geometry_model (umbrales, retención, el tope y el suelo en vigor, procedencia, reglas de clase) y geometry_summary (épocas por estado, por etiqueta y por clase de deducción, y las épocas con discrepancia de HDOP declarado); score_model.weights es como en 0,35. Dos pruebas lo mantienen idéntico byte a byte a la línea base «portable de 0,36» una vez retiradas esas claves (los hashes de 0.35.0, rebasados cuando el motor pasó a matemática portable y conservados en las pruebas como registro de lo que publicó 0.35.0: docs/revisions/STUDIO15-portable-math-rebaseline.md). tests/geometry_trust_no_change.rs mantiene todas las salidas de ambas sesiones de ejemplo (JSON de resultado, CSV, SVG, veredicto, líneas en vivo reproducidas, $PKSHT, Prometheus, CEF, LEEF). tests/geometry_corpus_identity.rs mantiene el resultado por lotes, el resumen, las líneas en vivo y el $PKSHT de cada corpus NMEA de embarcación incorporado (la demostración y los datos de prueba marítimos de referencia, cada uno también como cielo abierto solo con GPS de 8 satélites, y los escenarios de formación de tripulación generados a partir de sus semillas); en los dos escenarios de formación cuyo jamming vuelve mala la geometría, toda época que no es mala es idéntica y toda época mala difiere solo por la deducción de geometría. Un registro cuya geometría es mala pierde hasta 15 puntos que no perdía en 0,35; ese es el cambio previsto. En la demostración, el arrastre de posición se lee como inconsistency; tests/geometry_trust.rs edita el registro para enmascarar el cielo occidental (aceptable: etiquetado, no puntuado), para enmascararlo junto con dos satélites orientales (malo: deducciones de geometría, pérdida de enganche environment porque también se perdieron satélites altos), para perder solo satélites por debajo de 15° (pérdida de enganche geometry) y para declarar un HDOP que el cielo no explica (la deducción de geometría mala pasa a ser inconsistency). Los umbrales son MODELLED: elegidos, no ajustados a ningún registro; nada de esto afirma con qué frecuencia se distinguen la degradación natural y los ataques en datos reales.
Declaración de eventos y predicciones#
[[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, el resultado da las falsas alarmas entre la calibración y el inicio, la primera alarma y sus monitores, la latencia, el desenlace (detected, late, missed o not-evaluable) y la caída mediana de C/N0 observada frente a la predicción (agree, disagree, no-prediction o not-evaluable). La discrepancia se informa, nunca se ajusta para hacerla desaparecer: las tolerancias están en el escenario, y el escenario está en el hash del resultado.
Lo que esto no afirma#
Los monitores de confianza son MODELLED: sus umbrales son entradas, y su rendimiento de detección frente a interferencias reales es lo que mide la puntuación de eventos, no algo que afirme este tipo de escenario. En las sesiones de spoofing de JammerTest 2024, los monitores de la ruta del motor detectan algunos inicios en 10 s y llegan tarde en otros; véase la fila «Clock-aided χ², RAIM, AGC, SQM fused per-epoch security FoM» de VERIFICATION-MATRIX.md para las cifras. Una predicción que concuerda con un registro es un dato aislado, no una validación.