{"name":"primordial_canvas.nu","source":"// primordial_canvas.nu — primordial particle chemistry, live on a pixel canvas\n//\n// Same rules as examples/primordial.nu (compounds via bitmask OR, summed\n// velocities, catalyst scattering) but rendered as animated pixels into\n// a real canvas instead of as ASCII snapshots.\n//\n// In the browser playground the canvas element above the output animates\n// live; press Stop to exit. Natively the program opens an SDL2 window;\n// close it or press Escape to exit.\n//\n// Particle types (bitmask):\n//   A=1  →  vx=+1        (red channel)\n//   B=2  →  vx=-1        (green channel)\n//   C=4  →  vy=+1        (blue channel)\n//   D=8  →  vy=-1        (yellow = R+G)\n//   X=16 →  catalyst     (white; random-walks, scatters compounds)\n//\n// When a compound is rendered, its colour is the OR of its bits' colours,\n// so AC (red + blue) draws purple, ABCD draws all-channel-on = white,\n// and so on. Catalysts punch bright white pixels through the soup.\n\n& `canvas` @ canvas_open i w i h → *i\n\n& `canvas` @ canvas_present → v\n\n& `canvas` @ canvas_sleep i ms → v\n\n& `canvas` @ canvas_should_close → i\n\n& `canvas` @ canvas_close → v\n\n// ── Config ────────────────────────────────────────────────────\n: i W 180\n: i H 100\n: i SLOTS 360\n: i PARTICLES 240\n: i CATALYSTS 24\n: i FPS 30\n\n// Bit masks\n: i BIT_A 1\n: i BIT_B 2\n: i BIT_C 4\n: i BIT_D 8\n: i BIT_X 16\n\n// Background and particle colours (0xAARRGGBB)\n: i COL_BG 4279769112  // 0xFF181a20 — matches playground panel\n: i COL_A 4294901760  // 0xFFff0000 red\n: i COL_B 4278222848  // 0xFF00d040 green\n: i COL_C 4281784574  // 0xFF2090fe blue\n: i COL_D 4294964035  // 0xFFfff143 yellow\n: i COL_X 4294967295  // 0xFFffffff white\n\n// ── RNG ───────────────────────────────────────────────────────\n: ~ i RNG 305419896\n\n@ rand_next → i {\n    = RNG & + * RNG 1103515245 12345 2147483647\n    ^ RNG\n}\n\n@ rand_range i n → i { ^ % ( rand_next ) n }\n\n// ── Physics helpers ───────────────────────────────────────────\n@ vel_x i b → i {\n    : ~ i v 0\n    ? != 0 & b BIT_A { = v + v 1 } {}\n    ? != 0 & b BIT_B { = v - v 1 } {}\n    ^ v\n}\n\n@ vel_y i b → i {\n    : ~ i v 0\n    ? != 0 & b BIT_C { = v + v 1 } {}\n    ? != 0 & b BIT_D { = v - v 1 } {}\n    ^ v\n}\n\n@ wrap i n i m → i { ^ % + m % n m m }\n\n// Compose a colour from a compound bitmask by OR'ing per-bit colours.\n@ colour_of i b → i {\n    ? != 0 & b BIT_X { ^ COL_X } {}  // catalyst dominates\n    : ~ i c COL_BG\n    ? != 0 & b BIT_A { = c | c COL_A } {}\n    ? != 0 & b BIT_B { = c | c COL_B } {}\n    ? != 0 & b BIT_C { = c | c COL_C } {}\n    ? != 0 & b BIT_D { = c | c COL_D } {}\n    ^ c\n}\n\n@ find_free * i bs i N → i {\n    : ~ i k 0\n    ~ < k N {\n        ? == . bs k 0 { ^ k } {}\n        = k + k 1\n    }\n    ^ - 0 1\n}\n\n// ── One simulation tick ───────────────────────────────────────\n@ tick * i xs * i ys * i bs i N → v {\n    // 1) Move\n    : ~ i k 0\n    ~ < k N {\n        : i b . bs k\n        ? != 0 b {\n            : ~ i vx ( vel_x b )\n            : ~ i vy ( vel_y b )\n            ? != 0 & b BIT_X {\n                = vx + vx - ( rand_range 3 ) 1\n                = vy + vy - ( rand_range 3 ) 1\n            } {}\n            = . xs k ( wrap + . xs k vx W )\n            = . ys k ( wrap + . ys k vy H )\n        } {}\n        = k + k 1\n    }\n    // 2) Collisions\n    : ~ i i_ 0\n    ~ < i_ N {\n        ? != 0 . bs i_ {\n            : ~ i j_ + i_ 1\n            ~ < j_ N {\n                ? & != 0 . bs j_ & == . xs i_ . xs j_ == . ys i_ . ys j_ {\n                    : i merged | . bs i_ . bs j_\n                    : i xm . xs i_\n                    : i ym . ys i_\n                    ? != 0 & merged BIT_X {\n                        = . bs i_ BIT_X\n                        = . bs j_ 0\n                        : ~ i bit 1\n                        ~ <= bit 8 {\n                            ? != 0 & merged bit {\n                                : i free ( find_free bs N )\n                                ? >= free 0 {\n                                    = . bs free bit\n                                    = . xs free ( wrap + xm - ( rand_range 3 ) 1 W )\n                                    = . ys free ( wrap + ym - ( rand_range 3 ) 1 H )\n                                } {}\n                            } {}\n                            = bit * bit 2\n                        }\n                    } {\n                        = . bs i_ merged\n                        = . bs j_ 0\n                    }\n                } {}\n                = j_ + j_ 1\n            }\n        } {}\n        = i_ + i_ 1\n    }\n}\n\n// ── Render one frame into the framebuffer ────────────────────\n@ render * i fb * i xs * i ys * i bs i N → v {\n    // Clear to background\n    : ~ i g 0\n    ~ < g * W H {\n        = . fb g COL_BG\n        = g + g 1\n    }\n    // Plot each live particle\n    : ~ i k 0\n    ~ < k N {\n        : i b . bs k\n        ? != 0 b {\n            : i x . xs k\n            : i y . ys k\n            : i idx + * y W x\n            = . fb idx ( colour_of b )\n            // 2x2 block so pixels are visible even at small FPS\n            ? < + x 1 W { = . fb + idx 1 ( colour_of b ) } {}\n            ? < + y 1 H { = . fb + idx W ( colour_of b ) } {}\n            ? & < + x 1 W < + y 1 H { = . fb + idx + W 1 ( colour_of b ) } {}\n        } {}\n        = k + k 1\n    }\n}\n\n@ main → i {\n    : *i xs # *i ( malloc * SLOTS 8 )\n    : *i ys # *i ( malloc * SLOTS 8 )\n    : *i bs # *i ( malloc * SLOTS 8 )\n\n    : ~ i k 0\n    ~ < k SLOTS {\n        = . xs k 0\n        = . ys k 0\n        = . bs k 0\n        = k + k 1\n    }\n\n    // Seed particles\n    : ~ i p 0\n    ~ < p PARTICLES {\n        : i w ( rand_range 4 )\n        : i bit ? == w 0 BIT_A ? == w 1 BIT_B ? == w 2 BIT_C BIT_D\n        = . bs p bit\n        = . xs p ( rand_range W )\n        = . ys p ( rand_range H )\n        = p + p 1\n    }\n    : ~ i c 0\n    ~ < c CATALYSTS {\n        : i slot + PARTICLES c\n        = . bs slot BIT_X\n        = . xs slot ( rand_range W )\n        = . ys slot ( rand_range H )\n        = c + c 1\n    }\n\n    : *i fb ( canvas_open W H )\n    : i frame_ms / 1000 FPS\n\n    ~ == 0 ( canvas_should_close ) {\n        ( render fb xs ys bs SLOTS )\n        ( tick xs ys bs SLOTS )\n        ( canvas_present )\n        ( canvas_sleep frame_ms )\n    }\n\n    ( canvas_close )\n    ^ 0\n}\n","bytes":6674}