Chaos game: dragon curve, seen from each map’s own centre

WebGL2 sample in the CS315 style: ES modules (.mjs), let/const, shaders fetched
from shaders/, program built with Common/initShadersJS.mjs.

Run

Put this folder next to your Common/ folder (it imports ../Common/initShadersJS.mjs),
serve the tree over http (modules do not load from file://), open app.html.

Files

file job
app.html page and controls
main.mjs controller: the only file that touches the DOM
chaosgame.mjs ChaosGame: DOM-free renderer (buffers, uniforms, gl.POINTS)
ifs.mjs pure maths: 3x3 matrices, the empathic factorisation, the chaos-game loop
shaders/chaos.vert, chaos.frag one vertex per point; frag clips the centre handles to discs
test/verify.mjs node test/verify.mjs: maths and renderer against a mock GL, no dependencies
test/smoke.mjs npm i jsdom, then node test/smoke.mjs: runs the real controller; uses your real ../Common

The idea

A map is described from its own centre c (its fixed point), with scale s and rotation theta:

A = T(c) . R(theta) . S(s) . T(-c)

The brief’s two matrices become c0 = (0, 0), c1 = (0.6, 0.2), s = 1/sqrt(2), theta = 45 and 135 degrees.
Moving a centre keeps s and theta and recomputes the matrix, so the translation column follows.

Colour: “colour by last n” colours each point by the string of its last n map choices
(2^n colours). The newest choice is the most significant digit, so the big pieces of the
dragon get distinct hues and the finer pieces vary within them. The colour is a byte per
point, read by the shader as t in 0..1 and mixed between the two map colours; n = 1 is the
plain two-colour view.

Each point is p' = A_k . (x, y, 1)^T, with k drawn at random. The sequence of k is drawn once
(seeded) and reused, so point i stays point i while a centre moves and the picture deforms
instead of shimmering. Centre 0 is a fixed point of map 0, so it lies on the attractor and
the orbit needs no burn-in.

Possible next steps