PageSourceSearch

https://ilmenit.github.io/assets/js/hero/mona.js

js ilmenit.github.io collected 2026-10-03 09:20:12 UTC 4,483 bytes, 130 lines download raw bytes

1/*
2  Mona — port of ilmenit's 250/256 byte Atari intro.
3  64 brush strokes of cycling color, driven by a 32-bit Galois LFSR.
4  Each stroke: length = (64 - i) * 32 pixels; LFSR picks axis/direction per pixel.
5  Seeds and algorithm match the original; see source notes in README.
6*/
7(function () {
8  const BRUSH_PRE = 0x7EC8;
9  const BRUSH = [
10    0x030A, 0x37BE, 0x2F9B, 0x072B, 0x0E3C, 0xF59B, 0x8A91, 0x1B0B,
11    0x0EBD, 0x9378, 0xB83E, 0xB05A, 0x70B5, 0x0280, 0xD0B1, 0x9CD2,
12    0x2093, 0x209C, 0x3D11, 0x26D6, 0xDF19, 0x97F5, 0x90A3, 0xA347,
13    0x8AF7, 0x0859, 0x29AD, 0xA32C, 0x7DFC, 0x0D7D, 0xD57A, 0x3051,
14    0xD431, 0x542B, 0xB242, 0xB114, 0x8A96, 0x2914, 0xB0F1, 0x532C,
15    0x0413, 0x0A09, 0x3EBB, 0xE916, 0x1877, 0xB8E2, 0xAC72, 0x80C7,
16    0x5240, 0x8D3C, 0x3EAF, 0xAD63, 0x1E14, 0xB23D, 0x238F, 0xC07B,
17    0xAF9D, 0x312E, 0x96CE, 0x25A7, 0x9E37, 0x2C44, 0x2BB9, 0x2139
18  ];
19
20  // sepia/cream palette tuned to evoke the original intro
21  const PALETTE = [
22    [0x14, 0x0C, 0x06, 0xFF], // 0 deep brown
23    [0xEA, 0xCB, 0x8A, 0xFF], // 1 cream
24    [0xC8, 0x8A, 0x55, 0xFF], // 2 sepia
25    [0x6C, 0x40, 0x22, 0xFF], // 3 dark sienna
26  ];
27
28  class MonaEffect {
29    constructor(canvas) {
30      this.canvas = canvas;
31      this.ctx = canvas.getContext('2d', { alpha: false });
32      this.W = 128;
33      this.H = 128;
34      this.canvas.width = this.W;
35      this.canvas.height = this.H;
36      this.img = this.ctx.createImageData(this.W, this.H);
37      this._raf = 0;
38      this._done = false;
39    }
40
41    reset() {
42      // fill with color 0
43      const d = this.img.data;
44      const [r, g, b, a] = PALETTE[0];
45      for (let i = 0; i < d.length; i += 4) {
46        d[i] = r; d[i+1] = g; d[i+2] = b; d[i+3] = a;
47      }
48      // LFSR state
49      this.d6 = ((BRUSH_PRE << 16) >>> 0);
50      this.strokeIdx = 0;
51      this.pixelsInStroke = 0;
52      this.strokeLen = 0;
53      this.col = 0;
54      this.currentOp = 0x5245; // default: ADDQ.W #1, D5 (y+=1)
55      this._beginStroke();
56      this._done = false;
57    }
58
59    _beginStroke() {
60      if (this.strokeIdx >= BRUSH.length) { this._done = true; return; }
61      // replace low word of d6 with next brush seed (keep high word from previous LFSR state)
62      this.d6 = ((this.d6 & 0xFFFF0000) | BRUSH[this.strokeIdx]) >>> 0;
63      // initial coords from low word of d6
64      this.d4 = this.d6 & 0xFF;                // x seed
65      this.d5 = (this.d6 >>> 8) & 0xFF;        // y seed
66      this.col = (this.col + 1) & 3;
67      this.strokeLen = (64 - this.strokeIdx) * 32;
68      this.pixelsInStroke = 0;
69    }
70
71    _step() {
72      // one LFSR+putpixel iteration
73      const carry = (this.d6 & 0x80000000) !== 0;
74      this.d6 = ((this.d6 << 1) >>> 0);
75      if (carry) {
76        this.d6 = (this.d6 ^ 0x04C11DB7) >>> 0;
77        // patch SMC: choose axis (bit 1) and direction (bit 7)
78        let op = 0x5245;
79        if ((this.d6 & 0x02) !== 0) op -= 1;        // axis: 0 -> D5 (y), 1 -> D4 (x)
80        if ((this.d6 & 0x80) !== 0) op |= 0x0100;   // direction: 0 -> +, 1 -> -
81        this.currentOp = op;
82      }
83      // execute current op: add/sub 1 to x or y
84      const delta = (this.currentOp & 0x0100) ? -1 : 1;
85      if ((this.currentOp & 1) === 1) this.d5 = (this.d5 + delta) & 0xFFFF;
86      else                             this.d4 = (this.d4 + delta) & 0xFFFF;
87      // mask to 128x128
88      const x = this.d4 & 0x7F;
89      const y = this.d5 & 0x7F;
90      // putpix
91      const idx = (y * this.W + x) * 4;
92      const c = PALETTE[this.col];
93      this.img.data[idx]     = c[0];
94      this.img.data[idx + 1] = c[1];
95      this.img.data[idx + 2] = c[2];
96      this.img.data[idx + 3] = c[3];
97    }
98
99    start() {
100      this.reset();
101      const frame = () => {
102        // budget per frame so painting takes a few seconds
103        const perFrame = 480;
104        for (let i = 0; i < perFrame; i++) {
105          if (this._done) break;
106          this._step();
107          this.pixelsInStroke++;
108          if (this.pixelsInStroke >= this.strokeLen) {
109            this.strokeIdx++;
110            this._beginStroke();
111          }
112        }
113        this.ctx.putImageData(this.img, 0, 0);
114        if (this._done) {
115          // finished; hold the final frame
116          return;
117        }
118        this._raf = requestAnimationFrame(frame);
119      };
120      this._raf = requestAnimationFrame(frame);
121    }
122
123    stop() {
124      if (this._raf) cancelAnimationFrame(this._raf);
125      this._raf = 0;
126    }
127  }
128
129  window.MonaEffect = MonaEffect;
130})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.