%% Referencias de código (no se renderizan en el PNG, solo para mantenimiento futuro)
%% Fuente: src/python/src/navigation/direction_estimator.py
%% infer_direction() -- aplica los 4 filtros de abajo, en este mismo orden
%% DirectionEstimator.observe() -- registra un voto por lectura aceptada
%% DirectionEstimator.is_settled -- se cumple cuando el mismo lado gana MIN_VOTES veces
%% Constantes: src/config/navigation/blind_nav/direction_estimator.toml
%% alignment_tolerance_rad = 0.4363 (25 grados)
%% max_in_track_range_m = 4.5
%% plausible_span_threshold_m= 1.25
%% min_asymmetry_m = 0.20
%% min_votes = 5
%% Avance ciego previo: src/python/src/navigation/corridor_follower.py (follow_corridor)
%% Esquiva ante obstáculo -- detalle común en:
%% schemes/flowcharts/common/mermaid/esquiva-generica.mmd
%% Común a Open Challenge y Obstacle Challenge -- ambos arrancan en modo ciego
%% y usan exactamente esta misma lógica para decidir el sentido de la vuelta.
%% CORREGIDO 2026-09-10: mientras esta fase corre, la regla de paso rojo/verde
%% NO está disponible. Es relativa al sentido de marcha (WRO 2026 9.19) y
%% pass_side_lateral_axis() devuelve None sin una dirección asentada, así que
%% una señal encontrada aquí se esquiva como obstáculo genérico. Es decir: este
%% diagrama es un PRERREQUISITO de la regla de color, no algo paralelo a ella.
%% Ver la cabecera de esquiva-generica.mmd.
%% NOTA: la decisión NO es "cuál lado está más lejos" sino "cuál lado dejó de
%% ser pared", medido sobre la SUMA de ambos rayos. Ver el filtro 3.
flowchart TD
A["El robot avanza despacio,
centrado entre las paredes"] --> Block{"¿Hay un obstáculo
físico muy cerca,
justo adelante?"}
Block -- "No" --> G1
Block -- "Sí" --> Avoid[["Esquiva ante obstáculo"]]
Avoid --> A
G1{"FILTRO 1
¿El chasis está alineado
con el pasillo?
(error < 25 grados)"}
G1 -- "No: de lado los rayos
cortan en diagonal
y miden de más" --> A
G1 -- "Sí" --> M
M["El LIDAR mide el espacio libre
a la izquierda y a la derecha"] --> G2
G2{"FILTRO 2
¿Algún rayo mide
más de 4.5 m?"}
G2 -- "Sí: en esta pista de 3 m
eso no es una pared, es un
fallo de lectura leído como
'lado despejado'" --> A
G2 -- "No" --> G3
G3{"FILTRO 3
izquierda + derecha
¿supera 1.25 m?"}
G3 -- "No: la suma sigue siendo
el ancho del pasillo,
ambos lados son pared" --> A
G3 -- "Sí: un lado dejó
de ser pared" --> G4
G4{"FILTRO 4
¿La diferencia entre
ambos lados supera
0.20 m?"}
G4 -- "No: es ruido,
no evidencia" --> A
G4 -- "Sí" --> C
C{"¿Qué lado
se abrió?"}
C -- "La derecha" --> D["Anota un voto a favor
del sentido HORARIO"]
C -- "La izquierda" --> E["Anota un voto a favor
del sentido ANTIHORARIO"]
D --> F{"¿El mismo sentido
ganó 5 lecturas?"}
E --> F
F -- "Todavía no" --> A
F -- "Sí" --> G["Dirección de la pista
asentada (is_settled)"]
G --> H{"¿Cuál sentido
ganó?"}
H -- "Horario" --> I(["Sentido de carrera:
HORARIO (CW)"])
H -- "Antihorario" --> J(["Sentido de carrera:
ANTIHORARIO (CCW)"])