A generator and a discriminator fight live over a 2D target, failure modes and all.
Two small neural networks with hand-written backprop and Adam play the adversarial game behind early image synthesis. The discriminator learns to tell real points from generated ones, and its belief over the whole plane is the background, warm for 'real' and cool for 'fake', with its decision boundary traced by marching squares and drifting motes following the gradient that pushes every generated point. The generator turns 2D Gaussian noise into points, each sample keeping its own noise vector and latent color so you can watch it flow as the weights change, and the faint mesh is a grid in latent space warped through the generator, showing the thin bridges it stretches between modes. Switch between the non-saturating GAN loss, which can oscillate and drop modes, and WGAN-GP, whose gradient penalty is trained with a finite-difference double backprop, and the side panel tracks both losses and how many modes the generator actually hits.
Try it. Drag a mode to move it and watch the generator chase it, click empty space to add a mode, right-click to remove one, or drag to turn the spiral. Pick Ring, Grid or Spiral (keys 1 to 3), switch GAN and WGAN-GP (W), set each network's learning rate with the sliders to provoke collapse or calm it, R resets the weights and Space pauses training.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a live 2D generative adversarial network (GAN) demo with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Make a dark canvas that fills the window and stays sharp on high-DPI screens.
- The target distribution is 8 small Gaussian blobs arranged in a ring of radius about 1.3 (standard deviation about 0.07). Write a function that samples a real point.
- Write a tiny multilayer perceptron by hand with Float32Arrays: forward pass, backpropagation (including the gradient with respect to the input) and the Adam optimizer. Use leaky ReLU hidden layers and a linear output.
- Make two networks: a generator G (2D Gaussian noise in, 2D point out, three hidden layers of 32) and a discriminator D (2D point in, one logit out, same size).
- Each training iteration: update D with binary cross-entropy on a batch of 32 real and 32 generated points, then update G with the non-saturating loss -log D(G(z)) by backpropagating through D to its input and on into G. Run as many iterations per frame as fit in about 6 ms. Use Adam with learning rate 2e-3 and beta1 = 0.5.
- Draw the real points in white and 500 generated points (with fixed noise vectors, so you can watch each one move) in color.
Once that works, make it beautiful:
- Evaluate D on a coarse grid every few frames and paint it as a background heatmap, warm where D says real and cool where it says fake, upscaled smoothly with drawImage.
- Give each generated point a short fading trail and color it by the angle of its noise vector.
- Let me drag the real modes around with the mouse so the generator has to chase them.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as a WGAN-GP critic with a gradient penalty, a warped grid showing how G maps latent space, or learning rate sliders to provoke mode collapse.