Back to flin
flin

El opcode CreateEntity que desapareció

Cómo un manejador de opcode faltante en la máquina virtual de FLIN rompió silenciosamente toda la creación de entidades en funciones -- y el proceso de depuración que lo encontró.

Juste Thales Gnimavo & Claude | March 26, 2026 4 min flin
EN/ FR/ ES
flinrust

Hay una clase particular de error que te hace cuestionar la realidad. Todo parece correcto. El código compila. El servidor responde. No aparecen mensajes de error en ninguna parte. Sin embargo, algo fundamental está roto, y la única evidencia es una ausencia -- lo que esperabas que sucediera simplemente no sucede.

El 3 de febrero de 2026, encontramos uno de estos errores en FLIN. La creación de entidades dentro de funciones había dejado de funcionar silenciosamente. Los usuarios podían hacer clic en "Agregar tarea" en la aplicación de tareas y no pasaba nada. Sin error. Sin fallo. Sin advertencia. El botón se pulsaba, la solicitud se disparaba, el servidor respondía con {"type":"ok"}, y la lista de tareas permanecía sin cambios. El silencio era el síntoma.

La matriz de síntomas

La primera pista de que algo arquitectónico estaba mal vino del patrón de lo que funcionaba versus lo que no:

OperaciónEstadoNotas
saveEdit(task) -- editar entidades existentesFuncionandoCampos actualizados correctamente
toggleTask(task) -- modificar campos de entidadFuncionandoToggle booleano persistido
deleteTask(task) -- eliminar entidadesFuncionandoEntidades eliminadas correctamente
addTask() -- crear nuevas entidadesRotoNo pasaba nada en absoluto

Editar, alternar, eliminar -- todo funcionando. Solo la creación estaba rota. Y la creación era la única operación que requería construir un nuevo objeto de entidad desde cero.

El manejador faltante

En la Sesión 269, habíamos agregado varios manejadores de opcodes a execute_until_return para soportar el nuevo sistema de acciones. Cada opcode necesario para editar, eliminar y guardar entidades estaba presente. Pero CreateEntity -- el opcode que construye una nueva instancia de entidad -- no estaba en la lista. Había sido pasado por alto porque todas nuestras pruebas se enfocaban en saveEdit(task), que modifica entidades existentes usando SetField. Nunca probamos addTask() con el nuevo sistema de acciones.

Cómo funciona el fallo silencioso

Cuando la VM encontraba el bytecode para crear y guardar una nueva tarea:

ip=1742: CreateEntity (0x77) + u16 type_idx + u8 field_count = 4 bytes
ip=1746: StoreLocal (0x21) + u8 slot = 2 bytes
ip=1748: LoadLocal (0x20) + u8 slot = 2 bytes
ip=1750: Save (0x90) = 1 byte
ip=1751: LoadNone + Return

La VM no tenía manejador para CreateEntity en execute_until_return. El caso por defecto avanzaba el puntero de instrucciones solo un byte, pero CreateEntity es una instrucción de cuatro bytes. Así que el IP avanzaba al medio de los operandos de la instrucción. El siguiente byte se interpretaba como un opcode. Era basura, pero el caso por defecto lo manejaba avanzando de nuevo. El IP vagaba por el bytecode hasta que eventualmente golpeaba algo que causaba un retorno temprano.

La entidad nunca se creaba. El opcode Save nunca se alcanzaba. Sin error. Sin fallo. Solo silencio.

La corrección

La corrección fue sustancial pero mecánica -- agregar el manejador CreateEntity a execute_until_return:

rustOpCode::CreateEntity => {
    let type_idx = self.read_u16(code);
    let field_count = self.read_u8(code) as usize;
    let entity_type = self.get_identifier(chunk, type_idx)?;

    // Auto-register entity schema if not already registered
    if !self.database.has_entity_type(&entity_type) {
        use crate::database::EntitySchema;
        let schema = EntitySchema::new(&entity_type);
        let _ = self.database.register_entity(schema);
    }

    let mut entity = EntityInstance::new(entity_type.clone());

    for _ in 0..field_count {
        let value = self.pop()?;
        let name = self.pop()?;
        if let Value::Object(id) = name {
            if let Ok(s) = self.get_string(id) {
                entity.fields.insert(s.to_string(), value);
            }
        }
    }

    let id = self.alloc(HeapObject::new_entity(entity));
    self.push(Value::Object(id))?;
}

Lecciones para diseñadores de lenguajes

Este error ilustra varios principios que aplican a cualquier implementación de máquina virtual o intérprete.

Primero, los fallos silenciosos son la clase más peligrosa de errores. Un mensaje de error, por críptico que sea, al menos te dice que algo salió mal. Los fallos silenciosos no te dan nada.

Segundo, las tablas de despacho paralelas deben permanecer sincronizadas. Cualquier vez que tengas dos rutas de código que manejen el mismo conjunto de operaciones, agregar algo a una y olvidar la otra es una inevitabilidad.

Tercero, probar el flujo completo, no solo los componentes. Teníamos pruebas extensas para la creación de entidades. Teníamos pruebas para el sistema de acciones. Pero no teníamos una prueba que creara una entidad dentro de una función llamada por el sistema de acciones.


Esta es la Parte 156 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - Arco anterior: biblioteca estándar y ecosistema de FLIN - [156] El opcode CreateEntity que desapareció (estás aquí) - [157] El error de iteración del bucle for

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

El prompt decía que lo anunciara. El modelo no lo hizo. Así que lo hizo el worker: cerrar un silencio de 15 segundos en Déblo Voice

Déblo Voice se quedaba en silencio unos 15 segundos cada vez que una pregunta difícil activaba una búsqueda profunda. El prompt ya exigía un anuncio, pero el modelo de audio nativo emite la llamada a la función como su primer token. Además, el silencio tenía dos partes, la ejecución de la herramienta y la latencia de la respuesta, así que ningún prompt podía cerrarlo. Llevamos la garantía al worker de LiveKit: un anuncio pregrabado con la voz y el idioma de la sesión, y un suave sonido de tecleo mientras el agente piensa. Probado en producción el 16 de octubre de 2026: 14 y 17,5 segundos de silencio del lado del modelo, unos 0,4 segundos percibidos por el usuario.

13 min Oct 16, 2026
deblodeblo-voicevoice-aigemini-live +17
Thales & Claude zerosuite

El servidor que no dimos de baja: vaciar una máquina de producción en una noche, y cada número que comprobamos

Una noche, seis aplicaciones migradas fila por fila, tres copias de seguridad vacías, una sin destino y un servidor cuyo futuro cambió tres veces.

19 min Oct 2, 2026
caspclaude-codeclaude-opus-5.5infrastructure +10
Thales & Claude zerosuite

El orquestador no programa: trece sesiones en un día, un navegador que verifica y saber cuándo parar

Un día, una plataforma privada de un cliente, trece sesiones headless de Claude encadenadas por un orquestador que nunca escribe código. Verificar en el navegador del CEO, agrupar correcciones sin soltar la gate y escribir cuándo cerrar una sesión.

19 min Sep 30, 2026
caspclaude-codeclaude-opus-5.5multi-session +12