Tesla Roadster, el coche volador, recreado con Three.js

Tabla de contenidos

El nuevo Roadster de Tesla sube por una rampa en forma de loop, se queda boca abajo pegado a ella, suelta un chorro de gas, da una voltereta en el aire y aterriza sobre sus ruedas.

Lo presentaron el 15 de octubre de 2026 en las instalaciones de SpaceX en McGregor, Texas, y en cuanto vi el vídeo pensé lo mismo que muchos developers frontend: esto se puede hacer en el navegador.

Y lo he hecho. Con un coche inventado, sin logos ni siluetas de nadie, para que el ejemplo sea nuestro y para que lo que salga de aquí sirva para cualquier otra cosa que tenga que recorrer un camino.

Esto es lo que vas a encontrar:

  • Qué hizo de verdad el Roadster en la presentación, sin exagerar lo que no hizo
  • Una demo jugable que reproduce la secuencia completa: rampa, inversión, propulsores, voltereta y aterrizaje
  • Cómo se descompone el efecto en cuatro piezas técnicas: escena 3D, coreografía, partículas y pulido cinematográfico
  • La parte que suele faltar en las demos de efectos: cómo probar que funciona con Playwright
  • Cómo destilar todo esto en cinco skills componibles para tu agente de IA

¿El Roadster de Tesla vuela de verdad?

No vuela en el sentido de un avión ni de un dron. Sus propulsores lo levantan unos segundos, lo justo para saltar un obstáculo o corregir la orientación en el aire. No es un vuelo sostenido y Tesla tampoco dice que lo sea, según recoge Business Upturn.

Elon Musk lo vendió con su frase de siempre: “Turns out the future does have flying cars”. Pero si miras la coreografía con calma, ves una secuencia de fases muy marcadas.

Esto es lo que mostró la presentación, con los datos que dieron las fuentes:

Fase Qué pasó Dato
Rampa Sube acelerando por una rampa curva enorme 0 a 60 mph en menos de un segundo, según Tesla
Succión Se mantiene pegado con ventiladores hasta quedar boca abajo Aguanta invertido incluso parado, según Business Upturn
Despegue Activa propulsores de gas frío desarrollados con SpaceX 10 propulsores conectados a 600 litros de aire comprimido
Voltereta Gira en el aire y se endereza El sistema genera de 4 a 5 veces la fuerza de los neumáticos, según Fox Business
Aterrizaje Cae sobre sus ruedas En otra demo saltó por encima de un Cybertruck, un Semi y tres coches más

Las cifras de los propulsores las dio Lars Moravy, vicepresidente de ingeniería de vehículos de Tesla, según TechCrunch. La presentación llega casi nueve años después del primer prototipo, enseñado en 2017, y la producción está prevista para 2028.

⚠️ Hay dudas razonables. Electrek publicó antes del evento que el coche de la demo se manejaba en remoto y que no estaba homologado para circular. Business Upturn no pudo confirmarlo. Tampoco hay mediciones independientes del 0 a 60 en menos de un segundo.

A mí lo que me interesa es la coreografía: cada fase de ese salto se corresponde con una técnica web concreta.

¿Cómo se ve la demo jugable del Roadster volador?

Aquí la tienes. Pulsa Lanzar, arrastra el control Pilota para moverte por la secuencia a tu ritmo, cambia el tipo de propulsor o desmarca Efectos de cámara (bloom, estela, temblor y zoom) para ver cuánto aporta el último 20 %. Si los propulsores se te escapan, pon la velocidad a ¼×: solo empujan medio segundo.

Si la quieres a pantalla completa, ábrela en una pestaña aparte.

Fíjate en el panel de la derecha. Las diez casillas son los diez propulsores y se encienden cuando disparan. Debajo, el depósito de 600 litros se va vaciando con cada pulso. No hace falta para el efecto, pero gracias a él sabes en cada momento cuánto aire le queda al coche, y la animación pasa a contar algo.

La demo cabe en un solo fichero HTML. Carga Three.js r169 y GSAP 3.12.5 desde CDN, no tiene paso de build y no usa ningún modelo 3D externo: el coche está hecho con primitivas.

Cuatro fotogramas de la demo: el coche amarillo se acerca a la rampa en forma de loop; el coche boca abajo en lo alto del loop; los propulsores de gas frío lanzan vapor blanco mientras el coche gira en el aire; el coche aterriza sobre el asfalto levantando polvo.
Los cuatro actos de la secuencia, capturados por el propio script de prueba.

¿Cómo se descompone el efecto en JavaScript?

En cuatro capas que se apilan: escena 3D, coreografía, partículas y pulido. Cada una tiene una responsabilidad clara y le deja a la siguiente un contrato sencillo.

Antes de entrar en cada una, un aviso: no hace falta saber física para esto. Hace falta saber separar lo que se mueve de lo que decide cuándo se mueve. Esa separación es la que permite luego convertir el efecto en skills reutilizables.

Curso gratis por email

Lo que acabas de ver, convertido en skills que tu agente reutiliza

Un minicurso por email, a tu ritmo, para instalar skills y crear la tuya. Te viene justo antes de partir este efecto en piezas que tu agente pueda repetir.

Apúntate al minicurso →
Capa Tecnología Responsabilidad Contrato que deja
Escena Three.js Renderer, cámara, luces, suelo, resize Una escena y una cámara listas
Coreografía CatmullRomCurve3 + GSAP Dónde está el coche y cuándo Un objeto state con valores por acto
Partículas THREE.Points + shader El gas de los propulsores Emisores que leen state
Pulido Postprocesado Que parezca un vídeo de lanzamiento Nada: solo consume

Vamos capa a capa.

¿Cómo se monta la escena 3D con Three.js?

Con un renderer, una cámara en perspectiva, dos luces y un suelo. Nada exótico. La parte que más se olvida es la de siempre: el resize, el devicePixelRatio y respetar prefers-reduced-motion.

const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;

const renderer = new THREE.WebGLRenderer({ antialias: true });
// Más de 2 en móvil es quemar batería sin ganancia visible
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.shadowMap.enabled = true;

const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 400);
scene.add(new THREE.HemisphereLight(0xffe2c4, 0x3a3150, 1.1));
const sun = new THREE.DirectionalLight(0xffd2a1, 2.4);
sun.castShadow = true;

Con movimiento reducido, la demo no arranca sola, quita el temblor de cámara y emite un tercio de las partículas. El efecto sigue ahí, pero no te sacude la pantalla si le has pedido al sistema que no lo haga.

El coche: inventado y low-poly

El chasis es un perfil lateral en forma de cuña que se extruye a lo ancho con ExtrudeGeometry. Cuatro cilindros hacen de ruedas y una caja oscura, de cabina. Debajo van dos discos que se iluminan en lila cuando los ventiladores de succión están trabajando.

¿Por qué no un modelo glTF? Porque cualquier modelo realista acaba pareciéndose a un coche de verdad, y aquí quería algo que se pudiera publicar sin pensar en licencias ni marcas. Si prefieres un modelo, busca uno con licencia CC0 y cárgalo con GLTFLoader. El resto del código no cambia.

¿Cómo se hace que un objeto recorra una rampa en loop?

Definiendo el camino como una curva y colocando el objeto con getPointAt(t) y getTangentAt(t). La curva es una CatmullRomCurve3 que pasa por unos puntos en línea recta y luego por medio círculo vertical.

const R = 9; // radio del loop
const pts = [];
for (const z of [-40, -30, -20, -12, -5]) pts.push(new THREE.Vector3(0, 0, z));
for (let deg = 0; deg <= 180; deg += 12) {
  const a = THREE.MathUtils.degToRad(deg);
  pts.push(new THREE.Vector3(0, R - R * Math.cos(a), R * Math.sin(a)));
}
// "centripetal" evita que la curva se pase de largo entre puntos
const curve = new THREE.CatmullRomCurve3(pts, false, "centripetal");

getPointAt usa la longitud de arco, así que un mismo incremento de t avanza siempre la misma distancia. Eso es justo lo que quieres para controlar la velocidad desde fuera.

La tangente te dice hacia dónde mira el morro. Pero falta otra pieza: ¿hacia dónde está el techo? En una carretera plana es fácil, pero en un loop el techo tiene que ir girando hasta apuntar al suelo.

Marcos de orientación por transporte paralelo

La solución es calcular el vector “arriba” a lo largo de la curva sin que dé saltos. Empiezas con (0, 1, 0) y, en cada muestra, le quitas la componente que va en la dirección de la nueva tangente:

let up = new THREE.Vector3(0, 1, 0);
for (let i = 0; i <= FRAMES; i++) {
  const u = i / FRAMES;
  const tangent = curve.getTangentAt(u).normalize();
  // proyectamos el "arriba" anterior sobre el plano perpendicular a la tangente
  up = up.clone().sub(tangent.clone().multiplyScalar(up.dot(tangent))).normalize();
  frames.push({ pos: curve.getPointAt(u), tangent, up: up.clone() });
}

En la recta, up sigue siendo (0, 1, 0). En el loop va girando hacia el centro y en lo alto apunta hacia abajo: el coche queda boca abajo sin que hayas escrito un solo if para el loop. La demo precalcula 800 marcos y luego interpola entre ellos.

Con tangente y “arriba” ya puedes construir la rotación:

function orient(obj, forward, up) {
  const right = new THREE.Vector3().crossVectors(up, forward).normalize();
  const trueUp = new THREE.Vector3().crossVectors(forward, right).normalize();
  obj.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(right, trueUp, forward));
}

Con esos mismos marcos se construye la rampa. Para cada muestra, desplazas el punto media anchura a la izquierda y a la derecha, y unes los pares en triángulos. Coche y rampa no pueden desencajar porque salen de los mismos datos.

💡 Este patrón sirve para mucho más que coches: un logo que recorre la pantalla, un cohete, una cámara que hace un fly-through por una escena o una montaña rusa. Si algo sigue un camino, necesita posición, tangente y “arriba”.

Si te gusta destripar efectos así y pasarlos a tu agente, cada domingo compartimos con +7.200 developers lo que vamos aprendiendo sobre programar con IA. Gratis, desde 2018.

Quiero esa dinamita 🧨

¿Cómo se coreografía la secuencia con GSAP?

Con un único timeline de GSAP dividido en cuatro actos que anima un objeto plano llamado state. El timeline no toca el coche ni las partículas. Solo mueve números entre 0 y 1, y cada pieza de la escena lee los que le interesan.

const state = { s: 0, fans: 0, tau: 0, roll: 0, slide: 0, belly: 0, rollThrust: 0, shake: 0, fovKick: 0 };
const tl = gsap.timeline({ paused: true });

tl.addLabel("ramp", 0)
  .to(state, { s: 1, duration: 3.4, ease: "power2.in" }, "ramp")   // sube acelerando
  .to(state, { fans: 1, duration: 0.4 }, 1.5)                       // succión
  .addLabel("hold", 3.4)                                            // boca abajo, quieto
  .addLabel("launch", 4.1)
  .to(state, { belly: 1, duration: 0.06 }, "launch")                // 6 propulsores de los bajos
  .to(state, { tau: 1, duration: 2.2, ease: "none" }, "launch")     // trayectoria en el aire
  .to(state, { roll: 1, duration: 1.3, ease: "power2.inOut" }, "launch+=0.4")
  .addLabel("land", 6.3)
  .to(state, { slide: 1, duration: 1.3, ease: "power3.out" }, "land");

Los cuatro actos son:

  1. Rampa. s va de 0 a 1 con power2.in, así que el coche acelera de verdad. A mitad de subida se encienden los ventiladores.
  2. Hold. Siete décimas de segundo boca abajo y quieto. Sin esa pausa, el ojo no registra que el coche está invertido y la voltereta pierde fuerza.
  3. Despegue. Disparan los seis propulsores de los bajos, el coche se separa de la rampa y empieza a rodar sobre su eje. Los cuatro laterales disparan dos veces: una para iniciar el giro y otra para frenarlo.
  4. Aterrizaje. El cuerpo se aplasta con un elastic.out, la cámara tiembla y el coche se desliza unos metros mientras frena.

En el aire, el coche ya no sigue la curva. Su altura es una parábola sencilla y su orientación es la de lo alto del loop rotada 1,5 vueltas sobre el eje del morro. Empieza boca abajo y, tras vuelta y media, aterriza derecho.

const up = TOP.up.clone().applyAxisAngle(forwardFlight, state.roll * Math.PI * 3);
orient(car, forwardFlight, up);

¿Y la cámara? Va en el mismo timeline, con seis posiciones clave encadenadas con sine.inOut. Así, si rebobinas la secuencia, la cámara rebobina con ella.

Pilotarlo con scroll

El control Pilota de la demo hace lo mismo que haría ScrollTrigger: mover el timeline a mano. Si quieres que el lector “pilote” la secuencia con la rueda del ratón, basta con enganchar el timeline a un contenedor alto:

gsap.registerPlugin(ScrollTrigger);

ScrollTrigger.create({
  trigger: "#roadster-section",
  start: "top top",
  end: "+=3000",       // 3000 px de scroll para toda la secuencia
  pin: true,
  scrub: 0.5,
  onUpdate: (self) => seek(self.progress * tl.duration()),
});

Si es tu primera vez con GSAP, en el post sobre las skills oficiales de GSAP para agentes de IA tienes ScrollTrigger y compañía explicados con proyectos completos.

¿Cómo se hacen los propulsores de gas frío con partículas?

Con THREE.Points y un shader que calcula el tamaño y la opacidad de cada partícula según su edad. El detalle que da credibilidad es que el gas frío no es fuego: es vapor blanco que sale rápido, se expande y desaparece en menos de un segundo.

Cada propulsor es una tobera con una posición y una dirección en coordenadas del coche. La dirección es hacia dónde sale el gas, que es la contraria al empuje:

const NOZZLES = [
  // 6 en los bajos: separan el coche de la rampa y amortiguan el aterrizaje
  ...[[-0.75, 1.4], [0.75, 1.4], [-0.8, 0], [0.8, 0], [-0.75, -1.4], [0.75, -1.4]]
    .map(([x, z]) => ({ group: "belly", pos: new THREE.Vector3(x, -0.45, z), dir: new THREE.Vector3(0, -1, 0) })),
  // 4 laterales, a distinta altura: un par de fuerzas para hacerlo rodar
  { group: "roll", pos: new THREE.Vector3(-1.05, 0.25, 0.9), dir: new THREE.Vector3(-1, 0, 0) },
  { group: "roll", pos: new THREE.Vector3(1.05, -0.25, 0.9), dir: new THREE.Vector3(1, 0, 0) },
  // ...
];

Cuando el coche está boca abajo en lo alto del loop, los bajos miran al cielo. Al disparar, el gas sale hacia la rampa y el coche sale despedido hacia el suelo. Si te fijas en la demo, el chorro de vapor sube.

El shader: tamaño y opacidad según la edad

En cada paso, JavaScript mueve las partículas y calcula su edad normalizada (aAge, de 0 a 1). El shader hace el resto:

// vertex shader
attribute float aAge, aSize, aGrowth, aOpacity;
uniform float uScale;
varying float vAlpha;

void main() {
  vec4 mv = modelViewMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * mv;
  // crece con la edad: el gas se expande al salir de la tobera
  gl_PointSize = aSize * (1.0 + aGrowth * aAge) * uScale / -mv.z;
  // aparece rápido y se desvanece despacio
  vAlpha = aOpacity * pow(1.0 - aAge, 1.5) * smoothstep(0.0, 0.06, aAge);
}

El fragment shader dibuja un círculo con el borde difuminado y aplica vAlpha. Nada más. El reparto es sencillo: la física, en JavaScript, y el aspecto, en la GPU.

Presets por tipo de propulsor

Lo que distingue el gas frío del fuego son media docena de parámetros. Por eso la demo los agrupa en presets, y el selector te deja cambiar de uno a otro sin tocar nada más:

Preset Vida Expansión Rozamiento Mezcla Sensación
Gas frío Corta Alta Alto Normal Vapor blanco que se abre y se frena enseguida
Fuego Muy corta Baja Medio Aditiva Llama que brilla y se apaga
Humo Larga Muy alta Medio Normal Nube gris que sube despacio
Chispas Media Ninguna Bajo Aditiva Puntos que caen por gravedad

El rozamiento alto es lo que más vende el gas frío. Las partículas salen a 10-15 m/s, pero frenan tan rápido que el chorro se queda cerca de la tobera y se ensancha. El fuego, en cambio, usa mezcla aditiva para que las partículas sumen luz.

🔑 La demo pone un presupuesto de 5.000 partículas para el sistema principal (1.500 con movimiento reducido). Cuando se acaban, la nueva reemplaza a la más vieja (buffer circular). Nunca se crea un objeto nuevo en mitad de la animación.

¿Qué hace que parezca un vídeo de lanzamiento y no una demo técnica?

El postprocesado y los trucos de cámara: bloom suave, estela de movimiento, temblor en el despegue y un FOV que se abre al acelerar. Ese 20 % final es lo que hace que la demo se parezca a un vídeo de presentación.

Puedes comprobarlo desmarcando Efectos de cámara en la demo. La secuencia es la misma, pero sin esa capa parece una prueba de físicas.

La cadena de pases es esta:

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const afterimage = new AfterimagePass(0);              // estela a alta velocidad
composer.addPass(afterimage);
composer.addPass(new UnrealBloomPass(new THREE.Vector2(256, 256), 0.35, 0.35, 0.92));
composer.addPass(new OutputPass());                    // tone mapping y sRGB al final

Cada efecto está atado a un valor del timeline:

  • FOV dinámico: fovKick sube con la aceleración. La cámara pasa de 50° a 64° y la sensación de velocidad se multiplica sin mover nada más.
  • Estela: AfterimagePass mezcla cada fotograma con el anterior. Solo se activa cuando hay velocidad, así que en el resto de la secuencia no consume nada.
  • Temblor: shake sube de golpe en el despegue y en el aterrizaje y cae con power2.out. El desplazamiento usa senos del tiempo de simulación, no Math.random(), por una razón que verás en la siguiente sección.
  • Bloom: con umbral alto (0,92) para que solo brillen los reflejos y el vapor más denso.

Mi primera versión tenía el bloom a 0,55 con umbral 0,82. El vapor brillaba tanto que el coche amarillo parecía beige. Lo bajé tras comparar capturas lado a lado, y esa es la lección de esta capa: el pulido se ajusta mirando fotogramas, no leyendo parámetros.

El presupuesto de rendimiento

“Que vaya fluido” no es un requisito. “60 fps en un móvil de gama media” sí lo es. La demo mide los FPS cada segundo y, si pasa dos segundos seguidos por debajo de 45, baja el pixelRatio a 1. Es una degradación que el usuario casi no nota y que le devuelve los fotogramas.

¿Por qué no Rapier para el aterrizaje? Porque un motor de físicas real hace que cada ejecución sea un poco distinta, y aquí quería una coreografía exacta. Si necesitas que el coche rebote de verdad contra obstáculos que mueve el usuario, entonces sí: Rapier y su versión WebAssembly son la opción.

Ajustar el último 20 % de un efecto se aprende viendo cómo lo hacen otros. Cada domingo, +7.200 developers compartimos experiencias y 12 recursos sobre programar con IA.

Apúntate gratis →

¿Cómo se prueba que una animación 3D funciona?

Haciendo que la escena sea determinista y capturando fotogramas clave con Playwright. Es la parte que suele faltar en las demos de efectos, y la que hace de este ejemplo un ejemplo de harness.

El problema de partida: una animación con partículas aleatorias que depende de requestAnimationFrame nunca da dos veces el mismo fotograma. Así no hay manera de comparar.

La demo lo resuelve con tres decisiones:

  1. Paso fijo de simulación. La lógica avanza siempre en pasos de 1/60 s, vaya el navegador a 30 fps o a 120. El render va aparte.
  2. Timeline en pausa. GSAP no avanza solo: en cada paso, la simulación le dice tl.time(simTime).
  3. Aleatoriedad con semilla. Las partículas usan un generador con semilla en vez de Math.random(). Misma semilla, mismas partículas.

Con eso, la demo expone una pequeña API en window.__demo cuando la abres con ?proof:

window.__demo = {
  seek(progress) { resetSim(); seek(progress * DURATION); return this.snapshot(); },
  snapshot() {
    return {
      car: car.position.toArray(),
      upY: new THREE.Vector3(0, 1, 0).applyQuaternion(car.quaternion).y, // 1 = derecho, -1 = boca abajo
      activeNozzles: NOZZLES.filter((n) => n.active).length,
      tank: Math.round(tank),
    };
  },
  ready: true,
};

Y el script de Playwright captura los fotogramas t = 0; 0,5; 0,75 y 1 dos veces y compara sus hashes:

import { chromium } from "playwright";
import { createHash } from "node:crypto";

const KEYFRAMES = [0, 0.5, 0.75, 1];
const hash = (buf) => createHash("sha256").update(buf).digest("hex").slice(0, 12);
const browser = await chromium.launch();

async function run() {
  const page = await browser.newPage({ viewport: { width: 1200, height: 675 } });
  // la demo servida en local con cualquier servidor estático
  await page.goto("http://localhost:4321/demos/roadster-vuela/?proof");
  await page.waitForFunction(() => window.__demo?.ready);
  const frames = [];
  for (const t of KEYFRAMES) {
    const snap = await page.evaluate((t) => window.__demo.seek(t), t);
    const png = await page.locator("canvas").screenshot();
    frames.push({ t, hash: hash(png), ...snap });
  }
  await page.close();
  return frames;
}

const a = await run();
const b = await run();
console.table(a);
console.log("deterministic:", a.every((f, i) => f.hash === b[i].hash));
await browser.close();

Este es el resultado real:

t Posición del coche upY Propulsores activos Aire
0 (0; 0,95; −40) 1 0 600 L
0,5 (0; 17,06; −0,1) −0,99 0 600 L
0,75 (0; 9,17; −12,39) 0,99 4 549 L
1 (0; 0,95; −22) 0,99 0 528 L

La tabla ya es un test. En t = 0,5 el coche está a 17,06 de altura (dos radios menos la altura del chasis) y boca abajo. En t = 1 vuelve a estar a 0,95, derecho y con el depósito más vacío. Si mañana alguien toca la curva o el timeline y el coche aterriza de lado, upY lo canta.

La primera ejecución dio deterministic: false. ¿El culpable? El contador de FPS del HUD. Su texto cambiaba de ancho entre capturas y desplazaba los botones, que salían dentro del recorte del canvas. El render 3D era idéntico píxel a píxel. En modo prueba el contador ya no se actualiza, y la segunda ejecución dio true.

🛡️ Sin esa comparación habría dado la demo por buena con un falso positivo. Si quieres que tu agente haga estas comprobaciones sin llenar el contexto, mira Playwright CLI como alternativa a Playwright MCP.

Tu IA puede mentirte

Que tu agente demuestre que el efecto funciona, no que te lo diga

Verás métodos para revisar lo que generan los agentes: pruebas en navegador con Playwright, casos Gherkin y revisión adversarial entre modelos.

Entrar a la masterclass →

Acceso con suscripción Web Reactiva Premium

¿Qué skills convierten este efecto en algo repetible?

Cinco skills componibles en vez de una megaskill de “efecto Roadster”. Cada una resuelve una capa y deja un contrato claro para la siguiente. Así puedes reutilizar la coreografía sin las partículas o las partículas sin el loop.

Esta demo es la primera sesión de cocina. Primero sale el plato y luego se escribe la receta, que es lo que propone el método Cook-First Skills: la skill se escribe a partir de lo que ha pasado en una sesión real y solo se da por buena cuando funciona en una segunda.

Skill Qué resuelve Contrato de salida
threejs-scene-setup Renderer, cámara, luces, glTF, resize, prefers-reduced-motion scene, camera, renderer, reducedMotion
path-choreography Curva + marcos por transporte paralelo + timeline GSAP Objeto state y labels por acto
particle-thrusters Emisores por tipo con vida, color, dispersión y empuje Emisores que leen intensidades de state
cinematic-polish Bloom, shake, FOV dinámico, estela y presupuesto de fps Valores de pulido atados a state
prove-it-works Paso fijo, semilla, API __demo y fotogramas con Playwright Tabla de snapshots + deterministic: true

El orden de invocación es setup → choreography → thrusters → polish → prove. El contrato que lo cose todo es el objeto state: la coreografía lo escribe y el resto lo lee. Los emisores no saben nada del loop, solo que cuando state.belly pasa de 0,05 tienen que soltar gas.

Cómo sería el SKILL.md de particle-thrusters

Es la más reutilizable y la más vistosa para enseñar, así que va como ejemplo:

---
name: particle-thrusters
description: Añade emisores de partículas configurables (gas frío, fuego, humo,
  chispas, polvo) a una escena Three.js con THREE.Points y un shader de tamaño
  y opacidad por edad. Úsala cuando el usuario pida propulsores, toberas,
  escapes, humo, vapor, chispas o explosiones en una escena 3D.
---

# particle-thrusters

## Entrada
- Una escena Three.js existente y un objeto que porta las toberas.
- Un objeto `state` con intensidades 0..1 por grupo de toberas.

## Pasos
1. Define las toberas en coordenadas locales: `{ group, pos, dir }`.
   `dir` es hacia dónde SALE el gas, opuesto al empuje.
2. Crea un sistema por modo de mezcla (normal y aditiva) con buffer circular
   y un presupuesto fijo de partículas.
3. Emite con acumulador fraccional: `acc += rate * intensity * dt`.
4. La física (velocidad, rozamiento, gravedad) en JS; tamaño y opacidad
   por edad en el shader.
5. Usa un RNG con semilla, nunca Math.random().

## Presets
Copia la tabla de `references/presets.md`. Gas frío = vapor blanco,
rozamiento alto, expansión alta, mezcla normal. NO es fuego.

## Verificación
- Con intensidad 0 no se emite ninguna partícula.
- El número de partículas vivas nunca supera el presupuesto.

Fíjate en el campo description: hace de disparador. Enumera las palabras que usaría alguien que necesita esta skill sin saber que existe (“propulsores”, “escapes”, “vapor”). Si una skill no se activa cuando debería, casi siempre el problema está en ese campo.

Por qué partirlo en cinco y no en una

Una skill de “efecto Roadster” solo serviría para hacer más Roadsters. Partida en cinco, path-choreography sirve para el cohete de la landing de tu próximo proyecto, particle-thrusters para el humo de una chimenea y prove-it-works para cualquier canvas que quieras comprobar en CI.

Además, al tener un coche inventado, las skills no pueden quedar atadas a un modelo concreto. No hay ningún “el Roadster tiene 10 toberas” escondido en las instrucciones. Las toberas son un dato de entrada.

¿Qué aprendí construyendo la demo?

Tres cosas que no esperaba al empezar.

La primera: el hold boca abajo es lo que hace funcionar la voltereta. Sin esas siete décimas de segundo quieto, el ojo no procesa que el coche está invertido y el giro se queda en un movimiento cualquiera. La presentación de Tesla tiene la misma pausa, y no creo que sea casualidad.

La segunda: los shaders propios no reciben el tone mapping gratis. Con el pulido desactivado, el cielo salía mucho más oscuro que con el pulido activo, porque mi ShaderMaterial no incluía los chunks tonemapping_fragment y colorspace_fragment. Dos líneas de #include y arreglado. Es el típico error que solo ves si comparas capturas.

La tercera: la prueba encontró un fallo que no era del efecto. El HUD movía los botones y rompía la comparación. Si no hubiera exigido determinismo, nunca lo habría visto.

TL;DR

  • 🚗 El Roadster de Tesla no vuela de forma sostenida: sus 10 propulsores de gas frío, conectados a 600 litros de aire comprimido, lo levantan unos segundos para saltar o enderezarse.
  • 🎢 La secuencia de la presentación (rampa, succión, inversión, despegue, voltereta, aterrizaje) se recrea en el navegador con Three.js, GSAP, partículas y postprocesado.
  • 🧭 La clave del loop es calcular el vector “arriba” por transporte paralelo y construir la rampa con los mismos marcos que mueven el coche.
  • 🧪 Paso fijo, timeline en pausa y aleatoriedad con semilla hacen la escena determinista, y Playwright compara fotogramas clave para probar que funciona.
  • 🧩 El efecto se destila en cinco skills componibles unidas por un objeto state: setup, choreography, thrusters, polish y prove.

Preguntas frecuentes

¿El nuevo Tesla Roadster puede volar?

No en el sentido de un avión. Sus propulsores de gas frío lo elevan unos segundos para saltar obstáculos o corregir la orientación en el aire. Business Upturn subraya que no es un vuelo sostenido y que Tesla tampoco lo afirma.

¿Cuántos propulsores tiene el Tesla Roadster y cómo funcionan?

Según Lars Moravy, vicepresidente de ingeniería de vehículos de Tesla, tiene 10 propulsores conectados a 600 litros de aire comprimido, y el depósito se puede recargar. Son propulsores de gas frío desarrollados con SpaceX: expulsan aire a presión, no queman combustible.

¿Cuándo sale el Tesla Roadster y cuánto cuesta?

Tesla prevé empezar a fabricarlo en 2028. No dio precio base en la presentación, pero la página de reservas pide 50.000 dólares para la versión Performance y 250.000 para la Thrust. La versión Rocket, con el sistema de vuelo completo, es solo por invitación.

¿Qué necesito para hacer una animación 3D así en la web?

Three.js para la escena, GSAP para la coreografía y un shader sencillo para las partículas. La demo de este artículo cabe en un solo HTML sin paso de build y carga ambas librerías desde CDN.

¿Cómo hago que un objeto 3D siga una curva en Three.js?

Define el camino con CatmullRomCurve3, coloca el objeto con getPointAt(t) y oriéntalo con getTangentAt(t). Para el vector “arriba”, usa transporte paralelo: proyecta el “arriba” anterior sobre el plano perpendicular a cada nueva tangente.

¿Cómo se simula el gas frío de un propulsor con partículas?

Con partículas blancas de vida corta (menos de un segundo), mucha expansión de tamaño y un rozamiento alto que las frena cerca de la tobera. Usa mezcla normal y no aditiva: el gas frío es vapor, no luz.

¿Cómo engancho una animación de GSAP al scroll?

Crea el timeline en pausa y usa ScrollTrigger con scrub y pin. En onUpdate, convierte self.progress en un tiempo del timeline y llama a tu función de avance.

¿Cómo pruebo con Playwright una animación en canvas?

Haz la escena determinista (paso fijo, timeline en pausa, RNG con semilla), expón una función seek(t) en window y captura fotogramas clave dos veces. Si los hashes coinciden y el estado devuelto es el esperado, la animación funciona.

¿Cómo mantengo 60 fps en móvil con postprocesado?

Limita el devicePixelRatio a 2, fija un presupuesto de partículas, desactiva los pases que no aportan en cada momento y baja la resolución si los FPS caen por debajo de 45 durante un par de segundos.

¿Por qué dividir un efecto en varias skills en lugar de una?

Porque cada capa se reutiliza por separado: la coreografía sirve para cualquier objeto que recorra un camino y los emisores, para cualquier tipo de humo o fuego. Una sola skill grande quedaría atada a un caso concreto y no la volverías a usar.

Fuentes

¿Qué otra cosa de la presentación te gustaría ver recreada en el navegador? El salto por encima de los cinco coches está pidiendo a gritos un path-choreography con obstáculos.

🧨 Última oprtunidad para recibir la dinamita que mereces sobre programación con IA el próximo domingo: Suscríbete gratis a Web Reactiva en https://webreactiva.com/newsletter

Imagen de Daniel Primo
Claude, IA de Anthropic

Escrito con la ayuda de la IA generativa de Claude, fuentes fidedignas y con un human in the loop:
Dani Primo.

CEO en pantuflas de Web Reactiva. Programador y formador en tecnologías que cambian el mundo y a las personas. Activo en linkedin, en substack y canal @webreactiva en telegram

12 recursos para developers cada domingo en tu bandeja de entrada

Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.