// Shared by intent/apply/paint/render compute shaders. // // Cell word (u32): bits 0-15 material index, 16-23 colour variation, 26 gas/fire horizontal direction. // Per-cell extension (vec2): x = vertical velocity accumulator, y = horizontal velocity. // Material table: 3 uvec4 per material // [0] colour(ARGB), flags, density(float), liquid_gravity(float) // [1] liquid_viscosity(int), liquid_damping(float), liquid_flow_speed(float), // gas speeds packed: byte0 gas_speed, byte1 up, byte2 horizontal, byte3 down // [2] lifetime(int), durability(float), burn word 1 (fire_hp | burnable << 16 | on_fire << 17 | extinguishes << 18), burn word 2 (autoignition | temperature_of_fire << 8 | smoke << 16) // The last entry of the table is special: (fire id, smoke id, steam id, material count). layout(std430, set = 0, binding = 0) buffer Cells { uint cells[]; }; layout(std430, set = 0, binding = 1) readonly buffer Mats { uvec4 mats[]; }; layout(std430, set = 0, binding = 2) buffer Pixels { uint pixels[]; }; layout(std430, set = 0, binding = 3) buffer Ext { vec2 ext[]; }; layout(std430, set = 0, binding = 42) buffer Birth { uint birth[]; }; // the frame a cell got its material (cell age = frame - birth, 0070ebf0 req_lifetime) // Material stamp (high 16), CLiquidCell movement state (bits 8..11), rest age (low byte). layout(std430, set = 0, binding = 65) buffer TerrainAge { uint terrain_age[]; }; layout(std430, set = 0, binding = 4) buffer Claims { uint claims[]; }; // per cell: x = target index | (flip direction << 31), y = priority, z/w = new vy/vx bits layout(std430, set = 0, binding = 5) buffer Intent { uvec4 intent[]; }; layout(push_constant) uniform PC { int gw, gh; // grid size in cells int ow, oh; // output size in pixels int scale; // pixels per cell int frame; int pass_; // movement pass within the frame int brush_x, brush_y, brush_r; int brush_mat; // -1: none, 0: erase int seed; int view_x, view_y; // world cell at the top-left of the grid int area_ox, area_oy; // world cell at the centre of wang pixel (0,0) int pw, ph; // wang plane size int m_rock, m_soil, m_sand, m_wet; // fill materials int shift_x, shift_y, shift_mode; int scene_x0, scene_y0, scene_w, scene_h; // pixel-scene plane placement (cells) int bg_w, bg_h; // biome background tile size (0 = none) int ent_count; // sprite instances for sprites.comp // terrain.comp: mode 1 = scroll by (shift_x, shift_y), keep the overlap } pc; const uint F_STATIC = 1u; // never moves (solid or liquid_static) const uint F_LIQUID = 2u; // liquid or powder const uint F_GAS = 4u; const uint F_SAND = 8u; // powder (liquid_sand) const uint F_AIR = 16u; const uint F_FIRE = 32u; const uint F_LIQCELL = 128u; // cell_type liquid, static or not (the native count / immersion predicate: liquid slot 22 true, powder slot 25 false) const uint F_POWDER = 256u; // liquid_sand, static or not const uint F_STAINS = 512u; // liquid_stains: a body in it takes its stain (vine points: the stored material that slows them) const uint F_NOSTAIN = 2048u; // CellData stainable off (the box2d materials): no explosion burn const uint F_INDESTRUCT = 1024u; // material tag [indestructible]: a CellEaterComponent with ignored_material_tag leaves it const uint F_SLIME = 64u; // liquid_slime: moved by 0070b7c0 (docs/IMPLEMENTATION_BLOCKERS.md) const uint NONE = 0xFFFFFFFFu; const uint DIR_BIT = 1u << 26; const uint BURN_BIT = 1u << 27; // the cell is burning const uint FROZEN_BIT = 1u << 26; // a static cell stained frozen (MagicConvertMaterialComponent stain_frozen; bits 24-25 then hold its damage level, explode_rays.comp) const uint ATTACH_BIT = 1u << 26; // a powder attached to a wall or ceiling (passive: byte 22 of the original cell); bits 24-25 then hold the direction: 1 right (state 5), 2 above (6), 3 left (7) // Fire fade (the engine's signed fade byte, in steps of 4): bits 24-25 and 28-31 of the cell word. Burning cells and flames carry it. uint cell_fade6(uint c) { return ((c >> 28) & 15u) | (((c >> 24) & 3u) << 4); } uint with_fade6(uint c, uint f) { f = min(f, 63u); return (c & ~0xF3000000u) | ((f & 15u) << 28) | ((f >> 4) << 24); } // The age of a cell and its fire fade share the birth word: bits 0-23 the frame it got its material (the age is the difference modulo 2^24), bits 24-31 the engine's signed fade byte (cell + 0x11). const uint BIRTH_MASK = 0xFFFFFFu; uint birth_frame() { return uint(pc.frame) & BIRTH_MASK; } uint birth_age(uint b) { return (uint(pc.frame) - b) & BIRTH_MASK; } int fade_of(uint b) { return int(b) >> 24; } // signed byte uint with_fade(uint b, int f) { return (b & BIRTH_MASK) | (uint(f & 255) << 24); } uint cell_mat(uint c) { return c & 0xFFFFu; } uint mflags(uint m) { return mats[3u * m].y; } // 0070b1d0: state 3 and zero vertical accumulator; support is not probed. uint liquid_state(uint i, uint m) { return (terrain_age[i] >> 16) == m ? (terrain_age[i] >> 8) & 15u : 3u; } bool liquid_freeze_ready(ivec2 p, uint c) { const uint m = cell_mat(c); const int i = p.y * pc.gw + p.x; return (mflags(m) & F_LIQCELL) == 0u || (liquid_state(uint(i), m) == 3u && ext[i].x == 0.0); } // ChangeMaterial preserves the movement accumulators for nonburning same-class cells. void material_changed(int i, uint oldCell, uint m) { const uint old = cell_mat(oldCell); const uint classMask = F_LIQCELL | F_GAS | F_FIRE | F_AIR; const bool sameClass = (oldCell & BURN_BIT) == 0u && (mflags(old) & classMask) == (mflags(m) & classMask); terrain_age[i] = (m << 16) | (sameClass && (terrain_age[i] >> 16) == old ? (terrain_age[i] & 0xFFFFu) : 0x300u); if (!sameClass) ext[i] = vec2(0.0); birth[i] = birth_frame(); } float mdensity(uint m) { return uintBitsToFloat(mats[3u * m].z); } float mgravity(uint m) { return uintBitsToFloat(mats[3u * m].w); } int mviscosity(uint m) { return int(mats[3u * m + 1u].x & 0xFFFFu); } uint mceiling(uint m) { return mats[3u * m + 1u].x >> 16; } // liquid_sticks_to_ceiling (powders) float mdamping(uint m) { return uintBitsToFloat(mats[3u * m + 1u].y); } float mflow(uint m) { return uintBitsToFloat(mats[3u * m + 1u].z); } uint mgasbits(uint m) { return mats[3u * m + 1u].w; } int mlifetime(uint m) { return int(mats[3u * m + 2u].x); } uint hash(uint x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; } uint hash3(uint a, uint b, uint c) { return hash(a * 0x9E3779B1u ^ hash(b + 0x85EBCA6Bu ^ hash(c))); }