Back to Drone Zone — deep field
SOURCE / PINNED RELEASE

Made of little things.

Drone Zone — deep field

Release
578f277a8be1
Author-recorded commit
a96742b428de…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-9753e6cf3ec

Archive hash verified: f7f3bf8ff3482551. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/field-data.ts
export interface Point { x: number; y: number; z: number }
export interface Observation { name: string; value: string; description: string; x: number; y: number; z: number; label: string }
export const observations: Observation[] = [
  { name:'THE HYDROGEN LINE', value:'1 420.4058 MHz', description:'Neutral hydrogen whispers at a wavelength of roughly 21 centimetres. Radio astronomers use this line to trace the structure and motion of interstellar gas.',x:310,y:-165,z:130,label:'H I / REST FREQUENCY' },
  { name:'A DISTANCE MADE OF TIME', value:'299 792.458 km/s', description:'The speed of light in vacuum. Looking farther into space means looking farther into the past. This beacon is a place to pause, not a measured star.',x:-410,y:105,z:80,label:'c / VACUUM CONSTANT' },
  { name:'ONE ASTRONOMICAL UNIT', value:'149 597 870.7 km', description:'The astronomical unit is an exact length, originally based on the Earth–Sun distance. Sunlight takes about 8 minutes 19 seconds to cross it.',x:640,y:295,z:180,label:'AU / REFERENCE DISTANCE' },
  { name:'THE OLDEST AFTERGLOW', value:'≈ 2.725 K', description:'The cosmic microwave background fills space with the cooled afterglow of the early universe. Almost uniform. Never quite empty.',x:-670,y:-280,z:110,label:'CMB / MEAN TEMPERATURE' },
  { name:'THE PALE BLUE INTERVAL', value:'≈ 8 min 19 s', description:'The light reaching Earth from the Sun left about eight minutes ago. Even the most familiar daylight arrives with a little history.',x:65,y:345,z:90,label:'SOL → TERRA / LIGHT TIME' },
  { name:'A YEAR OF LIGHT', value:'≈ 9.461 × 10¹² km', description:'A light-year measures distance, not time: the distance light travels in one Julian year. Our drawn trajectories are an imagined atlas.',x:735,y:-340,z:150,label:'LY / DISTANCE UNIT' },
  { name:'THE LISTENING POST', value:'DRONE ZONE', description:'A receiver in an imagined field. The linked Nostr station supplies the real radio stream. The moving signal traces are generative artwork, not an audio spectrum.',x:-170,y:-340,z:200,label:'RX / AMBIENT TRANSMISSION' },
];

observations.push(
  { name:'THE LONG WAY HOME', value:'H01 / OUTER APPROACH', description:'A slow lane through the outer field. All its travelers follow the same curve, separated only by time. This is the beginning of our imagined highway.', x:-1700,y:510,z:90,label:'H01 / OUTER APPROACH' },
  { name:'THE LAGRANGE GARDEN', value:'05 / QUIET ORBITS', description:'Five nested ellipses turn around an empty focus. An imagined garden of balanced paths, inspired by the places where gravity and orbital motion find an equilibrium.',x:-1060,y:-640,z:140,label:'L05 / ORBITAL GARDEN' },
  { name:'THE SIGNAL THAT RETURNED', value:'ECHO / +0042.8 s', description:'An artificial pulse leaves this recorder, circles the field and returns. The displayed interval belongs to our simulation. Nobody is waiting for an answer.',x:1220,y:-690,z:170,label:'E07 / ECHO RECORDER' },
  { name:'A SMALL INTERRUPTION', value:'CARRIER / ABSENT', description:'The route continues on either side, but a piece is missing. A small flash marks each departure, then the traveler accelerates through the silence and returns to its unhurried pace.',x:920,y:817,z:210,label:'GAP / CARRIER ABSENT' },
  { name:'THE SPECTRAL ARCHIVE', value:'λ / 380–750 nm', description:'A sketch of the visible spectrum, approximately violet through red. The drawn traces are a generative score, not spectral measurements from this image.',x:-1410,y:980,z:130,label:'SP / OPTICAL ARCHIVE' },
  { name:'THE FAR RECEIVER', value:'RX 12 / LISTENING', description:'A receiver at the edge of the atlas. Its little aerial follows no source in particular. Sometimes listening is the entire experiment.',x:1830,y:720,z:180,label:'RX12 / FAR RECEIVER' },
  { name:'THE PARALLAX ROOM', value:'01 / TWO PERSPECTIVES', description:'Move the field and watch the foreground marks slide against the distant light. This small difference in viewpoint is the principle behind parallax.',x:1640,y:-80,z:230,label:'PX / TWO PERSPECTIVES' },
  { name:'THE SLOW INTERCHANGE', value:'H01 × H03', description:'Routes fold over one another at different depths. None of the apparent crossings is a collision. Our imagined traffic has all the time in the universe.',x:-910,y:180,z:150,label:'J03 / SLOW INTERCHANGE' },
  { name:'AN EMPTY FREQUENCY', value:'∅ / OPEN CHANNEL', description:'There is no station here. Only a drawn horizon and a quiet frequency waiting for something to cross it. An intentional empty room in the artwork.',x:180,y:-1070,z:110,label:'F00 / OPEN CHANNEL' },
  { name:'THE DUST LIBRARY', value:'ARCHIVE / 016', description:'A small optical library of supplied space images. Its windows collect different glimpses of distant light, rather than measurements of this imagined field.',x:-2040,y:-790,z:100,label:'D16 / DUST LIBRARY' },
  { name:'ONE MORE HORIZON', value:'FIELD / CONTINUES', description:'The edge of the chart is a choice, not the edge of space. Beyond this marker, the route thins and the foreground stars keep drifting.',x:2090,y:-970,z:160,label:'HZ / OUTER HORIZON' },
  { name:'THE PATIENCE ENGINE', value:'00:00 / NO ARRIVAL', description:'A machine built to keep moving without needing to arrive. Its concentric traces make a soft clock whose only unit is attention.',x:520,y:1120,z:180,label:'T18 / PATIENCE ENGINE' },
  { name:'THE LAST LANTERN', value:'BEACON / 019', description:'One warm point at the southern margin. The highways carry on beyond the chart. The light does too.',x:-610,y:1310,z:200,label:'B19 / LAST LANTERN' },
);

export const navigation = { minZoom: .55, maxZoom: 3.6, panX: 2400, panY: 1550 };
export const tourRange = { center: .5, radius: .09 };
export interface Route {
  samples: Point[];
  color: string;
  phase: number;
  speed: number;
  weight: number;
  opacity: number;
  gaps: [number, number][];
  label: string;
  destination?: number;
}

// Each family has ONE broad change in curvature. Lanes share the shape;
// no independently perturbed waypoints, terminal spurs or local highway loops.
const families = [
  { y: 120, slope: -.1, bend: -260, center: 220, width: 1150, lanes: 30 },
  { y: -360, slope: .13, bend: 190, center: -400, width: 1600, lanes: 20 },
  { y: 850, slope: -.045, bend: 130, center: 850, width: 1350, lanes: 14 },
];
function highway(family: number, lane: number): Point[] {
  const f = families[family];
  const dense = Array.from({ length: 4097 }, (_, i) => {
    const x = -10000 + i / 4096 * 20000;
    const spread = lane * 9;
    return { x, y: f.y + f.slope*x + f.bend*Math.tanh((x-f.center)/f.width)
      + spread*(1+.13*Math.tanh((x-f.center)/f.width)), z: 100+family*55+lane*1.5 };
  });
  // Equidistant, fine samples keep traffic speed constant. Maximum chord error
  // stays subpixel even at maximum zoom; the display never decimates these.
  const distances = [0];
  for(let i=1;i<dense.length;i++) distances.push(distances[i-1]+Math.hypot(dense[i].x-dense[i-1].x,dense[i].y-dense[i-1].y));
  let cursor=1;
  return Array.from({length:2049},(_,i)=>{
    const target=i/2048*distances[distances.length-1];
    while(cursor<dense.length-1&&distances[cursor]<target)cursor++;
    const a=dense[cursor-1],b=dense[cursor],t=(target-distances[cursor-1])/(distances[cursor]-distances[cursor-1]);
    return {x:a.x+(b.x-a.x)*t,y:a.y+(b.y-a.y)*t,z:a.z+(b.z-a.z)*t};
  });
}
export const principalHighway = highway(0,0);
export function sampleRoute(samples: Point[], t: number): Point {
  const f = Math.max(0, Math.min(1, t)) * (samples.length - 1);
  const index = Math.min(Math.floor(f), samples.length - 2), a = samples[index], b = samples[index+1];
  return { x:a.x+(b.x-a.x)*(f-index), y:a.y+(b.y-a.y)*(f-index), z:a.z+(b.z-a.z)*(f-index) };
}
export function createRoutes(): Route[] {
  let seed=71821;
  const random=()=>{seed=(seed*1664525+1013904223)>>>0;return seed/4294967296;};
  const colors=['117,193,217','100,167,197','221,135,89','157,188,208','106,149,217','176,204,209'];
  const routes:Route[]=[{samples:principalHighway,color:'173,211,224',phase:.451,speed:.0016,weight:1.65,opacity:.58,gaps:[[.468,.476]],label:'H01 / LONGITUDE HIGHWAY'}];
  families.forEach((f,family)=>{
    for(let i=0;i<f.lanes;i++) {
      const start=family===1&&i===8?.522:family===2&&i===7?.542:0;
      const gaps:[number,number][]=start?[[start,start+(family===1?.009:.008)*(3+random())]]:[];
      routes.push({samples:highway(family,(i-(f.lanes-1)/2)),color:colors[i%6],phase:gaps.length?gaps[0][0]-.018:.36+random()*.28,
        speed:.0013+random()*.0008,weight:i%10===0?1.2:i%4===0?.8:.52,opacity:i%10===0?.38:i%4===0?.24:.15,
        gaps,label:`H0${family+1} / ${String(i+1).padStart(2,'0')}`});
    }
  });
  return routes;
}

export interface Transit { t: number; flash: number; entry: number; warping: boolean }
export function createTransit(route: Route): Transit { return {t:route.phase,flash:0,entry:0,warping:false}; }
/** Integrate at gap boundaries, so even a long frame cannot miss an entry or
 * spend its entire duration at warp speed after crossing an exit. */
export function advanceTransit(state: Transit, route: Route, seconds: number): void {
  let remaining=Math.max(0,seconds);
  state.flash=Math.max(0,state.flash-remaining);
  while(remaining>1e-9) {
    const gap=route.gaps.find(([start,end])=>state.t>=start&&state.t<end);
    const next=route.gaps.find(([start])=>start>state.t);
    const boundary=gap?gap[1]:next?next[0]:1;
    const speed=route.speed*(gap?22:1),duration=(boundary-state.t)/speed;
    if(remaining<duration) {state.t+=remaining*speed;break;}
    remaining-=duration;state.t=boundary;
    if(!gap&&next) {state.entry=boundary;state.flash=Math.max(0,.34-remaining);}
    if(state.t>=1)state.t=0;
  }
  state.warping=route.gaps.some(([start,end])=>state.t>=start&&state.t<end);
}

export interface Orbit {
  center: Point; radius: number; eccentricity: number;
  inclination: number; ascending: number; phase: number; speed: number; warm: boolean;
}
export interface PlanetarySystem { index: number; orbits: Orbit[] }
export function createSystems(): PlanetarySystem[] {
  return [8,9,12,14,18,19].map(index=>({index,orbits:Array.from({length:index===8?5:3},(_,ring)=>({
    center:observations[index],radius:42+ring*24+(index%3)*5,eccentricity:.84+ring*.025,
    inclination:.85+(index%4)*.12+ring*.025,ascending:index*.63+ring*.035,
    phase:index+ring*2.4,speed:.045/(1+ring*.5),warm:ring===2,
  }))}));
}
/** Analytic closed ellipse, rotated in 3D. Bodies and orbit lines use the same
 * function; there is no duplicated seam vertex or spline closure to kink. */
export function orbitPoint(orbit: Orbit, angle: number): Point {
  const x=Math.cos(angle)*orbit.radius,y=Math.sin(angle)*orbit.radius*orbit.eccentricity;
  const cy=Math.cos(orbit.inclination),sy=Math.sin(orbit.inclination);
  const ca=Math.cos(orbit.ascending),sa=Math.sin(orbit.ascending);
  return {x:orbit.center.x+x*ca-y*cy*sa,y:orbit.center.y+x*sa+y*cy*ca,z:orbit.center.z+y*sy};
}
export interface Camera {
  width:number; height:number; scale:number; pan:{x:number;y:number};
  cy:number; sy:number; cp:number; sp:number;
}
export function createCamera(width:number,height:number,pan:{x:number;y:number},look:{x:number;y:number},zoom:number,motion:number): Camera {
  const yaw=look.x*.06*motion+pan.x/navigation.panX*.025;
  const pitch=look.y*.045*motion+pan.y/navigation.panY*.018;
  return {width,height,scale:Math.max(width/1700,height/1080,.31)*zoom,pan,
    cy:Math.cos(yaw),sy:Math.sin(yaw),cp:Math.cos(pitch),sp:Math.sin(pitch)};
}
export function projectPoint(p:Point,c:Camera): {x:number;y:number;depth:number} {
  const x=p.x-c.pan.x,y=p.y-c.pan.y;
  const rx=x*c.cy+p.z*c.sy,rz=p.z*c.cy-x*c.sy;
  const ry=y*c.cp-rz*c.sp,z=y*c.sp+rz*c.cp;
  const depth=2800/(2800-z);
  return {x:c.width*.5+rx*c.scale*depth,y:c.height*.5+ry*c.scale*depth,depth:z};
}

export interface Detour {
  route: Route; orbit: Orbit; source: Point[]; system: number;
  first: number; last: number; width: number;
}
/** Original highways are monotonic in x. Find the precise material coordinate
 * of a rendezvous so traffic and gap markers retain their identities as it moves. */
export function routeAtX(samples: Point[], x: number): number {
  let low=0,high=samples.length-1;
  while(low<high){const middle=(low+high)>>>1;if(samples[middle].x<x)low=middle+1;else high=middle;}
  const i=Math.max(1,low),a=samples[i-1],b=samples[i];
  return (i-1+Math.max(0,Math.min(1,(x-a.x)/(b.x-a.x))))/(samples.length-1);
}
/** Reroute one existing lane per system. Preserve the principal tour lane and
 * existing gates; choose the nearest available lane in the actual 3D field. */
export function createDetours(routes: Route[], systems: PlanetarySystem[]): Detour[] {
  return systems.map((system,ordinal)=>{
    const orbit=system.orbits[system.orbits.length-2],center=orbit.center;
    let route=routes[1],distance=Infinity;
    for(const candidate of routes) {
      if(candidate===routes[0]||candidate.destination!==undefined||candidate.gaps.length)continue;
      for(let i=0;i<candidate.samples.length;i++) {
        const p=candidate.samples[i],d=Math.hypot(p.x-center.x,p.y-center.y,p.z-center.z);
        if(d<distance){distance=d;route=candidate;}
      }
    }
    const source=Array.from({length:4097},(_,i)=>sampleRoute(route.samples,i/4096)),lastSample=source.length-1;
    const width=Math.max(520,distance*1.8+orbit.radius*2.5);
    const first=Math.max(0,Math.floor(routeAtX(source,center.x-width-orbit.radius)*lastSample)-1);
    const last=Math.min(lastSample,Math.ceil(routeAtX(source,center.x+width+orbit.radius)*lastSample)+1);
    route.destination=system.index;
    route.samples=source.map(p=>({...p}));
    route.weight=Math.max(.95,route.weight);route.opacity=Math.max(.44,route.opacity);
    route.phase=routeAtX(source,center.x-width*.8);
    if(ordinal%2===0) {
      const seed=Math.sin(system.index*127.1)*43758.5453,variation=seed-Math.floor(seed);
      const entry=center.x-width*.84,exit=entry+width*.08*(3+variation);
      route.gaps=[[routeAtX(source,entry),routeAtX(source,exit)]];
      route.phase=route.gaps[0][0]-.012;
    }
    const detour={route,orbit,source,system:system.index,first,last,width};
    updateDetour(detour,0);return detour;
  });
}
/** Compact C2 deformation: position, tangent and curvature meet the untouched
 * highway at both shoulders. A single broad bend follows the orbiting body in
 * x/y/z; it cannot grow additional loops or expose a new endpoint. */
export function updateDetour(detour: Detour, time: number): void {
  const {route,source,orbit,first,last,width}=detour;
  const body=orbitPoint(orbit,orbit.phase+time*orbit.speed);
  const anchor=sampleRoute(source,routeAtX(source,body.x));
  for(let i=first;i<=last;i++) {
    const base=source[i],p=route.samples[i],u=(base.x-body.x)/width;
    const blend=Math.abs(u)<1?Math.pow(1-u*u,3):0;
    p.y=base.y+(body.y-anchor.y)*blend;p.z=base.z+(body.z-anchor.z)*blend;
  }
}