Bitácora de experimentos MP-26-072
Miniproyecto MP-26-072 · Corte: 9 de octubre de 2026, 18:05

Bitácora de experimentos: controlador neuroevolutivo contra el árbol de comportamiento de Lyra

Documento para la Dra. Ponce de León y el Dr. Cuéllar. Reúne en orden cronológico todo lo que se construyó, probó, corrigió y decidió desde el lunes 5 de octubre, con el motivo de cada cambio de diseño y las cifras que lo respaldan. Se indica de forma explícita lo que no se puede afirmar.

ProyectoOptimización de agentes autónomos en entornos virtuales: enfoque neuroevolutivo aplicado a la IA en videojuegos
ResponsableJuan José Tovar Quiróz
CorresponsablesEunice E. Ponce de León Sentí · Luis D. Cuéllar Garrido
EntornoLyra Starter Game, Unreal Engine 5.5, neat-python 2.0.0
Declaración de uso de IA. Parte del código en C++ y Python, los análisis y la redacción de este documento se produjeron con asistencia de un modelo de IA (Claude, de Anthropic), a partir de la bitácora, el código fuente y los archivos de resultados del proyecto. El responsable definió los objetivos, revisó los resultados y decidió los cambios de diseño. Cada cifra proviene de archivos en NEAT_Training/; las rutas se indican en la sección de archivos.

Resumen

≈810
generaciones evolutivas evaluadas en Unreal (20 corridas)
≈194 mil
episodios de genoma simulados
>900
partidas de comparación contra el BT y de diagnóstico
53 h
con resultados escritos entre el 5 de oct 16:00 y el 9 de oct 17:00 (98 h transcurridas)

Desarrollo técnico: cómo funciona NEAT y cómo se conecta con Unreal

Esta sección responde a las dudas sobre cómo se implementó NEAT en Python, cómo se configura la red, qué contiene un genoma y cómo se conecta con el controlador de Unreal.

Respuestas directas

Arquitectura

flowchart LR
  A["evolve.py
neat-python: población de genomas"] -->|"genomas.json"| B["ue_evaluator.py
lanza N procesos de Unreal"] B --> C["UnrealEditor-Cmd
servidor sin ventana, L_GymAVE"] C --> D["LyraNeatBotController
cerebro C++ de cada genoma"] D --> E["LyraNeatEpisodeSubsystem
mide el episodio"] E -->|"launch_*.json"| B B -->|"resultados.json"| A A --> F["fitness.py
fitness por genoma"] F --> A

Ciclo de una generación, paso a paso

  1. Población. evolve.py crea la población con neat.Population (60 genomas) o la reanuda desde un checkpoint (--resume).
  2. Escenarios. Para la generación g define K=4 escenarios con semillas seed·100003 + g·1009 + k. Todos los genomas juegan los mismos escenarios, aunque las posiciones de aparición no se fijan.
  3. Exportación. ave_io.make_genomes_payload escribe genomas.json: la interfaz (entradas, salidas, normalización), los identificadores de nodos de entrada y de salida, los escenarios y, por genoma, sus nodos y conexiones habilitadas.
  4. Lanzamientos. UnrealEvaluator.run divide los genomas en bloques de 10 (o 5 en la etapa 2) y, para cada escenario y bloque, lanza UnrealEditor-Cmd.exe con -server -game -nullrhi, el mapa L_GymAVE?NumBots=…?Experience=B_AVE_Experience, un puerto propio y los argumentos -NeatGenomes, -NeatGenomeOffset, -NeatBotCount, -NeatGeneration, -NeatScenario, -NeatEpisodeSeconds, -NeatTimeDilation y -NeatResults. Con población 60, K=4 y bloques de 10 NEAT + 2 BT son 24 lanzamientos por generación, 8 en paralelo.
  5. Dentro de Unreal. El componente de creación de bots (LyraBotCreationComponent) asigna controlador NEAT a los primeros N bots y el Behavior Tree al resto. Cada ALyraNeatBotController carga su genoma (índice = offset + ranura) y construye su red. El gestor de episodios espera la fase Playing, acelera el tiempo ×3, mide la primera vida de cada bot NEAT durante 60 s (o hasta que mueran todos) y escribe launch_*.json. Luego el proceso termina.
  6. Unión. Python junta los resultados parciales en resultados.json y los valida (validate_results: versión, generación, genomas y campos).
  7. Fitness. fitness.genome_fitness calcula el fitness del episodio y promedia los K episodios de cada genoma. Se guardan components.csv (una fila por episodio) y curve.csv (mejor, medio y número de especies por generación).
  8. Reproducción. neat-python agrupa en especies, selecciona, cruza y muta, y deja un checkpoint por generación. Una generación toma unos 2.4 minutos en el entrenamiento normal.

Archivos de Python (carpeta NEAT_Training/)

ArchivoQué haceFunciones o clases principales
evolve.pyOrquestador del ciclo evolutivo. Semillas de población y reanudaciónmain, eval_genomes (que neat-python llama una vez por generación), banderas --seed-reflex, --resume, --per-launch, --bt-bots
ue_evaluator.pyLanza y coordina los procesos de UnrealUnrealEvaluator: _command, _launch_on_port, run (pool de hilos, un puerto por instancia)
ave_io.pyFormato de intercambio con Unrealgenome_to_dict, make_genomes_payload, validate_results, INTERFACE, EPISODE_FIELDS
fitness.pyFunción de aptitudFitnessWeights, support_lambda, episode_components, episode_fitness, genome_fitness
neat_config.txtConfiguración de neat-pythonSecciones [NEAT], [DefaultGenome], [DefaultSpeciesSet], [DefaultStagnation], [DefaultReproduction]
select_champion.pyElige el campeón entre los 10 mejores con 8 escenarios nuevosReevaluación y reporte (champion_report.txt, champion.json)
compare_bt.pyCompara un genoma contra el BT cara a cara, con la medición de disparos, cobertura y velocidadResumen en summary.txt, summary.json, matches.csv
stats_compare.pyEstadística de las comparaciones (Wilson, binomial, bootstrap, Cliff d)Escribe runs/estadistica_comparaciones.md
calibrate_fitness.py, audit_measurement.py, ab_pitch.py, mutation_robustness.pyPruebas de validación con protocolo escrito antesCalibración con referencias, auditoría ×1 contra ×3, A/B del pitch, robustez a mutaciones
json_net.py, make_golden.pyReferencia para comprobar la red de C++Evaluador sin neat-python; genera golden/golden_test.json
mock_evaluator.pySimulador falso para probar el ciclo sin UnrealSe usó el 5 de octubre (demo_mock)
dataset.py, train.pyCarga de la telemetría humana y primer entrenamiento offline (MSE)Línea descartada como principal
overnight.py, night2.py a night5.py, phase2.py, stage2.py, night_check.pyOrquestadores reanudables y chequeo de corridasColas de experimentos con estado en runs/overnight_state.json
make_figures.pyFiguras para el reporte y el cartel (PIL)Curvas, red del campeón, barras de comparación

Configuración de la red (neat-python)

ParámetroValor
Entradas y salidas13 y 5; 0 nodos ocultos al inicio; conexión inicial completa directa (65 conexiones)
Tipo de redHacia adelante (feed_forward = True); sin memoria
Activación y agregacióntanh (en neat-python equivale a tanh(2.5·z)) y suma; sin mutación de activación
Pesos y sesgosInicio con media 0 y desviación 1; mutación suave: pesos 30 % con potencia 0.1, sesgos 20 % con potencia 0.1, reemplazo 2 %; rango ±30
EstructuraAgregar conexión 0.10; quitar conexión 0.05; agregar nodo 0.05; quitar nodo 0.02; habilitar o deshabilitar 0.05
EspeciaciónUmbral de compatibilidad 1.5; coeficientes 1.0 (genes disjuntos) y 0.5 (pesos)
Estancamiento y reproducciónMáximo 20 generaciones sin mejora; élite 2 por especie y 2 globales; sobreviven el 20 % mejor
Población60 por corrida (el archivo dice 100; se reemplaza con --pop-size 60)

El genoma

En neat-python un genoma es un conjunto de genes de nodo (sesgo, respuesta, activación, agregación) y de conexión (origen, destino, peso, habilitada). Las entradas llevan identificadores −1 a −13 y las salidas 0 a 4. genome_to_dict exporta solo las conexiones habilitadas. Ejemplo real, el reflejo con retroceso hecho a mano (id 9002), con la estructura del campeón 7837: cinco nodos de salida con sesgos (avance −0.21, disparo +2.65) y una sola conexión, de la entrada −9 (ángulo hacia el enemigo) a la salida 2 (giro), con peso 4.94.

EntradaQué esNormalización
healthVida propia0 a 1
dir_x, dir_y, dir_zVector unitario hacia el enemigo (ojos a ojos)−1 a 1; 0 si no hay enemigo
distanceDistancia al enemigoEntre el radio de escaneo (5000 uu, 2500 en percepción por vista); 1 si no hay enemigo
has_enemy, visionHay enemigo; línea de visión despejada0 o 1
aim_errorError de apuntadoGrados entre 180; −1 si no hay enemigo
bearingGiro necesario hacia el enemigo, con signoGrados entre 180, recortado a [−1, 1]
ammo_fracMunición del cargador0 a 1; −1 si se desconoce
ray_front, ray_left, ray_rightTres rayos de obstáculos (0°, −45°, +45°, 500 uu)0 a 1; 1 = libre
SalidaUso en el juego
in_forward, in_rightEntrada de movimiento adelante/atrás y lateral, de −1 a 1 (AddMovementInput)
in_yawVelocidad de giro horizontal: salida × 180°/s
in_pitchVelocidad de giro vertical: salida × 60°/s (anulada en los experimentos, 0°/s)
in_shootDispara si la salida es mayor que 0

Archivos de C++ (Source/LyraGame/Neat/ y relacionados)

ArchivoQué hace
LyraNeatBrainLee genomas.json (LoadGenomesFile, LoadFromGenomeJson), ordena los nodos de forma topológica y evalúa la red (Evaluate): por nodo, tanh(2.5·(sesgo + respuesta·suma de pesos·fuentes)). Sin Python en tiempo de ejecución. Incluye el comando ave.Neat.Golden para la prueba de equivalencia.
LyraNeatObservationConstruye las 13 entradas (BuildObservation, ToNetworkInputs). La telemetría humana usa el mismo código, de modo que humano y bot se registran con las mismas variables.
LyraNeatBotControllerDerivado del controlador de bots de Lyra. Cada 0.1 s ejecuta Think: construye entradas, evalúa la red y guarda las salidas. En cada Tick aplica movimiento (AddMovementInput), giro limitado y disparo mediante las etiquetas de entrada de habilidades. Acumula las estadísticas del episodio.
LyraNeatEpisodeSubsystemExiste solo con -NeatResults. Espera la fase Playing, fija la dilatación de tiempo, mide el episodio, escribe el JSON de resultados y cierra el proceso. Con -NeatMeasureBt mide también a los bots del Behavior Tree (daño, disparos, cobertura y velocidad).
LyraBotCreationComponentLos primeros -NeatBotCount bots que crea el modo de juego usan el controlador NEAT; el resto conserva el Behavior Tree.
LyraTelemetryComponentRegistra las sesiones humanas a 10 Hz (CSV) con la misma observación; desde el 9 de octubre añade la columna speed.

Las banderas de percepción por vista (-NeatRequireLos, -NeatFovHalfDeg, -NeatSightRadius) y las de experimento (-NeatMaxPitchDegPerSecond, -NeatGenomeId, -NeatMeasureBt) se pasan por línea de comandos, y la variable de entorno NEAT_EXTRA_ARGS las agrega a todos los lanzamientos de Python.

Función de fitness, tal como está implementada

Por episodio: F = 1.0·Dd + 0.2·Ts − 0.2·Dr + λ(g)·(0.05·Fcerca + 4.0·Fapunta), con Dd = min(daño hecho / 100, 3), Ts = min(tiempo vivo / 60 s, 1), Dr = min(daño recibido / 100, 1), λ(g) = max(0, 1 − g/30), Fcerca la fracción de ticks con el enemigo a distancia media y Fapunta la alineación media de la mira (1 − error/90°, mínimo 0). El fitness del genoma es el promedio de sus 4 episodios. El peso de Fvis es 0.

Verificación de que Unreal calcula lo mismo que Python

El 9 de octubre, el comando ave.Neat.Golden comparó la red de C++ con la referencia de Python sobre golden/golden_test.json: 140 vectores de entrada, error máximo 1.4·10⁻¹⁵, con tolerancia de 10⁻⁵. Resultado: aprobado. Además, mock_evaluator.py permitió probar el ciclo completo sin Unreal el 5 de octubre.

Limitaciones técnicas del diseño

Línea de tiempo

Primero, el ritmo de trabajo medido. Cada celda es una hora; está activa si ese día se escribió al menos un archivo de resultados en NEAT_Training/runs/. Es una medida mínima de que la computadora estuvo ejecutando experimentos: no cuenta las pruebas que no dejan archivos ni el trabajo de redacción.

53 horas con actividad de 98 transcurridas desde el 5 de oct 16:00 (corte: 9 de oct, 17:37). El 8 de oct hubo actividad en 21 de 24 horas y el 9 de oct en 16 de las 18 horas transcurridas. Pausas largas: 7 de oct de 05:00 a 18:00; 8 de oct de 08:00 a 11:00; y 9 de oct de unos 09:20 a 12:05, entre el fin de la cola de la noche y el siguiente experimento, mientras se analizaban los resultados y se escribía el protocolo (sin corridas en ese lapso). El 9 de oct a las 00:45 se detuvo todo por decisión del responsable para corregir el diseño, y se reanudó a las 01:55. Desde las 12:50 corre la cola de reentrenamiento y sigue en marcha. Las horas en recuadro punteado todavía no ocurren.

28 de septiembre
17:41–18:05
ExperimentoPrimer entrenamiento NEAT fuera de Unreal, sobre la telemetría humana

Ajuste por error cuadrático medio (MSE) sobre los datos de las sesiones humanas. Dio salidas casi constantes (MSE 0.749 y 0.739); no se usa como línea principal. Los humanos registrados no son el objetivo de imitación: la solicitud pide evolucionar, no imitar.

1 a 5 de octubre
1–2 oct
DecisiónPlan de acción del miniproyecto

Un solo fitness de combate, referencia humana como dato de contraste y no como entrenamiento, integración por lotes y dos carteles distintos (congreso y miniproyecto).

5 oct 16:27
ConstrucciónFormato de intercambio con Unreal y simulador de prueba

Módulos ave_io.py y dataset.py, y una corrida de 40 generaciones contra un simulador falso (demo_mock) para probar el ciclo evolutivo antes de usar el motor.

6 de octubre
19:10
ConstrucciónPrimer lote real en Unreal (piloto)

4 generaciones, 800 episodios, evaluación por lotes con servidor dedicado sin ventana.

19:28–20:27
CorrecciónEl episodio incluía el calentamiento de 30 s

El mapa tiene una fase Warmup antes de Playing. Las corridas piloto, 2 y 3 se descartaron; el gestor de episodios ahora espera la fase Playing.

20:27–20:52
HallazgoLa señal de selección era casi puro ruido

Corrida 5 (K=2): la correlación entre dos episodios del mismo genoma era 0.09. Más del 80 % de lo que veía la selección era azar. La puntería es el único término estable (≈0.44). Cambio: K=4, población 60, peso de la ayuda de puntería 1.0 → 4.0.

oct 6
DecisiónSe retira Fvis del fitness

Observación del Dr. Cuéllar: ver al enemigo no implica mala conducta. Los datos coincidieron: efecto mínimo y sin repetibilidad entre episodios del mismo genoma.

20:51–23:16
ExperimentoCorrida 6: 122 generaciones

A los 22:20 se disparó una regla fijada de antemano (la puntería no subía) y se subió el peso de puntería a 8. La corrida no aprendió a combatir.

7 de octubre
19:00
CorrecciónSe retira la afirmación «el fitness premia esconderse»

Se había afirmado a partir de una partida de prueba de 2 bots. Una validación de 10 partidas mostró que un genoma mínimo hecho a mano supera al BT cara a cara (daño 112.5 contra 76.5, bajas 0.73 contra 0.43). El fitness sí ordena bien. Lo que fallaba era la búsqueda.

18:41–21:27
ExperimentoCorridas 7 a 11: semillas, especiación y pesos

Semilla mínima de puntería (7), umbral de especiación 3.0 → 1.5 porque la corrida 6 tuvo siempre una sola especie (8), semilla de reflejo (9, 10), pesos 0.2/0.2 (11). La 9 se descartó por un error: los sesgos aleatorios de giro saturaban la salida.

21:35–22:00
CorrecciónCalibración con controladores de referencia y corrección de mi propia conclusión

A las 21:35 se concluyó que con los pesos 0.5/0.5 pelear puntuaba menos que esconderse, con base en 20 episodios. La calibración formal (40 episodios por referencia, rejilla de 25 combinaciones de pesos) lo desmintió: el reflejo supera al inerte y al aleatorio en todas las celdas. El cambio a 0.2/0.2 no era necesario; se conservó y se declara. El diseño del fitness se congeló.

22:40
ExperimentoPausa a petición del responsable

La corrida 11 quedó en la generación 28.

8 de octubre
00:19–01:00
ExperimentoAuditoría de la medición, robustez a mutaciones y prueba A/B del giro vertical

Protocolos escritos antes de ver datos. Resultado: dos causas independientes y ambas necesarias explican que las corridas no aprendieran: (1) el giro vertical es una velocidad acumulada sin realimentación y deriva; (2) las mutaciones por defecto destruyen controladores competentes. Con el giro vertical anulado y mutación suave, los descendientes competentes pasan de 1–4 de 10 a 5–6 de 10.

01:00
DecisiónConfiguración v2 congelada

Mutación suave, pitch anulado, resto igual. Aprobada por el responsable.

01:02–04:49
ExperimentoCorrida 12: 100 generaciones con semilla de reflejo

Sin colapso al apagar la ayuda (g30). Campeón 5582, elegido entre los 10 mejores con 8 escenarios nuevos.

05:06–07:25
ExperimentoControl sin semilla, 60 generaciones

Aprende (la puntería sube de 0.28 a ≈0.6), a diferencia de las corridas 6, 8 y 10, pero a 60 generaciones queda muy por debajo del BT (daño 49 contra 173 en 3v3).

12:20
HallazgoA igual presupuesto, la semilla acelera

Con 60 generaciones, la línea con semilla empata al BT en daño y bajas; la que no tiene semilla pierde 17 de 20 partidas.

13:44–17:03
ExperimentoEtapa 2: 40 generaciones más, 5 NEAT contra 5 BT

Criterio fijado antes: daño y bajas ≥ BT en 3v3 y 5v5, y no peor que el campeón de la generación 100 en 10v2. Cumplido. Campeón 7837.

16:15
ExperimentoEl control sin semilla extendido a 100 generaciones alcanza paridad en 3v3

Daño 88.5 contra 99.5 del BT. A 100 generaciones la semilla ya no es necesaria para llegar a la paridad; acelera el aprendizaje.

17:04
ConstrucciónMedición de uso de cobertura y de disparos del BT

Nueva instrumentación en C++ para medir precisión de disparo (impactos entre disparos) y cobertura, pedidas en la solicitud.

18:10
HallazgoEl campeón 7837 es una red mínima

Una conexión habilitada (rumbo al enemigo → giro) más sesgos: dispara siempre y tiene un sesgo de avance negativo. Se interpretó como retroceder despacio; la velocidad medida después (≈20 cm/s) indica que casi no se mueve: «girar hacia el enemigo y disparar desde una posición fija».

18:55
CorrecciónLectura estadística de las comparaciones

Con intervalos de confianza, «supera ligeramente en daño y bajas» no se sostiene para la corrida 12. Lo correcto: no se distingue del BT y sobrevive claramente más.

17:04–22:52
ExperimentoRéplica independiente (semilla distinta) y su etapa 2

Mismo patrón: sobrevive 35 a 38 s más; en 3v3 y 5v5 supera al BT en daño con intervalo que excluye 0. La etapa 2 se replica.

23:12–23:40
HallazgoUn controlador de tres reglas hecho a mano rinde igual

Reflejo con retroceso (id 9002), 3v3: daño 101.8 contra 71.8 del BT; bajas 0.82 contra 0.33; vive 60 contra 15 s. La evolución redescubre la táctica; no hay evidencia de que la mejore.

9 de octubre (madrugada)
~00:35
HallazgoEl NEAT veía al enemigo a través de paredes

Verificado en el código de la observación: dirección, distancia, ángulo y error de apuntado apuntan al enemigo vivo más cercano a 5000 uu, sin exigir línea de visión y en 360°. Amenaza a la validez de la comparación. El responsable ordenó pausar todo (00:45).

~01:15–01:45
HallazgoPercepción y búsqueda del BT, leídas en el editor

El BT percibe a ≤2500 uu en un cono de 120° y sin memoria (bIncludeKnownActors=False). Su mira se ajusta de forma instantánea al objetivo percibido (verificado en el código del motor). Cuando no percibe a nadie, navega hacia el enemigo vivo más cercano de todo el mapa (EQS_GetAllEnemy, sin filtro de visión).

01:48
ConstrucciónCondición de percepción por vista

Compilación en 13 s. Prueba de humo con el reflejo con retroceso: con la observación original, 205 de 498 muestras (41 %) tenían enemigo sin línea de visión; con el filtro, 0 de 495.

01:55
DecisiónProtocolo escrito y cola nocturna lanzada

FAIR_PROTOCOL.md fija las condiciones, los pasos y qué se reporta antes de ver resultados. Pasos: medir los campeones actuales con el filtro, entrenar con semilla (60 generaciones) y entrenar sin semilla (100 generaciones). Estimado de término: alrededor de las 11:30.

02:00–02:39
ExperimentoMedición de los seis campeones con percepción por vista

Sin reentrenar. El daño relativo de todos baja entre 70 y 140 puntos; la supervivencia se mantiene en los que retroceden.

02:39–05:11
ExperimentoEntrenamiento con semilla bajo percepción por vista (60 generaciones)

Sin fallos. Campeón 3332: pierde 16 de 20 partidas 3v3 contra el BT.

05:11–09:20
ExperimentoEntrenamiento sin semilla bajo percepción por vista (100 generaciones)

La población no aprende: la compuerta de fitness cae a 0.03–0.08. Campeón 5598: pierde 16 de 20 en 3v3 y todas las de 5v5 y 10v2.

09:20–12:05
DecisiónAnálisis de los resultados por vista y protocolo del reentrenamiento

Lectura de los resultados, diagnóstico de hipótesis, plan de reentrenamiento y protocolo escrito antes de ver datos. En ese lapso no corrieron experimentos.

12:37–13:22
ConstrucciónMedición de velocidad compilada y aplicada a nueve controladores

El bot NEAT casi no se mueve en los reflejos y campeones de etapa 2 (≈12 a 68 cm/s); el BT se mueve a ≈400 cm/s. Prueba de humo con 2 partidas y medición con 10 partidas por controlador.

13:22–16:40
ExperimentoE1: etapa 2 (40 generaciones, 5 NEAT + 5 BT) sobre la línea por vista con semilla

Sin fallos. El campeón (5593) pierde 11 de 20 partidas 3v3, con Δ de daño −84.8. No mejora de forma clara respecto a la línea anterior (−89.7).

16:40–en curso
ExperimentoE2: réplica de la línea por vista con semilla (semilla 31, 60 generaciones)

En marcha: 32 de 60 generaciones a las 18:01, sin fallos. Sigue su etapa 2 (E2b) y después los entrenamientos contra el BT desde la generación 0 (E3 a E5). Estimado: E2 termina hacia las 19:15; E2b hacia las 23:10; E3 de las 23:10 a las 07:00 del sábado.

17:14
ConstrucciónPrueba de equivalencia entre la red de C++ y la de Python

Aprobada: 140 vectores de entrada, error máximo de 1.4·10⁻¹⁵ con tolerancia de 10⁻⁵.

12:17
HallazgoLa búsqueda simple a mano no mejora el resultado

El reflejo con retroceso y giro de búsqueda (9003) pierde 17 de 20 partidas y hace menos daño que sin búsqueda. La predicción se había anotado antes de correrlo.

Cómo leer la serie: desarrollo y condiciones

Los experimentos se pueden leer en dos etapas. La serie no se planeó como un diseño factorial desde el primer día; surgió de ir descubriendo qué variables importaban. Cada condición nueva se anotó con su motivo en el momento en que apareció, y eso es lo que permite compararlas ahora.

CondiciónInformación del enemigoInicializaciónEntrena contraCorridasEstado
C1Completa: ≤5000 uu, 360°, sin línea de visiónSemilla de reflejo10 NEAT + 2 BTrun12 (100), rep1 (60)Hecha
C2CompletaPoblación mínima aleatoria10 NEAT + 2 BTcontrol_sinsemilla (100)Hecha, n=1
C3CompletaContinúa C15 NEAT + 5 BT (etapa 2)run13_etapa2, rep1_etapa2 (40 c/u)Hecha
C4Por vista: línea de visión, ±60°, ≤2500 uuSemilla de reflejo10 NEAT + 2 BTfair_seed (60)Hecha
C5Por vistaPoblación mínima aleatoria10 NEAT + 2 BTfair_sinsemilla (100)Hecha
MPor vista, sin reentrenarCampeones de C1 a C3—fair_medicionHecha
Ref.Completa y por vistaControladores a mano 9001 y 9002—comparaciones directasHecha en ambas

En C4 y C5 cambia el significado de dos términos del fitness: Fapunta y Fcerca solo cuentan con enemigo percibido. El resto de la fórmula es idéntico.

Ventajas y desventajas del NEAT frente al BT, por condición

AspectoNEAT, información completa (C1–C3)NEAT, por vista (C4, C5)BT de Lyra
Ve al enemigo a través de paredes para apuntarSí ventajaNoNo
Ángulo de visión360° (ventaja)120°120°
Alcance5000 uu2500 uu2500 uu
Conoce dónde está el enemigo para buscarloSí, hasta 5000 uuNo desventajaSí todo el mapa
Mira≤180°/s, sin giro vertical (desventaja)IgualInstantánea, con giro vertical
Navegación, recarga, búsqueda de armasNo (desventaja)NoSí
Frecuencia de decisión10 Hz10 HzObjetivo cada ≈0.5 s
Memoria del enemigoNoNoSolo percibidos en ese momento

Con información completa, el NEAT tenía ventajas de información y desventajas de ejecución. Con percepción por vista pierde la ventaja de información y conserva las desventajas de ejecución, más la de búsqueda. Comparar C1 con C4 mide cuánto valía esa información. Es un resultado, no solo una corrección.

Qué significa para los jugadores humanos

Inventario de corridas

Se conservan todas, también las descartadas, porque cada una explica una decisión.

CorridaGen.Qué probabaEstado
piloto, run2, run34 · 2 · 15Primeras corridas en UnrealDescartadas incluían el calentamiento de 30 s
run57Fiabilidad de la señal (K=2)Conservada motivó K=4, población 60
run6122Sin semilla, pitch activo, mutación por defectoSin aprendizaje causa técnica identificada después
run7, run83 · 28Semilla mínima; y especiación 1.5Detenidas
run9, run10, run113 · 19 · 28Semilla de reflejo; pesos 0.2/0.2Descartadas / detenidas error de sesgos (9); sustituidas por la configuración v2 tras el A/B de pitch (10, 11)
run12100Configuración v2, con semilla de reflejoVálida
control_sinsemilla100Misma configuración, población mínima aleatoriaVálida (g60 archivada)
run13_etapa240Continuación de run12, 5 NEAT + 5 BTVálida
rep1, rep1_etapa260 · 40Réplica con otra semilla aleatoria y su etapa 2Válidas
control_replica20 de 100Réplica del control sin semillaPausada diseño con visión completa
fair_seed, fair_sinsemilla60 · 100Percepción por vista, con y sin semillaVálidas terminaron el 9 de oct
demo_mock40Simulador falso para probar el cicloSolo pruebas

Experimentos de validación del método

Estas pruebas no buscan un mejor controlador, sino saber si las medidas del proyecto se pueden creer.

Calibración del fitness con controladores de referencia

Pregunta
¿El fitness ordena a un buen combatiente por encima de uno que no hace nada o actúa al azar?
Qué se hizo
40 episodios por referencia en el entorno de entrenamiento y 24 del BT. Rejilla de pesos 0 a 0.5 para tiempo vivo y daño recibido.
Resultado
Aleatoria 0.145, inerte 0.15, reflejo a mano 0.81, BT 1.54. El reflejo supera al inerte y al aleatorio por más de 2 errores estándar en las 25 celdas de pesos.
Decisión
Fitness congelado: Dd + 0.2·Ts − 0.2·Dr + λ(g)·(0.05·Fcerca + 4.0·Fapunta).

Auditoría de la medición

Pregunta
¿Las partidas rápidas (×3) y la bandera de medición del BT alteran los resultados?
Qué se hizo
40 episodios por condición con el mismo controlador, incluida una réplica idéntica para medir el ruido de base.
Resultado
Daño, bajas y tiempo vivo sin diferencia significativa entre ×1 y ×3. La puntería baja de 0.96 a 0.88 a ×3 (z=4.9). Entre repeticiones idénticas el tiempo vivo varía entre 15 y 22 s.
Decisión
Entrenar a ×3 y evaluar a ×1; no interpretar diferencias menores que el ruido de base.

Giro vertical y mutaciones (A/B pre-registrado)

Pregunta
¿Por qué los controladores competentes dejaban de serlo al mutar?
Qué se hizo
Mismos genomas en tres brazos (actual, pitch anulado, pitch recentrado), 10 genomas por conjunto, K=4. Reglas de decisión escritas antes.
Resultado
Reflejo con tres mutaciones suaves: competentes 0 de 10 con pitch actual contra 6 de 10 con pitch anulado. Población aleatoria: puntería 0.054 → 0.249. Con pitch anulado, mutación suave conserva 5–6 de 10; la estándar, 1–4 de 10. Reflejo sin mutar: 8–9 de 10 en todos los brazos.
Decisión
Pitch anulado y mutación suave. El mecanismo de la deriva es inferido, no medido directamente.

Fiabilidad y especiación

Fiabilidad
Correlación entre episodios del mismo genoma: 0.09 (corrida 5, K=2) → 0.53 a 0.72 por episodio en la corrida 12 (≈0.9 con K=4). Depende de cuán distinta es la población, no solo del método de medición.
Especiación
Con umbral 3.0 hubo una sola especie en 122 generaciones. Con 1.5, la población final de la corrida 12 tuvo 2 especies; con 0.4, habría 5, y con 0.3, 10. La variante 0.35 estaba en la cola y sigue pendiente.

Resultados contra el BT

Partidas nuevas, a velocidad ×1, solo la primera vida de cada bot, media por bot. «Gana» es el equipo con más bajas por bot en la partida. Intervalo de Wilson al 95 %; diferencias por partida con remuestreo (bootstrap).

3 contra 3, 90 s, 20 partidas

ControladorDaño NEAT / BTBajas NEAT / BTVivo (s) NEAT / BT
run12 g60 (3330), con semilla91 / 1080.70 / 0.6527 / 18
run12 g100 (5582)83 / 930.65 / 0.4741 / 17
Control sin semilla g60 (3335)49 / 1730.23 / 1.2816 / 21
Control sin semilla g100 (5587)89 / 1000.72 / 0.4839 / 18
Etapa 2 de run12 (7837)101 / 870.68 / 0.5047 / 17
Réplica 1 g60 (3451)100 / 830.55 / 0.4751 / 15
Etapa 2 de réplica 1 (5768)101 / 790.62 / 0.3853 / 15
Reflejo a mano (9001)100 / 1070.63 / 0.7210 / 15
Reflejo con retroceso a mano (9002)102 / 720.82 / 0.3360 / 15

Estadística de los campeones principales

ComparaciónV/E/DTasa (Wilson)Δ daño (IC 95 %)Δ bajas (IC 95 %)Δ vivo, s (IC 95 %)
Etapa 2 run12, 3v310/4/60.62 (0.39–0.82)+14.7 (−2.9, +34.0)+0.18 (−0.18, +0.55)+30.1 (+22.7, +38.0)
Etapa 2 run12, 5v57/1/40.64 (0.35–0.85)+6.6 (−28.0, +36.5)+0.27 (−0.10, +0.62)+32.0 (+26.0, +38.4)
Etapa 2 réplica 1, 3v310/7/30.77 (0.50–0.92)+21.9 (+3.0, +40.8)+0.23 (−0.00, +0.47)+37.5 (+28.9, +46.0)
Etapa 2 réplica 1, 5v58/2/20.80 (0.49–0.94)+30.9 (+15.4, +45.5)+0.38 (+0.15, +0.63)+35.5 (+31.3, +39.7)
Control sin semilla g100, 3v313/2/50.72 (0.49–0.88)−10.9 (−38.0, +14.6)+0.23 (−0.08, +0.53)+20.9 (+12.4, +29.5)
Control sin semilla g60, 3v31/2/170.06 (0.01–0.26)−123.3 (−158.1, −90.3)−1.05 (−1.35, −0.75)−4.5 (−8.4, −0.3)

Un intervalo que no contiene 0 es evidencia de diferencia entre el NEAT y el BT en esa métrica. Con 12 a 20 partidas por comparación y sin corrección por comparaciones múltiples, ninguna tasa de victoria es significativamente distinta de 50 %. Los intervalos del reflejo con retroceso (9002) están pendientes de calcular.

Lo que muestran las métricas extra

El BT de Lyra, tal como se verificó en los activos

Tres ramas bajo un selector: sin munición, busca arma (EQS_FindWeapon); con munición y objetivo percibido, «Shoot And Move» (fija el foco en el objetivo, dispara cada ≈0.1 s y se mueve a una posición de EQS_MoveAgainstEnnemy con strafe); con munición y sin objetivo, recarga, busca arma y busca al jugador. El objetivo lo escribe un servicio que corre EQS_AIPerceptionEnemy. Las decisiones son ramas discretas, pero las acciones son continuas. Referencia humana: 33 sesiones (106.5 min, 247 vidas), el BT ganó 28; bajas y muertes por sesión 2.9 y 6.8.

Resultados bajo percepción por vista

Mismos escenarios y mismo protocolo de evaluación que antes (a ×1, partidas nuevas, 3v3 de 90 s con 20 partidas). Solo cambia lo que recibe el NEAT: el enemigo existe únicamente si hay línea de visión, está dentro de ±60° y a ≤2500 uu. Δ = NEAT menos BT, en promedio por bot.

Los mismos campeones, con visión completa y por vista

ControladorΔ daño, visión completaΔ daño, por vista (IC 95 %)V/E/D por vistaΔ vivo (s): completa → vista
Etapa 2 de run12 (7837)+14.7−92.5 (−121.3, −66.2)2/4/14+30.1 → +27.6
Etapa 2 de réplica 1 (5768)+21.9−83.0 (−109.0, −58.6)1/4/15+37.5 → +26.4
run12 g100 (5582)−9.9−81.9 (−115.7, −48.0)3/6/11+24.1 → +27.9
Control sin semilla g100 (5587)−10.9−152.6 (−175.2, −130.5)1/1/18+20.9 → +12.8
Reflejo a mano (9001)−6.5−126.9 (−160.2, −93.5)0/3/17−4.6 → +1.0
Reflejo con retroceso a mano (9002)+30.0 (+16.5, +43.5); 13/5/2−98.2 (−129.3, −66.8)1/3/16+45.4 → +25.5

Con visión completa, el reflejo con retroceso hecho a mano ganó 13 de 20 partidas al BT (tasa 0.87, Wilson 0.62–0.96), el único resultado de toda la serie con significancia sobre 50 %. Por vista pierde 16 de 20. Para los seis controladores, quitar la información privilegiada reduce el daño relativo en 70 a 140 puntos. El tiempo vivo se mantiene en los que casi no se mueven (+25 a +28 s).

Controladores entrenados bajo percepción por vista

CorridaEscenarioV/E/DΔ daño (IC 95 %)Δ bajas (IC 95 %)Δ vivo, s (IC 95 %)
Con semilla, 60 gen (3332)3v31/3/16−89.7 (−116.5, −63.9)−0.68 (−0.97, −0.40)−2.3 (−6.3, +1.9)
5v53/1/8−49.2 (−82.4, −17.0)−0.37 (−0.70, −0.07)+0.7 (−3.2, +5.1)
10v26/1/5−36.4 (−66.3, −2.4)−0.06 (−0.35, +0.21)+12.4 (+7.4, +17.0)
Etapa 2 de la anterior, 40 gen (5593)3v34/5/11−84.8 (−116.5, −53.7)−0.40 (−0.72, −0.08)+7.7 (+1.6, +14.3)
5v53/2/7−96.5 (−138.8, −54.6)−0.37 (−0.83, +0.05)−2.5 (−6.7, +1.9)
10v22/1/9−109.9 (−173.3, −53.6)−0.52 (−0.94, −0.10)+5.4 (−2.7, +13.2)
Sin semilla, 100 gen (5598)3v30/4/16−199.8 (−246.9, −150.6)−1.37 (−1.75, −0.98)−12.2 (−22.0, −4.4)
5v50/0/12−188.7 (−221.1, −151.2)−1.35 (−1.62, −1.07)−2.4 (−7.5, +2.8)
10v20/0/12−472.2 (−591.0, −345.4)−3.38 (−4.43, −2.24)−2.6 (−10.4, +5.8)

Qué muestran los registros de entrenamiento

Fracción de ticks en que el NEAT tenía un enemigo percibido con línea de visión, alineación media de la mira y daño por episodio en el entorno de entrenamiento (10 NEAT + 2 BT). Se compara el inicio (generaciones 0 a 2) con las últimas 10.

CorridaVisión (inicio → final)Alineación de miraDaño por episodioPrecisión de disparo
run12, visión completa, con semilla0.55 → 0.520.81 → 0.8374 → 800.033 → 0.045
Control, visión completa, sin semilla0.58 → 0.520.28 → 0.6714 → 560.007 → 0.029
Por vista, con semilla0.34 → 0.330.28 → 0.2867 → 830.026 → 0.034
Por vista, sin semilla0.18 → 0.230.11 → 0.1611 → 190.005 → 0.006

Control de diagnóstico: reflejo con búsqueda (9003)

Para comprobar si la falta de búsqueda explica el resultado, se escribió a mano una variante del reflejo con retroceso que gira de forma constante cuando no ve al enemigo (sesgo de giro +0.3 que se anula al percibirlo). La predicción, anotada antes de correrla: si la búsqueda fuera el cuello de botella, mejoraría de forma clara sobre 9002. Resultado por vista en 3v3 (20 partidas): daño 22.1 contra 162.2 del BT, bajas 0.13 contra 0.90, vivo 40.9 contra 23.4 s; Δ daño −140.1 (−164.4, −116.0), victorias 2/1/17. Es peor que 9002 (−98.2). La predicción no se cumple con una búsqueda tan simple. No descarta otras formas de búsqueda ni de memoria.

Velocidad medida

Velocidad horizontal media de los bots mientras viven (cm/s, tope de caminar 600), en las partidas 3v3 de 90 s con percepción por vista, 10 partidas por controlador (30 bots NEAT y 30 BT).

ControladorNEAT (cm/s)BT en esas partidas (cm/s)Tiempo vivo NEAT (s)Daño NEAT
Reflejo con retroceso a mano (9002)12.3405.647.246.8
Etapa 2 de réplica 1 (5768)17.4402.842.527.3
Etapa 2 de run12 (7837)19.9403.344.146.2
run12 g100 (5582)67.6390.254.661.8
Reflejo a mano (9001)98.8402.730.454.0
Reflejo con búsqueda a mano (9003)271.4390.950.214.1
Entrenado por vista, con semilla (3332)338.1408.919.274.4
Control sin semilla g100 (5587)525.9404.841.213.2
Entrenado por vista, sin semilla (5598)575.7491.448.97.8

Lo que implica: la táctica que la evolución y el reflejo a mano comparten se parece a una torreta: apuntar y disparar sin desplazarse. Contra ella, el BT se acerca con mira instantánea. Si un jugador humano percibiría ese comportamiento como campear o como parte natural del combate no está medido.

Qué pudo haber pasado

HipótesisEvidenciaEstado
Error de programación del filtro de visiónLa prueba de humo dio 0 de 495 muestras con enemigo sin visión (con la observación original, 205 de 498). El cono usa el coseno del ángulo, y el motor lo aplica como semiángulo (AISense_Sight.cpp), igual que el BT. Las corridas por vista se comportan distinto (visión 0.33 contra 0.52).Improbable, no demostrado falta comparar el trazo de visión del NEAT con el del BT
Error en las corridas o en la infraestructura0 fallos y 0 trazas de error en el entrenamiento; mismo protocolo y mismos escenarios; las mediciones de los campeones previos se hicieron en la misma cola.Sin indicios
Diseño: sin búsqueda ni memoriaEl NEAT ve al enemigo un tercio del tiempo y no mejora eso en 60 a 100 generaciones. El control con búsqueda a mano (9003) rinde peor que sin ella.Contribuye, no basta
Ventajas del BTMira instantánea hacia el objetivo percibido y navegación hacia el enemigo más cercano del mapa. El daño que hace el BT sube cuando el NEAT pierde la información (por ejemplo, de 87 a 136 contra el campeón 7837).Probable contribuyente grande no separable sin otro experimento
Entorno de entrenamiento no presiona contra el BTLa línea por vista con semilla iguala en el entrenamiento a la de visión completa (daño 83 contra 80) pero queda lejos del BT. La etapa 2 (5 NEAT + 5 BT) fue la que mejoró contra el BT con visión completa, y se aplicó después a la línea por vista (E1, 40 generaciones): el Δ de daño en 3v3 pasó de −89.7 a −84.8, con intervalos que se solapan; en 5v5 y 10v2 quedó peor. La velocidad media del campeón de E1 es de 342 cm/s (BT 412).No respaldada con 40 generaciones la etapa 2 por vista no mejoró
Presupuesto e hiperparámetros60 a 100 generaciones, población 60, mutación suave calibrada con visión completa, fitness no recalibrado, una corrida por línea.No descartable
«NEAT no sirve para este proyecto»Con visión completa evolucionó un controlador que empata al BT en 3v3, aunque uno de tres reglas escrito a mano lo supera. Con percepción por vista no alcanza a un BT que ganó 28 de 33 sesiones a jugadores humanos y que tiene ventajas de mira y de navegación.Sin base lo observado no permite concluirlo

Lo que sí se puede decir: con esta observación, estas salidas, este presupuesto y percepción comparable a la humana, la evolución no produjo un controlador que alcance al BT de Lyra. Eso es un resultado negativo documentado con una condición de partida (visión completa) que explica por qué antes parecía lo contrario.

Qué cambia en las conclusiones

Correcciones registradas

Estas son afirmaciones hechas durante el trabajo y retiradas o ajustadas cuando los datos las contradijeron. Se dejan por escrito porque forman parte del método.

Afirmación inicialQué se encontró
El mapa no tiene fase PlayingSí la tiene: Warmup de ≈30 s y luego Playing. Varias corridas tempranas estaban contaminadas.
El fitness premia esconderse (n=2, luego n=20)La calibración con 40 episodios lo desmintió. El orden de referencias es correcto con cualquier peso de 0 a 0.5.
Ver al enemigo equivale a recibir dañoNo es un argumento válido: la correlación observada depende de cómo dispara el BT.
La fiabilidad subió de 0.09 a 0.7, la medición mejoróEngañoso: depende de la diversidad de la población.
Con el umbral calibrado habrá ≈10 especiesEn las corridas finales hubo 2.
La etapa 2 supera ligeramente al BT en daño y bajasNo se sostiene con los intervalos para run12. Sí en la réplica, en 3v3 y 5v5.
Se usó el plugin Learning AgentsNo se usó. Se analizó y se implementó una integración nativa por archivos. La solicitud lo prometía; se declara como desviación.
Mismos escenarios y posiciones de inicio para todos los genomasNo implementado: solo el identificador de escenario es común. Es una fuente de ruido.
Hipótesis de mutaciones agresivas como única causaEstaba confundida con la deriva del giro vertical; las causas eran dos.
El campeón retrocede a media velocidad (por su sesgo de avance de −0.21)La velocidad medida es de 12 a 68 cm/s en los reflejos y campeones de etapa 2 (BT ≈400). Casi no se mueven. La causa no está confirmada; no se registran posiciones.
La comparación contra el BT es en igualdad de informaciónNo: el NEAT tenía visión a través de paredes (corregido, ver la siguiente sección).

Validez y asimetrías

Todos los resultados de las secciones anteriores son con información completa del enemigo. Dirección, distancia y ángulo apuntaban al enemigo vivo más cercano a ≤5000 uu, incluso detrás de paredes y a 360°. Sirven como una condición de comparación, no como una comparación en igualdad de capacidades con el BT.

La condición nueva, «percepción por vista», entrega al NEAT el enemigo más cercano que cumpla tres cosas: línea de visión, ángulo horizontal de ±60° y distancia ≤2500 uu, los valores del BT. No tiene memoria, igual que el BT. La prueba de humo mostró 0 de 495 muestras con enemigo sin visión. La condición cambia el significado de dos términos del fitness: Fapunta y Fcerca solo cuentan con enemigo percibido.

Asimetrías que permanecen, por orden de importancia:

Otras limitaciones: una sola corrida por condición sin semilla y dos con semilla, de modo que no hay variabilidad estimable; el campeón se elige entre 10 candidatos (maldición del ganador, parcialmente corregida con escenarios nuevos); la cobertura no está validada; el fitness no se recalibró bajo la nueva condición.

Solicitud contra realidad

Lo prometidoLo realizadoEstado
Controlador NEAT que reemplace al BT en NPCs de LyraControlador nativo en C++ integrado a Lyra y evaluado contra el BTHecho
Puente C++/Python con Learning AgentsIntegración propia por archivos JSON, evaluación por lotesDesviación
Sensores: distancia al enemigo, salud, línea de visión13 entradas que incluyen esas tres; en la condición nueva, restringidas a lo visibleHecho
Población de 50 a 100 genomas60Hecho
Fitness: premiar daño y supervivencia, penalizar daño recibidoEsa forma, más una ayuda de puntería que se apaga en la generación 30Hecho con cambios
Benchmark: tasa de victoria, precisión, uso de coberturaTasa de victoria con intervalo, precisión medida, cobertura medida con definición operacionalParcial
Población inicial de redes mínimas con pesos aleatorios (cartel del congreso)La línea sin semilla; los mejores resultados usan una semilla de reflejoDeclarar
Sesiones nuevas contra jugadores humanos (cartel del congreso)No realizadasPendiente
Reporte de máximo 4 cuartillas y cartel de 90 × 120 cmBorrador de cartel; este documento es la fuente para el reporteEn curso

Qué sigue

La cola night4.py terminó el 9 de oct a las 09:20 sin fallos. Quedan abiertas pruebas que separan las explicaciones de la sección anterior. La cola night5.py las ejecuta en orden desde las 12:50; han terminado el control 9003, la medición de velocidad y E1; corre ahora E2 (réplica de la línea por vista con semilla).

PruebaQué separaCosto aprox.
Etapa 2 (40 generaciones, 5 NEAT + 5 BT) sobre la línea por vista con semilla (terminó a las 16:40: sin mejora clara, ver «Resultados bajo percepción por vista»)Si la falta de presión contra el BT en el entrenamiento explica la caída≈3.7 h
Réplicas de las líneas por vista con otras semillasCuánta variación hay entre corridas (hoy n=1 por línea)2–5 h cada una
Medir la velocidad real de jugadores humanos (la columna speed ya está compilada)Si la referencia humana tiene una ventaja de movimiento1 h de partidas
Partidas jugadas por una persona contra los campeonesSi la táctica de retroceso funciona contra humanos1–2 h del jugador

Criterio de reporte fijado antes de ver datos: se reportan todos los resultados, sean buenos o malos, sin umbral de éxito.

Decisiones que requieren la opinión de los doctores

Archivos de respaldo

Participación de los alumnos inscritos en la solicitud: Leslie Benitez y Noé de Luna, por documentar por el responsable.