En el laboratorio, la voz llama a un personaje y las pantallas le hacen lugar. Aether presenta el arte; Medium coordina la conversación y la escena. Este capítulo abre esa ilusión para ver sus piezas.
Manual basado en el código revisado el 17 septiembre 2026. Los controles de esta página son una simulación local: no escuchan, no consultan una IA y no cambian ninguna pantalla física.
00 / ESTRUCTURAS DE DATOS I
No muevas el objeto. Cambiá cómo llegás a él.
Una pantalla te muestra el resultado. Una estructura de datos explica cómo lo organizaste. Primero construimos el modelo con punteros en C; después lo contrastamos con el planificador de escenas de Aether.
Al terminar esta clase
Vas a poder seguir una dirección, insertar y retirar un nodo sin perder la lista, recorrer un anillo sin quedar en un bucle infinito y distinguir una pila LIFO de una cola FIFO.
El nodo guarda dos cosas.
typedef struct Node {
const char *value; // el dato
struct Node *next; // dirección del siguiente
} Node;
Node a = {"Eleven", NULL};
Node b = {"Maniac", NULL};
a.next = &b;
Node *head = &a;
&b obtiene la dirección de b. head guarda una dirección, no una copia de a. head->next equivale a (*head).next: desreferenciar el puntero y acceder al campo.
Dirección no es posición.
Un arreglo permite pedir el elemento i. En una lista simplemente enlazada empezás en head y seguís next, una referencia a la vez. Sus nodos pueden estar separados en memoria.
Un Node * señala un nodo. Un Node ** señala una variable que guarda ese puntero: sirve cuando una función necesita cambiar el head o top del llamador.
Las direcciones del tablero son etiquetas ficticias. No son direcciones reales de tus servidores ni resultados de malloc.
MEMORIA / REFERENCIAS / OPERACIÓNMODELO C · PASO A PASO
Insertar: el orden importa.
// A → B → C. Insertar D después de B.
fresh->next = prev->next; // D → C
prev->next = fresh; // B → D
// A → B → D → C
Si primero hacés prev->next = fresh y luego copiás ese mismo enlace a fresh->next, D termina apuntándose a sí mismo. Perdiste desde la lista el camino hacia C. Guardar y conectar son pasos distintos.
Desenlazar modifica la estructura. free devuelve una reserva al allocator; no borra otras referencias al objeto. Este ejemplo requiere un nodo propio creado con malloc. No se usa con los nodos automáticos a y b del primer fragmento.
¿Por qué un anillo usa do/while y no espera NULL?
if (head != NULL) {
Node *cursor = head;
do {
puts(cursor->value);
cursor = cursor->next;
} while (cursor != head);
}
Precondición: el anillo está bien formado y no cambia durante el recorrido. Primero visitás y avanzás; después comprobás si regresaste al punto inicial. Empezar con while (cursor != head) haría cero visitas.
01 / DEL MODELO C AL ESCENARIO REAL
El centro es un lugar. No una persona.
Las posiciones permanecen. Sus ocupantes rotan. Si llamás a alguien que ya está, recorre el camino más corto hasta el centro. Si no está, sustituye al ocupante de la posición 2 y después rota hasta el centro.
THREE DISPLAYS / ONE FOCUSSIMULACIÓN
0 / IZQUIERDA
11Eleven
1 / CENTRO
MManiac
2 / ENTRADA
NNagini
0→1→2↶ 0
LLAMAR A
ESTADO INICIAL
Maniac ocupa el centro.
Probá Eleven para rotar a alguien presente, o Luna para ver una llegada en dos pasos.
[eleven, maniac, nagini]
El dibujo: una lista circular.
Podés imaginar cada posición enlazada con la siguiente: 0 → 1 → 2 → 0. Una lista enlazada real guardaría referencias entre nodos. Aquí las flechas describen la relación circular, no punteros implementados.
El código: una lista de Python.
plan() ordena las posiciones de un diccionario, crea una secuencia y usa slices para rotarla. Luego devuelve un nuevo diccionario y las posiciones que cambiaron. No modifica el anillo de entrada.
Abrir el fragmento real · llegada de una nueva persona
seq2 = seq[:]
seq2[-1] = persona
k = (n - 1 - c) % n
new = seq2[k:] + seq2[:k]
seq[:] copia; [-1] selecciona la última posición; c es el índice del centro. Para tres pantallas y centro 1, k = 1: el primer elemento pasa al final. El código reemplaza un valor: no hace un pop() literal.
El comentario dice “pop”; la operación es reemplazar.
En el código revisado, seq2[-1] = persona sobrescribe una posición de una copia. No elimina un elemento ni reduce la longitud de la lista. “Última pantalla” significa índice, no última llegada. La asignación no es un free() de C: Python administra sus referencias.
Empezá con [eleven, maniac, nagini].
Llamá a Luna: sale Nagini y queda [maniac, luna, eleven].
Sin reiniciar, llamá a Lilith: sale Eleven y queda [luna, lilith, maniac].
Luna era la llegada más reciente y no salió. Eso basta como contraejemplo de LIFO. Tampoco hay una cola de antigüedad que elija la expulsión: una rotación por llamar a alguien presente puede cambiar quién queda en la posición de entrada.
La cuenta modular del movimiento, con tres posiciones
n = 3 // cantidad de pantallas
c = 1 // centro fijo
i = 0 // Eleven está a la izquierda
izquierda = (i - c + n) % n = 2
derecha = (c - i + n) % n = 1
[eleven, maniac, nagini]
→ rotar 1 a la derecha
[nagini, eleven, maniac]
El + n evita un resto negativo en C/JavaScript para estos índices. Python ya produce un módulo no negativo cuando n es positivo. En empate, el planificador elige la izquierda. Buscar la persona y construir los slices cuesta O(n): dibujar un círculo no vuelve O(1) este algoritmo.
02 / DOS REGLAS PARA SACAR ALGO
¿Sale el último? ¿O el primero?
“Pila” sirve como intuición para hacer espacio. Pero la política del escenario es reemplazo por posición + rotación. Comparemos eso con las estructuras estrictas, sin confundirlas.
MISMAS ENTRADAS: A → B → CEJEMPLO DIDÁCTICO
PILA / LIFO
Último en entrar, primero en salir.
Base a la izquierda · tope a la derecha.
COLA / FIFO
Primero en entrar, primero en salir.
Frente a la izquierda · final a la derecha.
Mismo orden de llegada. Distinto orden de salida.
En el historial de escena sí aparece una idea FIFO: se agregan eventos y se conservan los 50 más recientes, descartando los más viejos. Eso es retención acotada de un registro, no una cola que decide quién ocupa las pantallas.
Una pila enlazada, en C.
// push: fresh ya fue reservado.
fresh->next = *top;
*top = fresh;
// pop: comprobar antes que *top != NULL.
Node *old = *top;
*out = old->value;
*top = old->next;
free(old);
Entradas A, B, C → salidas C, B, A. Un extremo, un puntero al tope. Pasar &top permite que la función actualice la variable del llamador.
Una cola enlazada, en C.
// enqueue: fresh->next ya es NULL.
if (q->rear) q->rear->next = fresh;
else q->front = fresh;
q->rear = fresh;
// dequeue: comprobar front != NULL.
Node *old = q->front;
*out = old->value;
q->front = old->next;
if (!q->front) q->rear = NULL;
free(old);
Entradas A, B, C → salidas A, B, C. Agregás por el final y extraés por el frente. Al vaciarla, ambos punteros deben quedar en NULL.
La estructura y la política no son lo mismo.
Modelo
Qué decide la salida
Costo relevante
Lista simplemente enlazada
La operación elegida, no la lista por sí misma.
Buscar O(n); insertar/desenlazar O(1) si ya tenés el predecesor.
Pila / LIFO
Último en entrar.
Push/pop O(1) con top.
Cola / FIFO
Primero en entrar.
Enqueue/dequeue O(1) con front y rear.
Anillo enlazado simple
El anillo no define una expulsión.
Avanzar head O(1); buscar o encontrar el anterior O(n).
Escena de Aether
Última posición del arreglo, luego rotación.
Buscar y copiar la secuencia O(n).
Historial acotado
Conserva los 50 eventos más recientes.
Append + slice; no es una cola consumida por trabajadores.
“Pop” es el nombre de una operación de extracción en muchas APIs; no demuestra LIFO por sí solo. Una deque permite ambos extremos. LRU expulsa lo menos recientemente usado: sería otra política, no la que implementa esta escena.
LABORATORIO / LEER → PREDECIR → EJECUTAR
Ahora sí: seguí los punteros.
El archivo completo implementa nodos, inserción, borrado, pila y cola con manejo de vacío y de fallos de reserva. Es un programa didáctico independiente: no se conecta al laboratorio ni reemplaza el planificador.
cc -std=c11 -Wall -Wextra -Wpedantic \
CODEX-structures.c -o structures
./structures
Los strings son literales prestados; se liberan los nodos de heap, no los strings ni los nodos automáticos del anillo. Cada estructura tiene sus propios nodos. No comparte su propiedad con otra.
01 · Si cursor = head, ¿hay dos nodos o dos punteros?
Dos variables de puntero que contienen la misma dirección. Cambiar cursor no cambia head; escribir cursor->value sí modifica el nodo que ambos pueden alcanzar.
02 · ¿Qué se rompe si libero victim antes de leer victim->next?
Accedés a memoria cuya vida útil terminó: use-after-free, comportamiento indefinido. Conservá el enlace o reconectá antes de liberar.
03 · A, B, C entran. Sale C. ¿Ya probaste que es una pila?
No con una sola observación. La política debe exigir que salga el último en cada operación. Puede haber prioridades, extracción arbitraria o, como acá, un índice de pantalla.
04 · Llamo a Eleven y después a Luna desde el estado inicial. ¿Quién sale?
Eleven rota al centro: [nagini, eleven, maniac]. Al llegar Luna, se reemplaza a Maniac en la posición 2 y queda [eleven, luna, nagini]. No estamos consultando un registro de antigüedad.
03 / DE VOZ A LUZ
La misma presencia. Dos relojes distintos.
El laboratorio no muestra tokens mientras el modelo responde. La versión web puede hacerlo; por eso sus disparadores visuales son diferentes.
CASA / COMPORTAMIENTO REVISADO
EscucharMicrófono → Whisper → nombre detectado.
Hacer lugarEl plan de escena lleva a la persona al centro.
Esperar la respuesta completaLa consulta al modelo usa stream: false.
Voz + movimientoSíntesis y reproducción; /play acompaña la voz, /stop cierra el movimiento.
WEB / TRADUCCIÓN PROPUESTA
Elegir y escribirUna persona del catálogo y un mensaje.
Hacer lugarLa misma regla de escena, dibujada en el navegador.
Primer token de contenidoEmpieza el clip al llegar texto, no un evento vacío de conexión.
Cerrar en todas las salidasFin, error, cancelación o desconexión: pausar el video y volver al poster. Audio en una segunda fase.
Pendiente de conexión: sesión autenticada y proxy de servidor, catálogo autorizado de personas, posters y clips optimizados. Ninguna llave de infraestructura ni prompt privado debe viajar al HTML. Esta lección no implementa chat ni streaming.
04 / SUMMONING SPELL
Leer el hechizo. Antes de despertarlo.
El CNN será el portero anterior a Whisper. El código revisado trabaja con ventanas de 1,5 segundos: log-mel de 40 × 148 → WakeNet → cinco clases. La lectura del código no equivale a aprobar su despliegue.
01
Qué entra.
Verificar PCM de 16 bits, mono y 16 kHz al cargar WAV. La implementación fija 148 frames; hay un comentario antiguo que dice 94. La forma del tensor la decide el código, no ese comentario.
02
Qué aprende.
Cuatro bloques convolucionales y un promedio global sobre frecuencia y tiempo. Ese promedio reduce la dependencia de la posición; no garantiza invariancia perfecta a desplazamientos.
03
Qué significa “test”.
El split actual mezcla ejemplos al azar y su accuracy elige el checkpoint. Ese conjunto es de validación. Para una prueba final honesta, separar sesiones de grabación y reservar datos que no elijan el modelo.
04
Qué falta medir.
Falsas activaciones por hora, nombres perdidos, confusiones y latencia en la máquina de escucha. Confirmar equivalencia del modelo exportado a ONNX y diseñar el modo susurro antes de conectar el portero.