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.

¿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.
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:
- Rampa.
sva de 0 a 1 conpower2.in, así que el coche acelera de verdad. A mitad de subida se encienden los ventiladores. - 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.
- 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.
- 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:
fovKicksube 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:
AfterimagePassmezcla cada fotograma con el anterior. Solo se activa cuando hay velocidad, así que en el resto de la secuencia no consume nada. - Temblor:
shakesube de golpe en el despegue y en el aterrizaje y cae conpower2.out. El desplazamiento usa senos del tiempo de simulación, noMath.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:
- 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.
- Timeline en pausa. GSAP no avanza solo: en cada paso, la simulación le dice
tl.time(simTime). - 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 ¶
- Tesla reveals SpaceX-powered Roadster that flies and falls with style — TechCrunch
- Tesla’s new Roadster takes flight during dramatic unveiling — Fox Business
- What the Tesla Roadster demo actually showed — Business Upturn
- CatmullRomCurve3 y EffectComposer — documentación de Three.js
- ScrollTrigger — documentación de GSAP
¿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
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.