26. Tiempo continuo, pasos discretos y bucle de simulación

Las ecuaciones describen tiempo continuo, pero un programa actualiza números en instantes separados. Elegir y controlar esos pasos es esencial para obtener simulaciones estables.

26.1 Del modelo continuo al programa

En una ecuación como x(t), el tiempo puede tomar cualquier valor. Una computadora solo evalúa una cantidad finita de instantes: t0, t1, t2 y así sucesivamente.

El intervalo entre dos estados es el paso temporal, habitualmente llamado dt. Durante cada paso aproximamos cómo cambian posición, velocidad y otras variables.

Discretizar no significa que el mundo avance a saltos; significa que nuestro algoritmo toma muestras y construye una aproximación.

26.2 Qué representa delta time

dt es tiempo, no cantidad de fotogramas. Si una velocidad está en m/s, dt debe expresarse en segundos para que v·dt produzca metros.

nueva posición = posición + velocidad × dt

Las API suelen informar milisegundos. Debemos dividir por 1000 antes de aplicar magnitudes expresadas por segundo.

Sin dt, sumar la misma distancia en cada cuadro hace que el movimiento dependa de la velocidad del dispositivo.

26.3 Paso variable

Con paso variable se usa el tiempo real transcurrido desde el cuadro anterior. Es sencillo y mantiene aproximadamente la velocidad cuando cambia la tasa de renderizado.

Sin embargo, un dt grande reduce precisión y puede atravesar paredes, producir resortes inestables o alterar el resultado entre ejecuciones. Conviene limitar pausas extraordinarias.

dt = Math.min((ahora − anterior) / 1000, dtMáximo)

26.4 Paso fijo

Con paso fijo la física avanza siempre con el mismo intervalo, por ejemplo 1/60 s. El render puede ocurrir a otra frecuencia. Esto mejora reproducibilidad y vuelve más predecible el error numérico.

Si un cuadro tarda demasiado, pueden ejecutarse varios pasos físicos para alcanzar el tiempo real. Un acumulador conserva el tiempo pendiente.

El paso fijo no elimina el error: solo lo hace uniforme. Reducirlo mejora precisión a cambio de más cálculos.

26.5 El bucle de simulación

Un bucle típico mide tiempo, procesa entradas, ejecuta cero o más actualizaciones físicas y dibuja. La física modifica el estado; el renderizado únicamente lo representa.

  1. medir el tiempo transcurrido;
  2. agregarlo al acumulador;
  3. mientras alcance para un paso, actualizar la física;
  4. dibujar el estado más reciente;
  5. solicitar el próximo cuadro.

Separar estas responsabilidades permite probar la física sin lienzo ni temporizador.

26.6 requestAnimationFrame

En el navegador, requestAnimationFrame solicita una función antes del próximo repintado. Recibe una marca temporal de alta resolución. No garantiza 60 cuadros por segundo.

La pestaña puede reducir o pausar llamadas cuando queda oculta. Al regresar, debemos evitar incorporar toda la pausa como un único paso físico enorme.

La función de animación no debe asumir que el intervalo es constante; debe medirlo o alimentar un acumulador fijo.

26.7 Movimiento independiente de los cuadros

function actualizar(estado, dt) {
  estado.posicion += estado.velocidad * dt;
  estado.tiempo += dt;
}

const estado = { posicion: 0, velocidad: 6, tiempo: 0 };
actualizar(estado, 0.25);
console.log(estado.posicion + ' m');
console.log(estado.tiempo + ' s');

En 0,25 s recorre 1,5 m. Cuatro pasos iguales producen los mismos 6 m que un segundo continuo en este modelo de velocidad constante.

26.8 Laboratorio: tamaño del paso

Compará la solución exacta de aceleración constante con Euler semiexplícito. Elegí dt y avanzá manualmente o reproducí la simulación.

Los resultados aparecen debajo.
Celeste: posición exacta. Amarillo: posición calculada por pasos.
Tiempo simulado0,00 s
Posición exacta0,00 m
Posición por pasos0,00 m
Error absoluto0,00 m

Simulación preparada.

26.9 Acumulador de tiempo

const paso = 1 / 60;
let acumulador = 0;

function procesarTiempo(dtReal) {
  acumulador += Math.min(dtReal, 0.25);
  let pasos = 0;
  while (acumulador >= paso) {
    pasos++;
    acumulador -= paso;
  }
  return pasos;
}

console.log(procesarTiempo(0.05));

Para 0,05 s se ejecutan 3 pasos de 1/60 s. El pequeño resto queda acumulado para el siguiente cuadro.

26.10 Estabilidad y espiral de la muerte

Si calcular un paso tarda más que el tiempo que ese paso representa, se acumula cada vez más retraso: el programa intenta ponerse al día ejecutando más pasos y empeora el problema.

Para evitarlo se limita el tiempo acumulado, se establece un máximo de pasos por cuadro y se optimiza la simulación. Descartar tiempo puede ralentizar el mundo, pero mantiene usable la aplicación.

La interpolación visual entre el estado anterior y el actual suaviza el render sin modificar la frecuencia física.

26.11 Ejercicio integrador

Un objeto se mueve a 8 m/s. Un cuadro tarda 20 ms. Calculá dt en segundos y el desplazamiento de ese paso. ¿Qué distancia recorrerá en 50 cuadros iguales?

Ver solución y código

dt = 20/1000 = 0,02 s. Recorre 8·0,02 = 0,16 m por cuadro. En 50 cuadros transcurre 1 s y recorre 8 m.

const velocidad = 8;
const milisegundos = 20;
const dt = milisegundos / 1000;
const porPaso = velocidad * dt;
const total = porPaso * 50;
console.log(dt + ' s');
console.log(porPaso + ' m');
console.log(total + ' m');

26.12 Ideas para recordar

  • El programa aproxima el tiempo continuo mediante pasos.
  • dt debe expresarse en unidades compatibles con las tasas.
  • El paso variable sigue el reloj real, pero cambia su error.
  • El paso fijo facilita reproducibilidad y estabilidad.
  • Un acumulador desacopla física y renderizado.
  • Los dt extremos deben limitarse.
  • Pasos más pequeños suelen mejorar precisión y requieren más cálculo.

En el próximo tema estudiaremos métodos numéricos simples para integrar el movimiento.