i feel fine — JS1K 2019 Entry #4159 — 980 bytes

A real-time raymarched rotating tunnel rendered in WebGL, packed into 980 bytes of JavaScript. The entire scene — geometry, lighting, fog, and animation — lives in a single GLSL fragment shader.

AuthorAlexander Timoshenko
Competitionwebgl
Year2019 (10th anniversary JS1K)
Bytes980 / 1024
TechniqueRaymarching / SDF

The JavaScript is almost entirely boilerplate: compile two shaders, link a program, upload a full-screen triangle, then drive a uniform float T (time) via setInterval. All the visual work happens on the GPU.

The demo uses the classic JS1K trick of aliasing WebGL methods by their first and seventh characters — for(B in g) g[B[0]+B[6]] = g[B] — so calls like createShader become cS, saving dozens of bytes.

Key Techniques
  • Full-screen triangle rendered with 3 vertices from a 6-byte Int8Array — two triangles worth of positions packed as (0,0), (0,6), (6,0) in BYTE format
  • WebGL method aliasing via for(B in g) g[B[0]+B[6]] = g[B] — e.g. createShader→cS, shaderSource→sS, compileShader→cS, useProgram→ug
  • Raymarching loop: 100 steps along each ray from a fixed eye, using a signed distance function to find scene intersections
  • bo(p) — a box SDF that defines the tunnel cross-section; the tunnel is an infinite repeating box
  • s(p) — the full scene SDF: combines fract()-based repetition, smoothstep for the mosaic tile pattern on the walls, and min(t, bo(p)) to union the tile and tunnel geometry
  • Surface normal computed by the central-difference tetrahedron trick: four SDF samples with offsets e.yxx, e.xxy, e.xyx, e.yyy
  • Camera rotation uses a 2D rotation matrix built from vec2(sin(3+T), cos(3+T)) — the same vector reused as #define rt for both the box orientation and the light direction
  • Ray twist: p.xy is rotated by .05*T each step, so the tunnel appears to corkscrew around the viewer
Shader at a Glance

Vertex shader — trivial pass-through. Takes a vec2 p attribute, outputs gl_Position = vec4(p-1, 0, 1) and passes u = p-1 (the NDC UV) to the fragment shader.

Fragment shader — does all the work:

  1. Build ray direction d = normalize(vec3(u, 1)) from the UV.
  2. March 100 steps; at each step twist p.xy by the time-varying rotation.
  3. When a hit is found (h < e), compute the surface normal and a diffuse term l.
  4. Colour is vec3(.5, l, l) minus fog (t*.05); box-hit cells are tinted cyan-blue.
This is the js1k entry, which no longer works.
I had Claude fix it and expand upon it.

You can run it in the Expanded tab.

for(_='for~uni~mZ3.+T)YfloatXp.WWz)Rs(QabQOcoQNbo(M0,L, K0KJ; H);GG in(.5(A){vec2) return )+e..05xy3  + max()K = sScS(3563void ma3(varyg  uHlength(g)O1G}`GceGaS(PKAG * normalize(*Qpe.X ~(B  gg[ B[0]+B[6]]g[B];with(g	PcP(G3`attribute  p;gl_Position=4(u=p-1.,L 2`precision highp XHZ TH\\n#defe rt (sY,NY)\\nMppOp-*rt,1)WxKWyR-.1;}Qpg =fract(WKT+R 0.47- ;gsmoothstep(0.3K1.K25.ggGt.25(5.-2(NWx)sT+R)Wx1.-Wy))GmtKMp)G}duK1)oLL-1c,p,n;t,h;e.002;~ (t i0Hi < 100Hi++	podt;WN*T)Ws*t)(WyK-WxGhQpt += h;if (h<e{ e(-e,en=e.yxxyxxxxxxyyyyyy)Gl dot(1,-rt- p n 0.c =(-Od.x*.2)+,l,l))-(t*Gif (Mp== hc =l-L,1t=0.;} }gl_FragColor4(cKlo(PGug(PGbf34962KcB()GeV(0GvA(J2K512JJJ0GbD,Int8Array.of=LLL6,6,035044GsetInterval(`g.Z1f(g.gf(PK"T"A++*Gg.dr(6KJ3G`K16G}';G=/[^	 -FIPSTV[-}]/.exec(_);)with(_.split(G))_=join(shift());eval(_)
var P = g.createProgram()

// Vertex shader: passes screen-space UV to the fragment shader
var vert = g.createShader(g.VERTEX_SHADER)
g.shaderSource(vert, `attribute vec2 p;
varying vec2 u;
void main() {
  gl_Position = vec4(u = p - 1., 0, 1);
}`)
g.compileShader(vert)
g.attachShader(P, vert)

// Fragment shader: raymarches the rotating tunnel scene
var frag = g.createShader(g.FRAGMENT_SHADER)
g.shaderSource(frag, `precision highp float;
varying vec2 u;
uniform float T;
#define rt vec2(sin(3. + T), cos(3. + T))

float bo(vec3 p) {
  p = abs(p - .5 * vec3(rt, 1));
  return max(max(p.x, p.y), p.z) - .1;
}

float s(vec3 p) {
  vec3 g = fract(vec3(p.xy, T + p.z) * 0.47) - .5;
  g = smoothstep(0.3, 1., 25. * g * g);
  float t = .25 * (5. - max(
    2.5 * (cos(p.x) * sin(T + p.z)) + length(g) + abs(p.x),
    length(g) + abs(1. - p.y)
  ));
  return min(t, bo(p));
}

void main() {
  vec3 d = normalize(vec3(u, 1)), o = vec3(0, 0, -1);
  vec3 c, p, n;
  float t, h;
  float e = .002;
  for (int i = 0; i < 100; i++) {
    p = o + d * t;
    p.xy = cos(.05 * T) * p.xy + sin(.05 * t) * vec2(p.y, -p.x);
    h = s(p);
    t += h;
    if (h < e) {
      vec2 e = vec2(-e, e);
      n = normalize(
        e.yxx * s(p + e.yxx) +
        e.xxy * s(p + e.xxy) +
        e.xyx * s(p + e.xyx) +
        e.yyy * s(p + e.yyy)
      );
      float l = max(dot(normalize(vec3(1, -rt) - p), n), 0.);
      c = (-abs(d.x * .2) + vec3(.5, l, l)) - (t * .05);
      if (bo(p) == h) c = l - vec3(0, .5, 1), t = 0.;
    }
  }
  gl_FragColor = vec4(c, 1);
}`)
g.compileShader(frag)
g.attachShader(P, frag)
g.linkProgram(P)
g.useProgram(P)

// Full-screen triangle: 3 BYTE vertices covering the entire viewport
var buf = g.createBuffer()
g.bindBuffer(g.ARRAY_BUFFER, buf)
g.bufferData(g.ARRAY_BUFFER, Int8Array.of(0, 0, 0, 6, 6, 0), g.STATIC_DRAW)
var loc = g.getAttribLocation(P, 'p')
g.enableVertexAttribArray(loc)
g.vertexAttribPointer(loc, 2, g.BYTE, false, 0, 0)

// Animate: increment T each frame and redraw
var T = 0
setInterval(function tick() {
  g.uniform1f(g.getUniformLocation(P, 'T'), T++ * .05)
  g.drawArrays(g.TRIANGLES, 0, 3)
}, 16)
// JS1K 2019 #4159 - "i feel fine" by Alexander Timoshenko // Commented version: every line explained
// createProgram() returns a new empty program object that shaders attach to.
var P = g.createProgram()
/* VERTEX_SHADER = 35633. This shader runs once per vertex (3 times total for the full-screen triangle). It receives a 2D attribute p, computes clip-space position, and passes u = p - 1 as a varying so the fragment shader gets the screen-space UV. */
var vert = g.createShader(g.VERTEX_SHADER) g.shaderSource(vert, `attribute vec2 p; varying vec2 u; void main() { gl_Position = vec4(u = p - 1., 0, 1); }`) g.compileShader(vert) g.attachShader(P, vert)
/* FRAGMENT_SHADER = 35632. This shader runs once per pixel. It does all the rendering work — the entire tunnel scene is computed here on the GPU via raymarching. */
var frag = g.createShader(g.FRAGMENT_SHADER) g.shaderSource(frag, `precision highp float; varying vec2 u;
// screen UV passed from vertex shader
uniform float T;
// time value, incremented each frame by JS
/* rt is a rotating 2D unit vector driven by time T. It is reused in three places: the box SDF orientation, the light direction, and the camera twist — all from one define. */
#define rt vec2(sin(3. + T), cos(3. + T))
/* Box SDF: signed distance to an axis-aligned box centred at 0.5, half-size 0.1. The box xy axes are tilted by the rt rotation, so it slowly spins as T advances. Returns negative inside the box, zero on its surface, positive outside. */
float bo(vec3 p) { p = abs(p - .5 * vec3(rt, 1)); return max(max(p.x, p.y), p.z) - .1; }
/* Scene SDF: minimum distance to anything in the scene. fract(vec3(p.xy, T+p.z) * 0.47) - .5 tiles 3D space into repeating unit cells. smoothstep(0.3, 1., 25.*g*g) sharpens tile edges into a mosaic pattern. The .25*(5. - max(...)) formula builds an inward-curved tunnel wall profile. min(t, bo(p)) unions the mosaic surface with the rotating inner box. */
float s(vec3 p) { vec3 g = fract(vec3(p.xy, T + p.z) * 0.47) - .5; g = smoothstep(0.3, 1., 25. * g * g); float t = .25 * (5. - max( 2.5 * (cos(p.x) * sin(T + p.z)) + length(g) + abs(p.x), length(g) + abs(1. - p.y) )); return min(t, bo(p)); } void main() {
// Build a ray per pixel: direction d points into the screen, origin o is behind it.
vec3 d = normalize(vec3(u, 1)), o = vec3(0, 0, -1); vec3 c, p, n; float t,
// accumulated distance marched along the ray
h;
// SDF value at the current sample point
float e = .002;
// hit threshold: closer than this = surface
/* Raymarching loop: take up to 100 steps. Each step is safe to advance by h (the SDF guarantees no overshoot). */
for (int i = 0; i < 100; i++) { p = o + d * t;
/* * Twist: rotate p.xy by a small angle that grows with both T and t. * This makes the tunnel appear to corkscrew around the viewer over time. */
p.xy = cos(.05 * T) * p.xy + sin(.05 * t) * vec2(p.y, -p.x); h = s(p); t += h; if (h < e) {
// hit — compute surface normal and colour
/* Tetrahedron normal estimation: sample s() at 4 offset points. The swizzled sign patterns (yxx, xxy, xyx, yyy) form a tetrahedron. Their weighted sum approximates the gradient vector = surface normal. */
vec2 e = vec2(-e, e); n = normalize( e.yxx * s(p + e.yxx) + e.xxy * s(p + e.xxy) + e.xyx * s(p + e.xyx) + e.yyy * s(p + e.yyy) );
/* Diffuse term: dot product of normal with direction toward the light. The light is at vec3(1, -rt) — a point that orbits with the scene rotation. */
float l = max(dot(normalize(vec3(1, -rt) - p), n), 0.);
/* Base colour: teal-green shifted by diffuse l, darkened by fog (t*.05) and edge-darkened by abs(d.x*.2) for a vignette effect. */
c = (-abs(d.x * .2) + vec3(.5, l, l)) - (t * .05);
/* Box hits get a cyan-blue tint instead, and t resets so the ray continues past the box interior (tunnel-within-tunnel effect). */
if (bo(p) == h) c = l - vec3(0, .5, 1), t = 0.; } } gl_FragColor = vec4(c, 1);
// write final colour, fully opaque
}`) g.compileShader(frag) g.attachShader(P, frag) g.linkProgram(P) g.useProgram(P)
/* Upload geometry: a single full-screen triangle as 3 (x,y) BYTE vertex pairs. Vertices (0,0), (0,6), (6,0) — after the vertex shader subtracts 1 they become (-1,-1), (-1,5), (5,-1), a triangle large enough to cover the entire [-1,1] NDC viewport. */
var buf = g.createBuffer() g.bindBuffer(g.ARRAY_BUFFER, buf) g.bufferData(g.ARRAY_BUFFER, Int8Array.of(0, 0, 0, 6, 6, 0), g.STATIC_DRAW) var loc = g.getAttribLocation(P, 'p') g.enableVertexAttribArray(loc) g.vertexAttribPointer(loc, 2, g.BYTE, false, 0, 0)
/* Animation: every 16ms upload the new time value and draw the triangle. T++ * .05 converts integer frame count to a slowly advancing float. */
var T = 0 setInterval(function tick() { g.uniform1f(g.getUniformLocation(P, 'T'), T++ * .05) g.drawArrays(g.TRIANGLES, 0, 3) }, 16)
The HTML shim injected into the iframe for every tab. The tab’s JavaScript is appended after the closing </script>.
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box }
    body { background: #000 }
    canvas { display: none; position: absolute; top: 0; left: 0 }
  </style>
</head>
<body>
<canvas id="c" width="512" height="512"></canvas>
<script>
  var a = document.getElementById('c')
  var b = document.body
  var g = a.getContext('webgl') || a.getContext('experimental-webgl')
</script>
<!-- tab source injected here as a <script> tag after 3 seconds -->
</body>
</html>