#version 450 #extension GL_GOOGLE_include_directive : require layout(local_size_x = 8, local_size_y = 8) in; #include "common.glsl" // Material reactions: pass 4 plans one attempt per source, pass 5 commits thermal // changes. Passes 0..3 visit disjoint horizontal/vertical pairs; one invocation // owns both outputs. Ordinary passes consume the plan, never a freshly converted material. layout(std430, set = 0, binding = 28) readonly buffer RHead { uint rhead[]; }; // [a * n + b] = (offset << 8) | count layout(std430, set = 0, binding = 23) buffer Ctrl { ivec4 inp; ivec4 inp2; ivec4 inp3; vec4 outp; vec4 outp2; vec4 view; vec4 hand0; ivec4 hand1; vec4 hand2; ivec4 burnN; ivec4 burnEv[256]; ivec4 reactN; ivec4 reactEv[64]; } ctrl; // reactEv: entities to spawn (x, y, entity index + 1) layout(std430, set = 0, binding = 29) readonly buffer RRec { uint rrec[]; }; // 5 words per entry: outs (a | b << 16, 0xFFFF = same), probability / 65536 layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; }; #include "biome_mods.glsl" const ivec2 RD4[4] = ivec2[4](ivec2(0, -1), ivec2(-1, 0), ivec2(1, 0), ivec2(0, 1)); uint canonical(uint m) { const uint n = mats[mats.length() - 1u].w; return rhead[2u * n * (n + 1u) + m]; } uint draw15(uint seed, uint salt) { return hash(seed ^ salt) & 0x7FFFu; } // One plan per source/update, stored in movement scratch after movement completes. // y: 0 idle, 1 combined, 2 fast, 3 material change. z: shared R, w: neighbour / target. void plan_reaction(ivec2 p, int i) { const uint c = cells[i], m = cell_mat(c), f = mflags(m); const uint n = mats[mats.length() - 1u].w, stride = n * (n + 1u); const uint seed = hash3(uint(p.x + pc.view_x), uint(p.y + pc.view_y), uint(pc.frame) ^ 0x7C6E0u); uint route = 2u; // Worker +0x460 forces a full/combined update after ten ordinary updates. // Initial loaded cells also receive the initial full update. const bool full = (pc.frame % 11) == 0; const bool passive = (f & F_STATIC) != 0u || ((f & F_POWDER) != 0u && (c & ATTACH_BIT) != 0u); const bool slowBranch = full && (!passive || draw15(seed, 1u) % 101u < 20u); if (slowBranch) route = !passive || draw15(seed, 2u) % 101u < 50u ? 1u : 0u; if (m == 0u || (!full && !passive && (f & F_LIQCELL) != 0u && liquid_state(uint(i), m) == 3u && (terrain_age[i] & 255u) > 49u)) route = 0u; const uint R = draw15(seed, 3u) % 10001u, dir = draw15(seed, 4u) & 3u; intent[i] = uvec4(c, route, R, dir); if (route == 1u && (c & BURN_BIT) == 0u) { const uint bb = bm_bytes(vec2(p + ivec2(pc.view_x, pc.view_y))); const uint fc = (bb >> 16) & 255u, uc = bb >> 24; if ((fc | uc) != 0u) { const uvec2 cv = bm_conversions(m); const bool cold = fc != 0u && (cv.x & 65535u) != 0u && draw15(seed, 5u) % ((cv.x >> 16) + 1u) < fc && liquid_freeze_ready(p, c); const bool warm = uc != 0u && (cv.y & 65535u) != 0u && draw15(seed, 6u) % ((cv.y >> 16) + 1u) < uc; if (cold || warm) { // A successful cold flag owns the attempt, including a blocked contact. intent[i].y = 0u; const ivec2 q = p + RD4[dir]; if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) return; const uint nm = cell_mat(cells[q.y * pc.gw + q.x]); const bool allow = cold ? nm == 0u || (mflags(nm) & F_LIQCELL) == 0u || (mflags(nm) & F_POWDER) != 0u : canonical(nm) != canonical(m); if (allow) { intent[i].y = 3u; intent[i].w = ((cold ? cv.x : cv.y) & 65535u) - 1u; } return; } } } // 0070ad30 is reached only on the passive full-update tail, after neither // reaction method returned. It is not unconditional terrain cleanup. if (full && passive && route != 1u && rhead[stride + n * n + m] == 0u && (c & BURN_BIT) == 0u && p.x > 0 && p.y > 0 && p.x + 1 < pc.gw && p.y + 1 < pc.gh) { if (cell_mat(cells[i-1]) == 0u && cell_mat(cells[i+1]) == 0u && cell_mat(cells[i-pc.gw]) == 0u && cell_mat(cells[i+pc.gw]) == 0u) { intent[i].y = 3u; intent[i].w = 0u; } } } void apply_side(ivec2 p, ivec2 q, int i, int j, uint ci, uint cj, uint mi, uint mj, uint e, uint rr) { uint outs = rrec[e]; uint third = rrec[e + 2u]; uint blob = rrec[e + 3u]; uint entityIdx = rrec[e + 4u] & 0x7FFFFFFFu; bool second = (mi == mj) && (i > j); // equal materials: the lower cell takes the first output uint o = second ? (outs >> 16) : (outs & 0xFFFFu); const bool firstIsI0 = (rrec[e + 4u] & 0x80000000u) == 0u; // (the record is the mirrored one when the first input is the second material of the pair) if (((blob >> 19) & 1u) != 0u && firstIsI0 && !second) { // destroy_horizontally_lonely_pixels: the first output becomes air when both horizontal neighbours are empty (both probed) const bool le = p.x > 0 && cell_mat(cells[i - 1]) == 0u, ri = p.x + 1 < pc.gw && cell_mat(cells[i + 1]) == 0u; if (le && ri) o = 0u; } if (o == 0xFFFFu || o == mi) return; // this side stays as it is cells[i] = o | (ci & 0x00FF0000u); // keeps the colour variation, clears burning and direction bits material_changed(i, ci, o); if (entityIdx != 0u && firstIsI0 && !second) { // the reaction loads an entity at the position of the first input (the host spawns it) const int ei = atomicAdd(ctrl.reactN.x, 1); if (ei < 64) ctrl.reactEv[ei] = ivec4(p.x + pc.view_x, p.y + pc.view_y, int(entityIdx), 0); else atomicAdd(ctrl.reactN.x, -1); } // blob_radius with blob_restrict_to_input_material (0071ab50 conditional mode): trunc(r * 2 pi + 0.5) rays of unit steps from the cell; a cell of the input material converts, // one that already holds the output lets the ray go on, anything else ends it (a flood of the connected input material up to the radius) { const uint rad = second ? ((blob >> 8) & 255u) : (blob & 255u); const bool restricted = ((blob >> (second ? 17 : 16)) & 1u) != 0u; if (rad > 0u && restricted) { const int nr = int(float(rad) * 6.2831853 + 0.5); for (int ray = 0; ray < nr; ++ray) { const float a = float(ray) * 6.2831853 / float(nr); const vec2 dir = vec2(cos(a), sin(a)); vec2 samplePos = vec2(p + ivec2(pc.view_x, pc.view_y)); for (int st = 1; st <= int(rad); ++st) { samplePos += dir; const ivec2 t = ivec2(samplePos) - ivec2(pc.view_x, pc.view_y); if (t.x < 0 || t.y < 0 || t.x >= pc.gw || t.y >= pc.gh) break; const int ti = t.y * pc.gw + t.x; const uint tc = cells[ti], tm = cell_mat(tc); if (canonical(tm) == canonical(o)) continue; if (canonical(tm) != canonical(mi)) break; if (atomicCompSwap(cells[ti], tc, o | (tc & 0x00FF0000u)) == tc) { material_changed(ti, tc, o); } // same-class movement state survives (0070d0b0) } } } else if (rad > 0u) { // direct mode (0071ab50, restriction explicitly off): every loaded cell of the disk dx^2 + dy^2 < r^2 takes the output, whatever it holds const int ri = int(rad); for (int dy = -ri; dy <= ri; ++dy) for (int dx = -ri; dx <= ri; ++dx) { if (dx * dx + dy * dy >= ri * ri) continue; const ivec2 t = p + ivec2(dx, dy); if (t.x < 0 || t.y < 0 || t.x >= pc.gw || t.y >= pc.gh) continue; const int ti = t.y * pc.gw + t.x; const uint tc = cells[ti]; if (cell_mat(tc) == o) continue; if (atomicCompSwap(cells[ti], tc, o | (tc & 0x00FF0000u)) == tc) { material_changed(ti, tc, o); } } } } } void main() { ivec2 p = ivec2(gl_GlobalInvocationID.xy); if (p.x >= pc.gw || p.y >= pc.gh) return; const int i = p.y * pc.gw + p.x; if (pc.pass_ == 4) { plan_reaction(p, i); return; } if (pc.pass_ == 5) { if (intent[i].y == 3u && cells[i] == intent[i].x) { cells[i] = intent[i].w | (intent[i].x & 0xFF0000u); material_changed(i, intent[i].x, intent[i].w); } return; } int ph = pc.pass_ & 3; ivec2 q; if (ph < 2) q = p + ivec2(((p.x & 1) == ph) ? 1 : -1, 0); else q = p + ivec2(0, ((p.y & 1) == (ph - 2)) ? 1 : -1); if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) return; const int j = q.y * pc.gw + q.x; if (i > j) return; // One invocation owns both outputs; no half-reaction race. uint ci = cells[i], cj = cells[j]; // A conversion must not start a second reaction as its new material this tick. if (ci != intent[i].x || cj != intent[j].x) return; uint mi = cell_mat(ci), mj = cell_mat(cj); if (mi == 0u && mj == 0u) return; const uint n = mats[mats.length() - 1u].w, stride = n * (n + 1u); const int a = min(i, j), b = max(i, j); const ivec2 pa = i < j ? p : q, pb = i < j ? q : p; const uint ma = cell_mat(intent[a].x), mb = cell_mat(intent[b].x); uint selectedBase = 0u, k = 0u; bool found = false; for (int side = 0; side < 2; ++side) { const int src = side == 0 ? a : b; const uint route = intent[src].y; if (route != 1u && route != 2u) continue; const ivec2 delta = side == 0 ? pb - pa : pa - pb; if (RD4[intent[src].w] != delta) continue; const uint sm = side == 0 ? ma : mb, tm = side == 0 ? mb : ma; const uint base = route == 2u ? stride : 0u; const uint h = rhead[base + sm * n + tm], cnt = h & 255u, R = intent[src].z; if (sm == 0u || cnt == 0u || rhead[base + n * n + sm] == 0u || R > rhead[base + n * n + sm]) continue; const uint candidate = R % cnt, pw = rrec[((h >> 8) + candidate) * 5u + 1u]; const uint rel = delta.y < 0 ? 1u : delta.y > 0 ? 2u : delta.x < 0 ? 3u : 4u; if (R > (pw & 65535u) || (((pw >> 16) & 7u) != 0u && ((pw >> 16) & 7u) != rel)) continue; selectedBase = base; k = candidate; found = true; break; } if (!found) return; const uint h = rhead[selectedBase + mi * n + mj], cnt = h & 255u; if (cnt == 0u) return; const uint rr = hash3(uint(a), uint(b), uint(pc.frame) ^ 0x51u); uint e = ((h >> 8) + k % cnt) * 5u; const uint third = rrec[e + 2u]; const bool firstIsI0 = (rrec[e + 4u] & 0x80000000u) == 0u; const uint reqLife = rrec[e + 3u] >> 20; if (reqLife > 0u && birth_age(birth[firstIsI0 ? i : j]) < reqLife) return; // three inputs (0070ebf0): a third cell next to the first input's cell (a cardinal neighbour chosen at random; the second cell's place is mirrored to the other side) // must hold the third material; the thread of the first cell writes its output if ((third & 0xFFFFu) != 0xFFFFu) { const bool firstIsI = firstIsI0; const ivec2 pf = firstIsI ? p : q, ps = firstIsI ? q : p; const ivec2 dirs[4] = ivec2[4](ivec2(0, -1), ivec2(-1, 0), ivec2(1, 0), ivec2(0, 1)); ivec2 p3 = pf + dirs[(rr >> 20) & 3u]; if (p3 == ps) p3 = 2 * pf - ps; if (p3.x < 0 || p3.y < 0 || p3.x >= pc.gw || p3.y >= pc.gh) return; const int k3 = p3.y * pc.gw + p3.x; const uint c3 = cells[k3]; if (canonical(cell_mat(c3)) != canonical(third & 0xFFFFu)) return; const uint o3 = (third >> 16) & 0xFFFFu; if (o3 != 0xFFFFu && o3 != cell_mat(c3)) { if (atomicCompSwap(cells[k3], c3, o3 | (c3 & 0x00FF0000u)) == c3) material_changed(k3, c3, o3); } } apply_side(p, q, i, j, ci, cj, mi, mj, e, rr); const uint reverseHead = rhead[selectedBase + mj * n + mi]; if ((reverseHead & 255u) != 0u) apply_side(q, p, j, i, cj, ci, mj, mi, ((reverseHead >> 8) + k % (reverseHead & 255u)) * 5u, rr); }