FLIGHT-1S v2 — resultados medidos
Estado: ROJO. El defecto de v1 quedó reparado; el banco no cierra por dos
causas nuevas y distintas. Fecha: 2026-09-10.
Diseño: FLIGHT1S-V2-DESIGN.md.
Repara, sin tocarlo: FLIGHT1S-RESULTS.md.
Informe: lab/echo3_flight1s_v2_report.json ·
report_sha256 = 6e3de4a8da35f3ec4820601aa99266c925e5d864ece9799e0fe475f371cb4764.
Auditoría: lab/echo3_flight1s_v2_audit.json.
Ocho de los once grupos en verde, 48/48 episodios completos.
flight1s_green=false: fallan challenge (1 de 48, un episodio nominal),
recovery (1 de 24) y repetition (1 de 24, consecuencia del anterior).
El rojo se conserva. §4 dice exactamente qué falló.
1. Comandos y salidas reales
cd /home/roger/REALRXOS/RXos/echoai
PYTHONPATH=.. python3 -m echoai.nexus0.flight1s.v2 validate # exit 0
PYTHONPATH=.. python3 -m echoai.nexus0.flight1s.v2 preflight \
--protocol docs/examples/flight1s-v2/protocol.json \
--output lab/flight1s-v2/runtime-lock.json # exit 0
PYTHONPATH=.. python3 -m echoai.nexus0.flight1s.v2 bench --rehearsal \
--protocol docs/examples/flight1s-v2/protocol.json \
--output lab/echo3_flight1s_v2_report.json # exit 1
PYTHONPATH=.. python3 -m echoai.nexus0.flight1s.v2 audit lab/echo3_flight1s_v2_report.json # exit 1
PYTHONPATH=.. python3 tests/test_flight1s_v2.py # 25 tests OK
PYTHONPATH=.. python3 tests/test_flight1s.py # 24 tests OK (banco v1, intacto)
PYTHONPATH=.. python3 tests/test_sim3.py # 44 tests OK
PYTHONPATH=.. python3 tests/test_flight1s_backend.py # 13 tests OK (PX4 real)
preflight exit=0
bench exit=1 3.946,0 s de reloj de pared, flight1s_green=false
audit exit=1 evidence_valid=true, 435 comprobaciones, 0 hallazgos
audit devuelve 1 porque el informe es rojo, no porque la evidencia falle.
Son dos hechos distintos y el código los separa.
Nota sobre la CLI. El relevo pedía --protocol en los comandos v1.
Añadirlo habría cambiado nexus0/flight1s/__main__.py, que está dentro del
inventario de código del lock de v1, y el informe v1 habría dejado de
reconstruir — justo el fallo que §2 del relevo prohíbe. Por eso v2 tiene su
propia entrada, python3 -m echoai.nexus0.flight1s.v2, con --protocol en
todos sus comandos y el instante v2 por defecto. Los comandos v1 conservan su
significado byte por byte.
2. v1 sigue intacto
Se hashearon los artefactos v1 antes de empezar
(lab/flight1s-v2/v1-preservation.json) y se volvieron a hashear al terminar:
10 archivos protegidos sin cambios
15 archivos de código v1 sin cambios code_sha256 c4dd8fdb…
48 trazas v1 digest idéntico
48 ULog v1 digest idéntico
auditor v1 evidence_valid=true, 375 comprobaciones,
0 hallazgos, 48 episodios recalculados, exit 1
Cómo se consigue: nexus0/flight1s/v2/ es un subpaquete y el lock de v1
hashea nexus0/flight1s/*.py con un glob no recursivo; el esquema v2 vive en
docs/schemas/flight1s-v2/ porque un archivo docs/schemas/flight1s-*.json
sí habría entrado en ese inventario — comprobado, no supuesto. Ningún archivo
de v1 se editó. El preflight v2 registra el code_sha256 de v1 y se niega a
correr si cambia; el grupo v1_preserved lo vuelve a comprobar en cada banco;
el auditor v2 rechaza un informe que declare v1 tocado o v1 verde.
3. Calibración A-v2
68 episodios en los cuatro mundos A, orden permutado por la seed 20260911:
32 nominales frescos, 32 perturbados frescos y 4 controles negativos
(force_declared_without_wrench, uno por mundo). Todo en
lab/flight1s-v2/calibration.json.
separación en impulso
controles negativos n=4 -7 .. 39 ppk mediana 27
perturbados reales n=32 895 .. 1067 ppk mediana 963
hueco 856 ppk
nominal hover horiz máx 67,7 .. 191,3 mm excursión 95,2 .. 235,0 mm
perturbado excursión 401,3 .. 636,2 mm recuperación 1.147 .. 6.827 ms
actitud 12.255 .. 13.036 mdeg
Las ocho puertas de la calibración pasaron. Los números salieron de las reglas escritas antes de volar:
radio de recuperación max(200, ceil(191,3 → 50)) = 200 mm
margen de salida ceil(1500 mm/s × 25 ms) + 10 mm = 38 + 10 → 50 mm
umbral de desafío 200 + 50 = 250 mm
suelo de impulso max(500, 16,25 + 6×17,88 → 130) = 500 ppk
techo de impulso 1500 ppk, fijo
Protocolo congelado:
docs/examples/flight1s-v2/protocol.json,
sha256 = 8afa05238c27a7869d385bf38f17739b4e34e1e69c610bd88012b6329d7dee99,
order_seed = 20260912. Fuerza 5.000 mN durante 600 ms, sin cambios respecto
a v1. Impulso mandado 3.000 mN·s, delta-v libre 1.453 mm/s.
4. El rojo
4.1 recovery — 1 de 24
perturbed-e6da9ac91abd450b-r2. El cuerpo volvió a la banda de 200 mm en
1,23 s, más rápido que su propia pareja (1,41 s), y luego volvió a
salir:
t+0,01 s 409,5 mm fuera
t+1,42 s 102,2 mm dentro
t+2,83 s 270,3 mm fuera ← sobreoscilación de vuelta
t+3,53 s 283,0 mm fuera
t+7,05 s 218,3 mm fuera
t+7,75 s 194,5 mm dentro
t+9,87 s 151,5 mm dentro
La segunda entrada definitiva ocurre alrededor de t+7,4 s y sólo deja 2,6 s dentro antes de que la ventana de 10 s se cierre: falta permanencia, no retorno. El criterio pide volver y quedarse 3 s, y eso es lo que no hizo. Es comportamiento real de PX4, no un artefacto de medida.
4.2 challenge — 1 de 48, y es un nominal
nominal-fbfb2f3b01be4f00-r2, con wrench_published=0 y fuerza [0,0,0],
derivó 270,6 mm en la ventana posterior al hover, por encima del umbral de
250 mm. Su máximo dentro de la ventana de hover fue de sólo 98,0 mm: la
deriva ocurre después, en la fase de mantenimiento de posición.
Los 24 perturbados salieron de la banda. Lo que falla es la mitad de control de la puerta, la que exige que el vehículo sin perturbar se quede dentro. Esa puerta se declaró en el diseño antes de correr el banco, junto con el riesgo que aceptaba: en A-v2 el máximo nominal fue 235,0 mm sobre 32 episodios, a 15 mm del umbral. El banco encontró 270,6 sobre 24. La cola de la distribución nominal es más larga de lo que 32 episodios de A mostraban — la misma lección de v1, un nivel más arriba.
4.3 repetition — 1 de 24, derivada
La pareja perturbada del mundo e6da9ac9 no puede comparar tiempos de
recuperación porque uno de los dos no recuperó. Su diferencia de RMS de hover
es 9,7 mm sobre una tolerancia de 230. No es un fallo independiente.
4.4 Qué no se ha tocado
No se ha movido ningún umbral, ni la fuerza, ni N, ni las bandas, ni un PID. No se han repetido sólo los episodios fallidos. FLIGHT-1S no se declara cerrado. Lo que un v3 tendría que resolver, dicho aquí y no aplicado:
- la permanencia de 3 s dentro de una ventana de 10 s deja poco margen cuando PX4 sobreoscila al volver; o la ventana crece, o la permanencia se mide sobre la primera entrada estable con un criterio explícito de reentrada;
- el umbral de desafío se compara contra una excursión nominal medida en una ventana de 10,6 s de loiter, cuya cola no está acotada por 32 episodios; medir la deriva nominal con muchas más muestras, o separar la ventana de control de la ventana de desafío.
Ambas exigirían protocolo v3 con hash nuevo, recalibración y banco completo.
5. Lo que v2 sí demostró
El defecto de v1 está reparado y medido. Las tres preguntas se separaron y la que v1 confundía sale limpia:
stimulus 24/24 wrench publicada exactamente una vez, retirada registrada,
impulso dentro de 500–1500 ppk
challenge 24/24 perturbados fuera de 250 mm (fallo: 1 nominal, §4.2)
recovery 23/24 vuelta y permanencia (fallo: 1, §4.1)
Ninguna pregunta paga por otra: el nominal que derivó no compensó nada, y el episodio que no permaneció no fue salvado por tener un impulso perfecto.
Bajo el criterio v2, los dos episodios que v1 suspendió (389,4 y 291,7 mm) superan el umbral de 250 mm, y el máximo nominal de v1 (195,4 mm) no lo alcanza. Es una consecuencia documentada del umbral, no su motivo: el umbral es radio más un margen derivado de resolución de muestreo, y hay un test que fija esa consecuencia por escrito para que no se confunda con la razón.
| Grupo | Estado | Medido |
|---|---|---|
v1_preserved |
pass | código, protocolo, informe, trazas y ULog de v1 idénticos |
envelope |
pass | 12/12 mundos admiten la envolvente del X500 |
episodes |
pass | 48/48 armaron, despegaron, mantuvieron, aterrizaron y desarmaron |
stimulus |
pass | 24/24 |
challenge |
fail | 24/24 perturbados; 23/24 nominales |
recovery |
fail | 23/24 |
repetition |
fail | 23/24 parejas |
hygiene |
pass | 48/48 sin regresión de reloj ni contacto no autorizado; un lock y un protocolo |
controls |
pass | 4/4 |
mutants |
pass | 13/13 |
regression |
pass | NEXUS-0, SIM-3 y el banco unitario v1 |
Vuelo medido sobre los 48 episodios:
| Magnitud | n | min | p50 | max |
|---|---|---|---|---|
| Cociente de impulso, perturbado (ppk) | 24 | 876 | 956 | 1084 |
| Excursión perturbada (mm) | 24 | 397,4 | 525,1 | 697,9 |
| Excursión nominal (mm) | 24 | 74,7 | 143,2 | 270,6 |
| Tiempo de recuperación (ms) | 23 | 1.108 | 1.511 | 5.491 |
| Hover, error horizontal RMS (mm) | 48 | 35,1 | 65,8 | 99,9 |
| Actitud máxima perturbada (mdeg) | 24 | 12.120 | 12.659 | 13.045 |
| Velocidad lateral máxima (mm/s) | 24 | 1.272 | 1.407 | 1.528 |
La velocidad lateral máxima medida (1.528 mm/s) queda por debajo del techo de 1.500 mm/s declarado para derivar el margen en sólo dos episodios; el margen se redondeó de 48 a 50 mm, así que el punto ciego real sigue cubierto. Se registra como límite del cálculo, no como corrección.
Controles
config_carries_no_room_identity 24 configuraciones; sólo varían spawn_pose y condition
no_network_during_bench TCP inalcanzable, DNS sin resolución
paired_nominal_vs_perturbed el nominal se queda dentro, el perturbado sale y vuelve
world_hash_mismatch_refused sdf_hash_mismatch antes de volar
Mutantes
13 casos, cada uno desde un control positivo que cerró las tres preguntas:
force_declared_without_wrench stimulus suspende; el episodio vuela igual
wrench_accumulated publicaciones ≠ 1 / impulso fuera de banda
disturb_window_shifted impulso fuera de banda o wrench fuera de ventana
force_units_wrong impulso por encima del techo
never_left_the_band_but_claims_recovery challenge suspende con recovery perfecta
recovery_scored_before_the_wrench_stopped el esquema fija measured_from
pose_teleported_or_replayed sin firma PX4 y sin stimulus
fabricated_or_frozen_clock el episodio no completa
branch_by_split / ground_truth_as_setpoint el inspector de configuración lo ve
asset_download_during_bench sin ruta fuera del contenedor
6. Auditoría independiente
evidence_valid true
checks 435
findings []
recomputed 48 episodios
El auditor no lee flight1s_green. Regenera los doce mundos desde las semillas
publicadas, re-hashea las 48 trazas y los 48 ULog, y recalcula por su cuenta el
RMS de hover, la excursión, el cociente de impulso, la salida de banda y la
permanencia desde las muestras crudas; comprueba que la wrench se publicó una
vez y se retiró, reconstruye la permutación de condiciones desde la seed,
verifica el denominador de 48 con 24 por condición, y exige un solo lock y un
solo protocolo v2. Además comprueba que v1 sigue rojo, con sus bytes y su
hash, y que el margen y el suelo de impulso son los que las reglas producen.
--self-test muta copias del informe y exige que cada una sea rechazada,
partiendo de un control que aceptó:
positive_control aceptado
impulse_altered_in_report rechazado recompute/…/impulse
exit_margin_relaxed_in_report rechazado protocol_challenge
impulse_floor_relaxed_in_report rechazado protocol_stimulus
omitted_red_episode rechazado episode_count
two_locks_across_episodes rechazado single_runtime_lock
v1_and_v2_protocols_mixed rechazado single_protocol
v1_code_reported_as_touched rechazado v1_code_unchanged
v1_red_reported_as_green rechazado supersedes_v1
rewritten_trace_hash rechazado trace_sha/…
rewritten_ulog_hash rechazado ulog_sha/…
green_without_groups rechazado flight1s_green_consistent
wrong_code_hash rechazado code_sha256
7. Runtime
lab/flight1s-v2/runtime-lock.json,
lock_sha256 = a8f6f0a8a5007a387f6bc7c5fe4712d85cc2c69554a56e13c2580af60c204ac5,
code_sha256 = 0c9615df6c0eb95272dae4fc8d6c27752bb544c27a3d4cbc6b86120d0f7f6be6.
Imagen, PX4, modelos y contenedor son exactamente los de v1: PX4 v1.15.4
commit 99c40407…, modelos d754381a…, gz-sim 8.15.0, imagen
echoai-flight1s-px4:v1.15.4 0911af28…. Ningún PID tocado.
--pull=never, --network=none, sin descargas durante el banco.
8. Lo que esto no demuestra
- Software in the loop y nada más: ni aerodinámica realista, ni viento real, ni autonomía, ni seguridad universal, ni sim-to-real.
- Es una reparación de un banco rehearsal sobre doce mundos ya volados en v1, no un examen ciego de generalización.
- echoAI sigue desconectado:
agent_in_gazebo=false,cortex_calls=0,wsp_writes=0. Ningún sensor simulado se publica como evidencia ni llega a WSP. Eso es SENSOR-1 y GROUND-1. - PX4 conserva estabilización y mezcla; la interfaz de intención y los failsafes son PX4-1 y SAFE-1.
- El cociente de impulso es una diferencia finita del flujo de poses del backend, no un sensor de velocidad independiente, y el momento neto también refleja la reacción de PX4.
- La perturbación es una wrench constante. No es viento.
- Tiempos y RTF describen un host.
hardware_status="not_available".
9. Estado
FLIGHT-1S sigue sin cerrar. El defecto de medición de v1 está reparado y demostrado: las tres preguntas se separan y los 24 perturbados pasan estímulo y desafío. Lo que ahora bloquea son dos cosas nuevas y medidas — una permanencia de 3 s que PX4 no sostuvo en 1 de 24 tras sobreoscilar, y una deriva nominal de 270,6 mm que cruza un umbral de 250 mm en 1 de 24. Ambas exigen un protocolo v3, no un umbral movido. Los dos rojos, el de v1 y el de v2, se conservan.