/*
 * H7 RTX SPV · headless Vulkan compute · NO OpenGL
 * Loads Build/rtx.spv · dispatch field+free · readback · stamp train
 *
 *   field[i]    = bgf(cheb, rad)     = (a-b)|1
 *   free_spv[i] = spv(x, y, seed)    = (a^b^c)|1  never fold
 *
 *   gcc -O2 -o Build/h7-rtx-spv Build/h7-rtx-spv.c -lvulkan
 *   ./Build/h7-rtx-spv [W] [H] [seed] [out_dir]
 *
 * Exit 0 on FULL GPU pass + iron sample match · SPV free ride
 */
#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* ── dynamic load (no link surprises) ── */
static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
#define LOAD(inst, name) name = (PFN_##name)vkGetInstanceProcAddr(inst, #name)
#define LOAD_DEV(dev, name) name = (PFN_##name)vkGetDeviceProcAddr(dev, #name)

static PFN_vkCreateInstance vkCreateInstance;
static PFN_vkDestroyInstance vkDestroyInstance;
static PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
static PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
static PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
static PFN_vkCreateDevice vkCreateDevice;
static PFN_vkDestroyDevice vkDestroyDevice;
static PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
static PFN_vkGetDeviceQueue vkGetDeviceQueue;
static PFN_vkCreateShaderModule vkCreateShaderModule;
static PFN_vkDestroyShaderModule vkDestroyShaderModule;
static PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
static PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
static PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
static PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
static PFN_vkCreateComputePipelines vkCreateComputePipelines;
static PFN_vkDestroyPipeline vkDestroyPipeline;
static PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
static PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
static PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
static PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
static PFN_vkCreateBuffer vkCreateBuffer;
static PFN_vkDestroyBuffer vkDestroyBuffer;
static PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
static PFN_vkAllocateMemory vkAllocateMemory;
static PFN_vkFreeMemory vkFreeMemory;
static PFN_vkBindBufferMemory vkBindBufferMemory;
static PFN_vkMapMemory vkMapMemory;
static PFN_vkUnmapMemory vkUnmapMemory;
static PFN_vkCreateCommandPool vkCreateCommandPool;
static PFN_vkDestroyCommandPool vkDestroyCommandPool;
static PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
static PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
static PFN_vkEndCommandBuffer vkEndCommandBuffer;
static PFN_vkCmdBindPipeline vkCmdBindPipeline;
static PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
static PFN_vkCmdDispatch vkCmdDispatch;
static PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
static PFN_vkQueueSubmit vkQueueSubmit;
static PFN_vkQueueWaitIdle vkQueueWaitIdle;
static PFN_vkCreateFence vkCreateFence;
static PFN_vkDestroyFence vkDestroyFence;
static PFN_vkWaitForFences vkWaitForFences;
static PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;

static void *libvk;

static int load_vulkan(void) {
  /* try common sonames */
  const char *cands[] = {
      "libvulkan.so.1", "libvulkan.so", NULL};
  void *h = NULL;
  for (int i = 0; cands[i]; i++) {
    /* dlopen via glibc */
    extern void *dlopen(const char *, int);
    extern void *dlsym(void *, const char *);
    h = dlopen(cands[i], 1 /* RTLD_LAZY */);
    if (h) break;
  }
  if (!h) {
    fprintf(stderr, "h7-rtx-spv: cannot dlopen libvulkan\n");
    return -1;
  }
  libvk = h;
  {
    extern void *dlsym(void *, const char *);
    vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(h, "vkGetInstanceProcAddr");
  }
  if (!vkGetInstanceProcAddr) {
    fprintf(stderr, "h7-rtx-spv: no vkGetInstanceProcAddr\n");
    return -1;
  }
  return 0;
}

static void load_instance(VkInstance inst) {
  LOAD(inst, vkCreateInstance); /* already used */
  LOAD(inst, vkDestroyInstance);
  LOAD(inst, vkEnumeratePhysicalDevices);
  LOAD(inst, vkGetPhysicalDeviceProperties);
  LOAD(inst, vkGetPhysicalDeviceQueueFamilyProperties);
  LOAD(inst, vkCreateDevice);
  LOAD(inst, vkGetDeviceProcAddr);
  LOAD(inst, vkGetPhysicalDeviceMemoryProperties);
}

static void load_device(VkDevice dev) {
  LOAD_DEV(dev, vkDestroyDevice);
  LOAD_DEV(dev, vkGetDeviceQueue);
  LOAD_DEV(dev, vkCreateShaderModule);
  LOAD_DEV(dev, vkDestroyShaderModule);
  LOAD_DEV(dev, vkCreateDescriptorSetLayout);
  LOAD_DEV(dev, vkDestroyDescriptorSetLayout);
  LOAD_DEV(dev, vkCreatePipelineLayout);
  LOAD_DEV(dev, vkDestroyPipelineLayout);
  LOAD_DEV(dev, vkCreateComputePipelines);
  LOAD_DEV(dev, vkDestroyPipeline);
  LOAD_DEV(dev, vkCreateDescriptorPool);
  LOAD_DEV(dev, vkDestroyDescriptorPool);
  LOAD_DEV(dev, vkAllocateDescriptorSets);
  LOAD_DEV(dev, vkUpdateDescriptorSets);
  LOAD_DEV(dev, vkCreateBuffer);
  LOAD_DEV(dev, vkDestroyBuffer);
  LOAD_DEV(dev, vkGetBufferMemoryRequirements);
  LOAD_DEV(dev, vkAllocateMemory);
  LOAD_DEV(dev, vkFreeMemory);
  LOAD_DEV(dev, vkBindBufferMemory);
  LOAD_DEV(dev, vkMapMemory);
  LOAD_DEV(dev, vkUnmapMemory);
  LOAD_DEV(dev, vkCreateCommandPool);
  LOAD_DEV(dev, vkDestroyCommandPool);
  LOAD_DEV(dev, vkAllocateCommandBuffers);
  LOAD_DEV(dev, vkBeginCommandBuffer);
  LOAD_DEV(dev, vkEndCommandBuffer);
  LOAD_DEV(dev, vkCmdBindPipeline);
  LOAD_DEV(dev, vkCmdBindDescriptorSets);
  LOAD_DEV(dev, vkCmdDispatch);
  LOAD_DEV(dev, vkCmdPipelineBarrier);
  LOAD_DEV(dev, vkQueueSubmit);
  LOAD_DEV(dev, vkQueueWaitIdle);
  LOAD_DEV(dev, vkCreateFence);
  LOAD_DEV(dev, vkDestroyFence);
  LOAD_DEV(dev, vkWaitForFences);
}

/* iron mirrors · match limb asm */
static int iron_bgf(int a, int b) { return (a - b) | 1; }
static int iron_spv(int a, int b, int c) { return (a ^ b ^ c) | 1; }

static uint8_t *read_file(const char *path, size_t *out_sz) {
  FILE *f = fopen(path, "rb");
  if (!f) return NULL;
  fseek(f, 0, SEEK_END);
  long n = ftell(f);
  fseek(f, 0, SEEK_SET);
  if (n <= 0) {
    fclose(f);
    return NULL;
  }
  uint8_t *b = (uint8_t *)malloc((size_t)n);
  if (!b) {
    fclose(f);
    return NULL;
  }
  if (fread(b, 1, (size_t)n, f) != (size_t)n) {
    free(b);
    fclose(f);
    return NULL;
  }
  fclose(f);
  *out_sz = (size_t)n;
  return b;
}

static uint32_t find_mem_type(VkPhysicalDevice phys, uint32_t bits, VkMemoryPropertyFlags flags) {
  VkPhysicalDeviceMemoryProperties mp;
  vkGetPhysicalDeviceMemoryProperties(phys, &mp);
  for (uint32_t i = 0; i < mp.memoryTypeCount; i++) {
    if ((bits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & flags) == flags)
      return i;
  }
  return UINT32_MAX;
}

typedef struct {
  VkBuffer buf;
  VkDeviceMemory mem;
  void *mapped;
  VkDeviceSize size;
} Buf;

static int make_buf(VkDevice dev, VkPhysicalDevice phys, VkDeviceSize size, VkBufferUsageFlags usage,
                    VkMemoryPropertyFlags props, Buf *out) {
  memset(out, 0, sizeof *out);
  out->size = size;
  VkBufferCreateInfo bi = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
  bi.size = size;
  bi.usage = usage;
  bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  if (vkCreateBuffer(dev, &bi, NULL, &out->buf) != VK_SUCCESS) return -1;
  VkMemoryRequirements req;
  vkGetBufferMemoryRequirements(dev, out->buf, &req);
  VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
  ai.allocationSize = req.size;
  ai.memoryTypeIndex = find_mem_type(phys, req.memoryTypeBits, props);
  if (ai.memoryTypeIndex == UINT32_MAX) return -1;
  if (vkAllocateMemory(dev, &ai, NULL, &out->mem) != VK_SUCCESS) return -1;
  if (vkBindBufferMemory(dev, out->buf, out->mem, 0) != VK_SUCCESS) return -1;
  if (props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
    if (vkMapMemory(dev, out->mem, 0, size, 0, &out->mapped) != VK_SUCCESS) return -1;
  }
  return 0;
}

static void free_buf(VkDevice dev, Buf *b) {
  if (b->mapped) vkUnmapMemory(dev, b->mem);
  if (b->buf) vkDestroyBuffer(dev, b->buf, NULL);
  if (b->mem) vkFreeMemory(dev, b->mem, NULL);
  memset(b, 0, sizeof *b);
}

int main(int argc, char **argv) {
  int W = 256, H = 256, seed = 7;
  const char *outdir = "out/h7fast";
  if (argc > 1) W = atoi(argv[1]);
  if (argc > 2) H = atoi(argv[2]);
  if (argc > 3) seed = atoi(argv[3]);
  if (argc > 4) outdir = argv[4];
  if (W < 16) W = 16;
  if (H < 16) H = 16;
  if (W > 4096) W = 4096;
  if (H > 4096) H = 4096;

  /* resolve Build/rtx.spv · cwd or next to argv0 */
  char spv_path[1024];
  size_t spv_sz = 0;
  uint8_t *spv_code = NULL;
  {
    const char *cands[4];
    int nc = 0;
    cands[nc++] = "Build/rtx.spv";
    cands[nc++] = "rtx.spv";
    if (argc > 0 && argv[0]) {
      static char beside[1024];
      snprintf(beside, sizeof beside, "%s", argv[0]);
      char *slash = strrchr(beside, '/');
      if (slash) {
        *slash = 0;
        snprintf(spv_path, sizeof spv_path, "%s/rtx.spv", beside);
        cands[nc++] = spv_path;
      }
    }
    for (int i = 0; i < nc && !spv_code; i++) {
      spv_code = read_file(cands[i], &spv_sz);
      if (spv_code) fprintf(stderr, "h7-rtx-spv: loaded %s (%zu bytes)\n", cands[i], spv_sz);
    }
  }
  if (!spv_code) {
    fprintf(stderr, "h7-rtx-spv: missing Build/rtx.spv (glslangValidator -V -S comp -o Build/rtx.spv Build/rtx.comp)\n");
    return 2;
  }

  if (load_vulkan() != 0) {
    free(spv_code);
    return 3;
  }

  /* instance globals before create */
  vkCreateInstance = (PFN_vkCreateInstance)vkGetInstanceProcAddr(NULL, "vkCreateInstance");
  if (!vkCreateInstance) {
    fprintf(stderr, "h7-rtx-spv: no vkCreateInstance\n");
    free(spv_code);
    return 3;
  }

  VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
  app.pApplicationName = "h7-rtx-spv";
  app.apiVersion = VK_API_VERSION_1_1;

  VkInstanceCreateInfo ici = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
  ici.pApplicationInfo = &app;

  VkInstance inst = VK_NULL_HANDLE;
  if (vkCreateInstance(&ici, NULL, &inst) != VK_SUCCESS) {
    fprintf(stderr, "h7-rtx-spv: vkCreateInstance failed\n");
    free(spv_code);
    return 3;
  }
  load_instance(inst);

  uint32_t ndev = 0;
  vkEnumeratePhysicalDevices(inst, &ndev, NULL);
  if (!ndev) {
    fprintf(stderr, "h7-rtx-spv: no physical devices\n");
    return 3;
  }
  VkPhysicalDevice *devs = (VkPhysicalDevice *)calloc(ndev, sizeof *devs);
  vkEnumeratePhysicalDevices(inst, &ndev, devs);

  /* prefer discrete NVIDIA */
  VkPhysicalDevice phys = devs[0];
  uint32_t qfam = UINT32_MAX;
  for (uint32_t d = 0; d < ndev; d++) {
    VkPhysicalDeviceProperties prop;
    vkGetPhysicalDeviceProperties(devs[d], &prop);
    uint32_t nq = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(devs[d], &nq, NULL);
    VkQueueFamilyProperties *qf = (VkQueueFamilyProperties *)calloc(nq, sizeof *qf);
    vkGetPhysicalDeviceQueueFamilyProperties(devs[d], &nq, qf);
    uint32_t found = UINT32_MAX;
    for (uint32_t i = 0; i < nq; i++) {
      if (qf[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
        found = i;
        break;
      }
    }
    free(qf);
    if (found == UINT32_MAX) continue;
    /* pick NVIDIA discrete if present */
    if (prop.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ||
        prop.vendorID == 0x10de /* NVIDIA */) {
      phys = devs[d];
      qfam = found;
      fprintf(stderr, "h7-rtx-spv: GPU %s vendor=0x%x type=%u\n", prop.deviceName, prop.vendorID,
              prop.deviceType);
      break;
    }
    if (qfam == UINT32_MAX) {
      phys = devs[d];
      qfam = found;
      fprintf(stderr, "h7-rtx-spv: GPU %s vendor=0x%x type=%u\n", prop.deviceName, prop.vendorID,
              prop.deviceType);
    }
  }
  free(devs);
  if (qfam == UINT32_MAX) {
    fprintf(stderr, "h7-rtx-spv: no compute queue\n");
    return 3;
  }

  float prio = 1.0f;
  VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
  qci.queueFamilyIndex = qfam;
  qci.queueCount = 1;
  qci.pQueuePriorities = &prio;
  VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
  dci.queueCreateInfoCount = 1;
  dci.pQueueCreateInfos = &qci;
  VkDevice dev = VK_NULL_HANDLE;
  if (vkCreateDevice(phys, &dci, NULL, &dev) != VK_SUCCESS) {
    fprintf(stderr, "h7-rtx-spv: vkCreateDevice failed\n");
    return 3;
  }
  load_device(dev);
  VkQueue queue;
  vkGetDeviceQueue(dev, qfam, 0, &queue);

  /* buffers: field, free, params — host visible for simplicity */
  VkDeviceSize ncells = (VkDeviceSize)W * (VkDeviceSize)H;
  VkDeviceSize isz = ncells * sizeof(int32_t);
  Buf field = {0}, freeb = {0}, params = {0};
  VkBufferUsageFlags usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
  VkMemoryPropertyFlags host =
      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
  if (make_buf(dev, phys, isz, usage, host, &field) || make_buf(dev, phys, isz, usage, host, &freeb) ||
      make_buf(dev, phys, 16, usage, host, &params)) {
    fprintf(stderr, "h7-rtx-spv: buffer alloc failed\n");
    return 4;
  }
  memset(field.mapped, 0, (size_t)isz);
  memset(freeb.mapped, 0, (size_t)isz);
  int32_t *p = (int32_t *)params.mapped;
  p[0] = W;
  p[1] = H;
  p[2] = seed;
  p[3] = 0;

  /* shader */
  VkShaderModuleCreateInfo smci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
  smci.codeSize = spv_sz;
  smci.pCode = (const uint32_t *)spv_code;
  VkShaderModule sm = VK_NULL_HANDLE;
  if (vkCreateShaderModule(dev, &smci, NULL, &sm) != VK_SUCCESS) {
    fprintf(stderr, "h7-rtx-spv: bad SPIR-V\n");
    return 5;
  }
  free(spv_code);
  spv_code = NULL;

  /* descriptors: 3 storage buffers binding 0,1,2 */
  VkDescriptorSetLayoutBinding binds[3];
  memset(binds, 0, sizeof binds);
  for (int i = 0; i < 3; i++) {
    binds[i].binding = (uint32_t)i;
    binds[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    binds[i].descriptorCount = 1;
    binds[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
  }
  VkDescriptorSetLayoutCreateInfo dslci = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
  dslci.bindingCount = 3;
  dslci.pBindings = binds;
  VkDescriptorSetLayout dsl;
  vkCreateDescriptorSetLayout(dev, &dslci, NULL, &dsl);

  VkPipelineLayoutCreateInfo plci = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
  plci.setLayoutCount = 1;
  plci.pSetLayouts = &dsl;
  VkPipelineLayout layout;
  vkCreatePipelineLayout(dev, &plci, NULL, &layout);

  VkComputePipelineCreateInfo cpci = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
  cpci.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
  cpci.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
  cpci.stage.module = sm;
  cpci.stage.pName = "main";
  cpci.layout = layout;
  VkPipeline pipe;
  if (vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &cpci, NULL, &pipe) != VK_SUCCESS) {
    fprintf(stderr, "h7-rtx-spv: pipeline failed\n");
    return 5;
  }

  VkDescriptorPoolSize dps = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3};
  VkDescriptorPoolCreateInfo dpci = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
  dpci.maxSets = 1;
  dpci.poolSizeCount = 1;
  dpci.pPoolSizes = &dps;
  VkDescriptorPool pool;
  vkCreateDescriptorPool(dev, &dpci, NULL, &pool);
  VkDescriptorSetAllocateInfo dsai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
  dsai.descriptorPool = pool;
  dsai.descriptorSetCount = 1;
  dsai.pSetLayouts = &dsl;
  VkDescriptorSet ds;
  vkAllocateDescriptorSets(dev, &dsai, &ds);

  VkDescriptorBufferInfo bis[3] = {
      {field.buf, 0, VK_WHOLE_SIZE},
      {freeb.buf, 0, VK_WHOLE_SIZE},
      {params.buf, 0, VK_WHOLE_SIZE},
  };
  VkWriteDescriptorSet writes[3];
  memset(writes, 0, sizeof writes);
  for (int i = 0; i < 3; i++) {
    writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    writes[i].dstSet = ds;
    writes[i].dstBinding = (uint32_t)i;
    writes[i].descriptorCount = 1;
    writes[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    writes[i].pBufferInfo = &bis[i];
  }
  vkUpdateDescriptorSets(dev, 3, writes, 0, NULL);

  VkCommandPoolCreateInfo cpoci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
  cpoci.queueFamilyIndex = qfam;
  VkCommandPool cpool;
  vkCreateCommandPool(dev, &cpoci, NULL, &cpool);
  VkCommandBufferAllocateInfo cbai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
  cbai.commandPool = cpool;
  cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  cbai.commandBufferCount = 1;
  VkCommandBuffer cmd;
  vkAllocateCommandBuffers(dev, &cbai, &cmd);

  VkCommandBufferBeginInfo begin = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
  begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
  vkBeginCommandBuffer(cmd, &begin);
  vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
  vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, 1, &ds, 0, NULL);
  uint32_t gx = (uint32_t)((W + 15) / 16);
  uint32_t gy = (uint32_t)((H + 15) / 16);
  vkCmdDispatch(cmd, gx, gy, 1);
  /* host-visible · barrier for host read */
  VkMemoryBarrier mb = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
  mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
  mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
  vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1,
                       &mb, 0, NULL, 0, NULL);
  vkEndCommandBuffer(cmd);

  struct timespec t0, t1;
  clock_gettime(CLOCK_MONOTONIC, &t0);
  VkFence fence;
  VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
  vkCreateFence(dev, &fci, NULL, &fence);
  VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
  si.commandBufferCount = 1;
  si.pCommandBuffers = &cmd;
  if (vkQueueSubmit(queue, 1, &si, fence) != VK_SUCCESS) {
    fprintf(stderr, "h7-rtx-spv: submit failed\n");
    return 6;
  }
  vkWaitForFences(dev, 1, &fence, VK_TRUE, 60ull * 1000 * 1000 * 1000);
  clock_gettime(CLOCK_MONOTONIC, &t1);
  double ms = (t1.tv_sec - t0.tv_sec) * 1000.0 + (t1.tv_nsec - t0.tv_nsec) / 1e6;

  int32_t *farr = (int32_t *)field.mapped;
  int32_t *sarr = (int32_t *)freeb.mapped;

  /* iron sample check · corners + center + diagonals */
  int mismatch = 0, samples = 0;
  int pts[][2] = {{0, 0}, {W - 1, 0}, {0, H - 1}, {W - 1, H - 1}, {W / 2, H / 2}, {16, 16}, {W / 4, H / 3}};
  int npts = (int)(sizeof pts / sizeof pts[0]);
  int cx = W / 2, cy = H / 2, rad = (W < H ? W : H) / 4;
  for (int k = 0; k < npts; k++) {
    int x = pts[k][0], y = pts[k][1];
    if (x < 0 || y < 0 || x >= W || y >= H) continue;
    int i = y * W + x;
    int dx = x - cx, dy = y - cy;
    int ax = dx < 0 ? -dx : dx, ay = dy < 0 ? -dy : dy;
    int cheb = ax > ay ? ax : ay;
    int want_f = iron_bgf(cheb, rad);
    int want_s = iron_spv(x, y, seed);
    samples++;
    if (farr[i] != want_f || sarr[i] != want_s) {
      mismatch++;
      if (mismatch <= 4)
        fprintf(stderr, "MISMATCH (%d,%d) field=%d want_f=%d free=%d want_s=%d\n", x, y, farr[i],
                want_f, sarr[i], want_s);
    }
  }

  /* mkdir out */
  char cmdmk[1200];
  snprintf(cmdmk, sizeof cmdmk, "mkdir -p '%s'", outdir);
  if (system(cmdmk) != 0) { /* ignore */
  }

  char path[1200];
  snprintf(path, sizeof path, "%s/rtx_field.bin", outdir);
  FILE *of = fopen(path, "wb");
  if (of) {
    fwrite(farr, sizeof(int32_t), (size_t)ncells, of);
    fclose(of);
  }
  snprintf(path, sizeof path, "%s/rtx_free.bin", outdir);
  of = fopen(path, "wb");
  if (of) {
    fwrite(sarr, sizeof(int32_t), (size_t)ncells, of);
    fclose(of);
  }

  /* TSV slice for train track · subsample grid for curriculum (not full dump) */
  snprintf(path, sizeof path, "%s/rtx_spv_lessons.tsv", outdir);
  of = fopen(path, "w");
  int written = 0;
  if (of) {
    /* stride so ~4096 lessons max from grid */
    int stride = 1;
    while ((W / stride) * (H / stride) > 4096) stride++;
    for (int y = 0; y < H; y += stride) {
      for (int x = 0; x < W; x += stride) {
        int i = y * W + x;
        /* id filled later by mega train · limb field=bgf path via synthetic */
        fprintf(of, "rtx_field\t%d\t%d\tfield %d %d\n", x * 10000 + y, farr[i], x, y);
        fprintf(of, "rtx_free\t%d\t%d\tspv %d %d seed=%d\n", x * 10000 + y, sarr[i], x, y, seed);
        written += 2;
      }
    }
    fclose(of);
  }

  /* stamp */
  snprintf(path, sizeof path, "%s/rtx_spv_status.json", outdir);
  of = fopen(path, "w");
  if (of) {
    fprintf(of,
            "{\n"
            "  \"word\": \"H7_RTX_SPV\",\n"
            "  \"stack\": \"vulkan+spirv\",\n"
            "  \"no_gl\": true,\n"
            "  \"spv\": \"%s\",\n"
            "  \"W\": %d, \"H\": %d, \"seed\": %d,\n"
            "  \"cells\": %llu,\n"
            "  \"dispatch_ms\": %.3f,\n"
            "  \"iron_samples\": %d,\n"
            "  \"iron_mismatch\": %d,\n"
            "  \"lessons_tsv\": %d,\n"
            "  \"full\": %s,\n"
            "  \"law\": \"compute SPV the RTX · not GL · free never folds · iron match\"\n"
            "}\n",
            "Build/rtx.spv", W, H, seed, (unsigned long long)ncells, ms, samples, mismatch, written,
            mismatch == 0 ? "true" : "false");
    fclose(of);
  }

  printf("H7_RTX_SPV W=%d H=%d seed=%d cells=%llu ms=%.3f iron_mismatch=%d/%d lessons=%d %s\n", W, H,
         seed, (unsigned long long)ncells, ms, mismatch, samples, written,
         mismatch == 0 ? "FULL" : "FAIL");

  /* cleanup */
  vkDestroyFence(dev, fence, NULL);
  vkDestroyCommandPool(dev, cpool, NULL);
  vkDestroyDescriptorPool(dev, pool, NULL);
  vkDestroyPipeline(dev, pipe, NULL);
  vkDestroyPipelineLayout(dev, layout, NULL);
  vkDestroyDescriptorSetLayout(dev, dsl, NULL);
  vkDestroyShaderModule(dev, sm, NULL);
  free_buf(dev, &field);
  free_buf(dev, &freeb);
  free_buf(dev, &params);
  vkDestroyDevice(dev, NULL);
  vkDestroyInstance(inst, NULL);

  return mismatch == 0 ? 0 : 1;
}
