Los pájaros de este sitio parecen estar simplemente deambulando haciendo lo que sea, y ese es precisamente el punto, pero debajo de ese "lo que sea" hay una cantidad sorprendente de aritmética. Tamaño de reserva, un tope duro de cuántos pueden estar visibles a la vez, tres zonas separadas que no tienen permitido abandonar, y un pájaro construido para no sincronizarse nunca del todo con nada más en la página.

la reserva y el tope
Hay una reserva de ocho candidatos voladores de la que elegir (frailecillos, gaviotas, un charrán, un cormorán, un águila) más tres pájaros que siempre están en pantalla, posados en vez de volando (dos frailecillos y un ostrero). Cada carga de página saca un subconjunto aleatorio de la reserva voladora en vez de mostrar los ocho a la vez:
export function rollSkyColony(rand: () => number = Math.random): SkyColonyRoll {
const guaranteed = CANDIDATES.find((c) => c.guaranteed)!;
const rest = CANDIDATES.filter((c) => !c.guaranteed);
const shuffled = [...rest].sort(() => rand() - 0.5);
const extraCount = 2 + Math.floor(rand() * 3); // 2-4
const chosen = [guaranteed, ...shuffled.slice(0, extraCount)];
...
}Un candidato siempre se elige (el frailecillo más cercano, marcado guaranteed, sobre todo para que un visitante móvil, que solo ve nunca a ese único volador sin importar el viewport, siempre tenga algo moviéndose en el cielo) y luego un número aleatorio de dos a cuatro más viene del resto. Haz la cuenta y el número de pájaros volando en pantalla siempre está entre tres y cinco, nunca más, nunca menos, y combinado con los tres pájaros posados siempre presentes, el total de pájaros sentados en la página a la vez siempre es de seis a ocho. Ese es un techo deliberado, no una coincidencia de cómo cae la aleatoriedad, cada sorteo posible cae dentro de ese rango por construcción.
por qué los números de cada pájaro se ven tan deliberados
Cada candidato lleva dos valores de tiempo independientes, una duración de cruce horizontal y una duración de balanceo vertical, y los elegí específicamente para que nunca dos compartan una proporción simple:
// duration 210s / bob 233s, both prime-adjacent-ish and nowhere
// near a simple ratio; tiny 0.4px amplitude, "wanders least".
{
id: 'eagle',
species: 'eagle',
top: '4vh',
left: '50%',
width: 32,
duration: 210,
bobDuration: 233,
bobAmp: 0.4,
opacity: 0.35,
dir: 'ltr'
}Si el barrido horizontal de un pájaro y su balanceo vertical compartieran un factor común, digamos un cruce de 60 segundos y un balanceo de 20 segundos, el movimiento combinado se repetiría cada 60 segundos y un espectador paciente eventualmente notaría el bucle. Elige duraciones sin un factor compartido en su lugar y el período de repetición real se convierte en su mínimo común múltiplo, que para la mayoría de estos pares cae en algún punto de los miles de segundos. Nadie mira una cabecera el tiempo suficiente para pillar eso. Se lee como deriva orgánica porque, en cualquier escala de tiempo que una persona realmente experimenta, en la práctica nunca se repite.
El águila en concreto recibe la amplitud de balanceo más pequeña y el período de balanceo más largo de toda la reserva, 0.4 píxeles de deriva sobre 233 segundos, frente a los frailecillos que derivan un píxel entero sobre períodos una décima parte de largos. También se sienta más alta en el cielo, 4vh desde arriba, la más lejos de todo lo demás en la reserva, tanto espacial como rítmicamente. Es el único pájaro construido para no sincronizarse jamás con el ritmo de ningún otro pájaro ni siquiera por accidente, y para derivar visiblemente lo menos posible mientras lo hace, que es de donde viene realmente "el águila vuela sola," no soledad en un sentido poético, solo movimiento genuinamente no correlacionado por construcción.
tres zonas, y una lección sobre distinguir especies
Los pájaros viven por encima de una línea dura, el propio borde superior medido de la silueta de fondo de la isla, expresado en las mismas unidades vh que la posición vertical de cada candidato para que la relación se mantenga a cualquier ancho de ventana. Los peces viven por debajo de su propia línea, la línea de agua de la franja del horizonte del pie de página, definida como exactamente la fila 3 de su cuadrícula de píxeles de 5 filas (la tierra ocupa las filas 0 a 2, el mar toma las filas 3 y 4). Un pez es la excepción deliberada, cruzando esa línea brevemente en su propio ciclo largo, todo lo demás se queda estrictamente en su propio lado.
Dividí esas zonas por una razón real, no solo por pulcritud. Al principio del trabajo de decorado ambiental tuve un reporte de un "pájaro" avistado nadando en la franja del pie de página, y al revisar el código, una silueta de pez y una silueta de pájaro volando bajo eran lo bastante parecidas en forma y color de un vistazo que quien lo miraba genuinamente no podía distinguir cuál de las dos estaba viendo. El arreglo no fue solo hacer cumplir las zonas verticales con más rigor, fue hacer que las gramáticas de silueta reales fueran deliberadamente distintas entre sí, un pez se lee como una forma de torpedo de perfil lateral en un gris azulado frío, un pájaro se lee como una forma completamente distinta vista desde arriba con las alas extendidas. Dos sprites que solo tienen la forma correcta en la zona correcta, así que incluso un vistazo te dice qué estás mirando sin tener que pensarlo.
La propia línea de zona pasó por su propia mala versión antes de eso. La primera versión simplemente eligió un porcentaje de top que se veía más o menos bien en mi propia pantalla:
// first attempt: eyeballed the split, no relation to the actual art
const SKY_ZONE_MAX_TOP = '55%';Se veía bien en el ancho de mi propio viewport y se desmoronó en cuanto el propio arte de la cabecera cambió de altura, ya que el número no estaba ligado a nada real, solo era una suposición. Lo que de verdad se sostiene es medir la silueta real en vez de adivinar un porcentaje:
// Zone safety: the island-shadow layer is fixed, bottom-anchored, 42vh
// tall on desktop, so its own topmost silhouette pixel sits at
// (100-42)=58vh from the viewport top. Every candidate's `top` stays
// well clear of that line instead of a guessed percentage.la parte que casi no construí
El decorado es genuinamente el único sitio en todo este sitio que anima siquiera, todo lo demás es estático o encaja en su sitio en 70 milisegundos y se detiene. Habría sido mucho más simple dejar la cabecera y el pie de página como arte fijo, elegido a mano, tal como era realmente la primerísima versión de este sitio, cinco pájaros específicos en cinco poses específicas, sin aleatoriedad, sin lógica de persistencia, sin matemáticas de zona que acertar. Parte del código de arriba, especialmente los comentarios de tiempos coprimos, es del tipo de cosa que ningún lector notará nunca conscientemente. Sé que está ahí, y una vez que una cabecera empezó a sentirse realmente viva en vez de parecer decoración que resultaba moverse, no pude convencerme de volver a lanzar la versión estática.
el tope se sostiene incluso en un teléfono
Nada de esto es exclusivo de escritorio tampoco. Por debajo de un viewport de 768px, cada candidato salvo el único frailecillo guaranteed se oculta directamente en CSS, a propósito, para que una pantalla pequeña no se quede renderizando cuatro o cinco voladores superpuestos en una cabecera que solo tiene espacio para mostrarlos apretujados unos encima de otros. Ese único pájaro garantizado es exactamente el mismo que siempre se dibuja en escritorio también, así que el móvil no necesita un sorteo separado ni una reserva separada, simplemente descarta en silencio todo lo que el sorteo de escritorio eligió más allá de ese único pájaro. Y toda la reserva respeta prefers-reduced-motion de la misma manera que el resto del decorado del sitio, cada sprite sigue renderizándose en su propia posición de reposo, congelado en vez de ausente, nada en la página queda vacío solo porque el sistema de alguien pidiera menos movimiento.