{"name":"primordial.nu","source":"// primordial.nu — primordial-soup particle chemistry in NURL\n//\n// Five elementary particle types, each with its own fixed velocity:\n//\n//     A  (bit 0):  moves right       (vx = +1)\n//     B  (bit 1):  moves left        (vx = -1)\n//     C  (bit 2):  moves down        (vy = +1)\n//     D  (bit 3):  moves up          (vy = -1)\n//     X  (bit 4):  catalyst          random-walks\n//\n// When particles share a cell they form a COMPOUND — their bitmasks\n// are OR'd together. The compound's velocity is the componentwise\n// sum of each set bit's contribution, so AB (right + left) sits\n// still, AC drifts south-east, ABCD is also still, and so on.\n//\n// Catalysts are disruptive: if an X ends up in a cell with anything\n// else, the whole mixture SCATTERS — each non-X bit breaks back off\n// into its own particle and diffuses one cell outward. Meanwhile\n// X itself keeps drifting on its random walk.\n//\n// So the system breathes: random collisions knit particles into\n// ever-larger bound compounds; catalyst strikes tear them apart;\n// drift carries the pieces off to collide somewhere else.\n//\n// Try tweaking PARTICLES / CATALYSTS, adding a bit, or changing\n// the per-bit velocity — radically different regimes emerge.\n//\n// NURL features on display:\n//   - Top-level constants (: i NAME value)\n//   - Mutable globals (: ~ i NAME value) — the RNG state\n//   - Bitwise & / | on i64\n//   - Nested prefix arithmetic and chained ternaries\n//   - Heap arrays via malloc + # *i cast\n//   - Toroidal wrap-around via modular arithmetic\n\n// ─── Config ───────────────────────────────────────────────────\n\n: i W 60  // grid width  (display cells per row)\n: i H 18  // grid height (rows)\n: i SLOTS 128  // particle slots (>= PARTICLES + CATALYSTS,\n//                  plus room for scatter)\n: i PARTICLES 56  // live particles at t=0 (A/B/C/D mix)\n: i CATALYSTS 6  // extra X particles mixed in\n: i TICKS 120  // total simulation ticks\n: i FRAME_EVERY 20  // snapshot every N ticks\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// ─── RNG: 31-bit LCG ──────────────────────────────────────────\n\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// ─── Velocity from bitmask (ignores X) ────────────────────────\n\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// Positive-modulo wrap: (n mod m + m) mod m\n@ wrap i n i m → i { ^ % + m % n m m }\n\n// ─── Glyph table ──────────────────────────────────────────────\n//\n// Single chars that hint at each compound's drift direction:\n//\n//     >  <  v  ^  : single movers (A, B, C, D)\n//     ─  │      : stationary (AB=horizontal cancel, CD=vertical)\n//     ↘ ↗ ↙ ↖  : diagonals (AC, AD, BC, BD)\n//     ⇓ ⇑ ⇒ ⇐  : triples with one net direction\n//     ●        : ABCD stationary cluster\n//     ✦        : catalyst (X dominates glyph)\n//     #        : anything else\n\n@ glyph i b → s {\n    ? == b 0 { ^ ` ` } {}\n    ? != 0 & b BIT_X { ^ `✦` } {}\n    ? == b 1 { ^ `>` } {}\n    ? == b 2 { ^ `<` } {}\n    ? == b 4 { ^ `v` } {}\n    ? == b 8 { ^ `^` } {}\n    ? == b 3 { ^ `─` } {}\n    ? == b 12 { ^ `│` } {}\n    ? == b 5 { ^ `↘` } {}\n    ? == b 9 { ^ `↗` } {}\n    ? == b 6 { ^ `↙` } {}\n    ? == b 10 { ^ `↖` } {}\n    ? == b 7 { ^ `⇓` } {}\n    ? == b 11 { ^ `⇑` } {}\n    ? == b 13 { ^ `⇒` } {}\n    ? == b 14 { ^ `⇐` } {}\n    ? == b 15 { ^ `●` } {}\n    ^ `#`\n}\n\n// ─── Slot helpers ─────────────────────────────────────────────\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// ─── Simulation tick ──────────────────────────────────────────\n\n@ tick * i xs * i ys * i bs i N → v {\n\n    // 1) Move each live particle\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            // Catalysts random-walk in addition to their baseline velocity\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\n    // 2) Resolve same-cell collisions (pairwise)\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                        // SCATTER: i_ keeps only X, j_ dies, and each\n                        // non-X bit of merged spawns a new particle\n                        // displaced by ±1 so it doesn't instantly\n                        // re-collide with the catalyst.\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                        // Plain merge: i_ absorbs j_\n                        = . bs i_ merged\n                        = . bs j_ 0\n                    }\n                } {}\n                = j_ + j_ 1\n            }\n        } {}\n        = i_ + i_ 1\n    }\n}\n\n// ─── Rendering ────────────────────────────────────────────────\n\n@ render i tick_no * i xs * i ys * i bs i N → v {\n    // Cell bitmask buffer\n    : *i grid # *i ( malloc * * W H 8 )\n    : ~ i g 0\n    ~ < g * W H {\n        = . grid g 0\n        = g + g 1\n    }\n\n    : ~ i k 0\n    ~ < k N {\n        : i b . bs k\n        ? != 0 b {\n            : i idx + * . ys k W . xs k\n            = . grid idx | . grid idx b\n        } {}\n        = k + k 1\n    }\n\n    ( nurl_print `\\n── tick ` )\n    ( nurl_print ( nurl_str_int tick_no ) )\n    ( nurl_print ` ` )\n    : ~ i dash 0\n    ~ < dash - W 12 {\n        ( nurl_print `─` )\n        = dash + dash 1\n    }\n    ( nurl_print `\\n┌` )\n    : ~ i d2 0\n    ~ < d2 W { ( nurl_print `─` ) = d2 + d2 1 }\n    ( nurl_print `┐\\n` )\n\n    : ~ i y 0\n    ~ < y H {\n        ( nurl_print `│` )\n        : ~ i x 0\n        ~ < x W {\n            ( nurl_print ( glyph . grid + * y W x ) )\n            = x + x 1\n        }\n        ( nurl_print `│\\n` )\n        = y + y 1\n    }\n\n    ( nurl_print `└` )\n    : ~ i d3 0\n    ~ < d3 W { ( nurl_print `─` ) = d3 + d3 1 }\n    ( nurl_print `┘\\n` )\n}\n\n// ─── Init + main loop ─────────────────────────────────────────\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 with PARTICLES random single-bit movers\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\n    // Sprinkle catalysts\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    ( nurl_print `\\n╔═════════════════════════════════════════════════════════════════╗\\n` )\n    ( nurl_print `║  primordial life — particle chemistry in NURL                   ║\\n` )\n    ( nurl_print `║    A >    B <    C v    D ^    X ✦ (catalyst, scatters)        ║\\n` )\n    ( nurl_print `║    compounds: AB ─ , CD │ , AC ↘ , AD ↗ , BC ↙ , BD ↖ , ABCD ●  ║\\n` )\n    ( nurl_print `╚═════════════════════════════════════════════════════════════════╝\\n` )\n\n    ( render 0 xs ys bs SLOTS )\n\n    : ~ i t 1\n    ~ <= t TICKS {\n        ( tick xs ys bs SLOTS )\n        ? == 0 % t FRAME_EVERY { ( render t xs ys bs SLOTS ) } {}\n        = t + t 1\n    }\n\n    ( nurl_print `\\n…time marches on. Final tick = ` )\n    ( nurl_print ( nurl_str_int TICKS ) )\n    ( nurl_print `.\\n` )\n    ^ 0\n}\n","bytes":9859}