%% Referencias de código (no se renderizan en el PNG, solo para mantenimiento futuro)
%% Salida de la bahía: src/python/src/navigation/bay_exit.py, invocado desde
%% corridor_follower.py::follow_corridor() y track_navigator_node.py (rama
%% ASSUME_BAY_START). Constantes: bay_exit_* en
%% src/config/navigation/blind_nav/corridor_follower.toml
%% Fase 1 (inferir dirección) -- detalle común en:
%% schemes/flowcharts/common/mermaid/inferencia-direccion.mmd
%% SignRouter: src/python/src/navigation/planning/sign_router/
%% descubrimiento en vivo: src/python/src/navigation/planning/sign_discovery.py (ObservedSignMap)
%% selección de señal activa: SignRouter._active_sign_candidates()
%% regla de paso y carril: ver schemes/flowcharts/obstacles/mermaid/regla-senales.mmd
%% enmascarado de LIDAR: mask_mapped_obstacles() (control/controllers), escape_mask_radius_m = 0.12 m
%% rearme por vuelta: SignRouter.reset_for_new_lap()
%% Vigilancia de colisión/atasco -- detalle común en:
%% schemes/flowcharts/common/mermaid/escape-colision.mmd
%% Conteo de vueltas -- detalle común en:
%% schemes/flowcharts/common/mermaid/conteo-vueltas.mmd
%% Continúa en: schemes/flowcharts/obstacles/mermaid/flujo-parte2-estacionamiento.mmd
%% -
%% ACTUALIZADO 2026-09-10, dos cambios:
%% -
%% 1) FALTABA EL ARRANQUE DESDE LA BAHÍA. Desde 2026-09-05 el Obstacle Challenge
%% arranca DENTRO del hueco de estacionamiento por defecto
%% (assume_bay_start = true): es el arranque que vale 7 puntos. Antes de
%% poder inferir nada el robot tiene que salir del hueco, y esa maniobra no
%% aparecía en ningún diagrama. Nótese que ASUMIR la bahía es seguro aunque
%% sea falso: un arranque en paralelo tiene espacio libre adelante y la
%% creencia se descarta en el tick siguiente.
%% -
%% 2) LA NOTA SOBRE LA REGLA DE COLOR ERA FALSA. Decía que incluso en la Fase 1,
%% antes de fijar la dirección, una señal se esquiva por su lado correcto
%% "porque la regla no depende del sentido de la vuelta". La regla SÍ depende
%% del sentido (WRO 2026 9.19, y ROUTING_TABLE tiene filas opuestas por
%% sentido), y pass_side_lateral_axis() devuelve None sin dirección asentada.
%% Durante la Fase 1 una señal se esquiva como obstáculo genérico.
%% -
%% La salida de la bahía es un TRINQUETE contra la pared exterior: piernas
%% alternas adelante/atrás a tope de volante, con el volante ESPEJADO en el
%% retroceso -- sin espejar, el retroceso rehace el arco de la ida y la maniobra
%% se vuelve un péndulo que no rota (resuelto 2026-09-10: 62.2 s -> 24.1 s solo
%% con el espejo, y 10.4 s con el presupuesto de frenado medido). Cada pierna se
%% corta por CLEARANCE PREDICHA contra las aletas, no por contacto: terminar una
%% pierna tocando una aleta ES la violación 9.24.7 y anula los puntos de
%% estacionamiento. El criterio de salida es la ROTACIÓN acumulada
%% (bay_exit_target_yaw_deg = 70), no el espacio libre adelante: dentro del
%% hueco la pared cae por debajo del rango mínimo del LIDAR y no se mide nada.
flowchart TD
Start(["Inicio de la carrera
(modo ciego: sin mapa
ni dirección conocidos)"]) --> InBay{"¿Está dentro del hueco
de estacionamiento?
(se asume que sí)"}
InBay -- "No: hay espacio
libre adelante" --> Fase1
InBay -- "Sí" --> BayExit
subgraph FASE_0["Fase 0 - Salir de la bahía (solo Obstacle Challenge)"]
BayExit["Gira el volante a tope
y avanza un tramo corto"] --> Guard{"¿La pierna dejaría al
chasis demasiado cerca
de una aleta?"}
Guard -- "Sí" --> Flip["Descarta esa pierna e
invierte el sentido de marcha"]
Guard -- "No" --> Move["Ejecuta la pierna"]
Flip --> Mirror
Move --> Mirror["Al invertir, ESPEJA el volante:
sin espejo el retroceso rehace
el arco de ida y no se rota"]
Mirror --> YawCheck{"¿Ya rotó 70 grados
desde la posición
de arranque?"}
YawCheck -- "No" --> BayExit
end
YawCheck -- "Sí" --> Fase1
Fase1[["Inferencia de dirección"]] --> Commit["Fija la dirección
y ubica al robot en la pista"]
Commit --> StartPose["Mide con precisión
la posición de arranque"]
StartPose --> PlanPath["Traza la ruta de
puntos de paso a seguir"]
PlanPath --> BuildRouter["Prepara el reconocimiento de señales.
Como es modo ciego, empieza vacío
y se llena con lo que ve la cámara"]
BuildRouter --> BuildLap["Prepara el contador
de vueltas para esa ruta"]
BuildLap --> Drive
subgraph FASE_2["Fase 2 - Conducción y esquiva de señales"]
Drive["Sigue la ruta trazada"] --> Discover["La cámara detecta señales
nuevas y las añade
al mapa de señales"]
Discover --> Candidate["Elige la señal activa más
cercana que aún no ha pasado"]
Candidate --> HasSign{"¿Hay una señal
activa cerca?"}
HasSign -- "No" --> Watch
HasSign -- "Sí" --> Reroute[["Regla de paso por señal"]]
Reroute --> Watch[["Vigilancia de colisión y atasco"]]
Watch --> WPCheck
end
WPCheck{"¿Llegó al punto
de paso objetivo?"}
WPCheck -- "No" --> Drive
WPCheck -- "Sí, y cerró
la vuelta de la ruta" --> LapPhase
subgraph FASE_3["Fase 3 - Contar vueltas"]
LapPhase[["Conteo de vueltas"]] --> Rearm["Vuelve a habilitar todas
las señales para
la siguiente vuelta"]
end
Rearm --> LapsDone{"¿Ya completó
las 3 vueltas?"}
LapsDone -- "No" --> Drive
LapsDone -- "Sí" --> Next(["Continúa en:
Parte 2 - Final de carrera"])