// 流体内核(assembly/core.ts 的 Moonbit 同构移植):f32 存储 / f64 中间量,
// 混沌流场(李雅普诺夫放大)下任何降 f32 都会指数放大差异,全链路严禁;
// 逐位一致由 tests/engine-golden.test.ts golden hash 钉死。红黑 GS 相位内单元格
// 独立(von Neumann 邻域全异奇偶)→ 空气索引表 + f64x2 SIMD 与标量逐位等价。
// SIMD 在 bun/JSC 的"无地形全 bulk"路径误编译(Safari 同源风险);
// 该路径已由 build_air_lists 按 solid_count 门控转标量,有地形关卡 SIMD 照常——见 pitfalls I8

///| 上限统一在 grid.mbt(流体/示踪/顶点批共用),改容量只动那一处

///|
let u : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let v : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let t : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let solid : FixedArray[Byte] = FixedArray::make(grid_max_cells, b'\x00')

///|
let u0 : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let v0 : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let t0 : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let q1 : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let q2 : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let p : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)
// 散度 × h²(f64):project_div 直接产出,GS 消费(不存 f32 中间量,少一趟扫描)

///|
let div_h2 : FixedArray[Double] = FixedArray::make(grid_max_cells, 0.0)
// 环境风位流基场:远场单位水平风的贴地绕流(烘焙一次,采样按强度线性叠加)

///|
let fx_u : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)

///|
let fx_v : FixedArray[Float] = FixedArray::make(grid_max_cells, 0.0)
// 空气索引表(rebuild_solid 按行主序重建,地形静态跨步复用):扫描只迭代空气格免固体分支;
// bulk = 四邻全空气主体格走 GS 免检查快路径(红黑相位内遍历顺序无关,见文件头)

///|
let air_list : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
let air_i : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
let air_j : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
let air_even : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
let air_odd : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
let bulk : FixedArray[Byte] = FixedArray::make(grid_max_cells, b'\x00')

///|
let solid_list : FixedArray[Int] = FixedArray::make(grid_max_cells, 0)

///|
priv struct Params {
  mut nx : Int
  mut ny : Int
  mut cell : Double
  mut buoyancy : Double
  mut t_max : Double
  mut source_radius : Double
  mut vel_damping : Double
  mut t_damping : Double
  mut iterations : Int
  mut margin_cells : Int
  mut ox : Int
  mut oy : Int
  mut sponge_vel_in : Double
  mut sponge_vel_out : Double
  mut sponge_t_out : Double
  mut ambient_x : Double
  mut ambient_y : Double
  mut ambient_t : Double
  mut out_vx : Double
  mut out_vy : Double
  mut solid_count : Int
  mut air_count : Int
  mut air_even_count : Int
  mut air_odd_count : Int
}

///|
let pm : Params = Params::{
  nx: 0,
  ny: 0,
  cell: 0.0,
  buoyancy: 0.0,
  t_max: 0.0,
  source_radius: 0.0,
  vel_damping: 0.0,
  t_damping: 0.0,
  iterations: 0,
  margin_cells: 0,
  ox: 0,
  oy: 0,
  sponge_vel_in: 0.0,
  sponge_vel_out: 0.0,
  sponge_t_out: 0.0,
  ambient_x: 0.0,
  ambient_y: 0.0,
  ambient_t: 0.0,
  out_vx: 0.0,
  out_vy: 0.0,
  solid_count: 0,
  air_count: 0,
  air_even_count: 0,
  air_odd_count: 0,
}

// 导出名 init(对齐 assembly 契约);Moonbit 保留无参 fn init 作包初始化,故本地名 fluid_init

///|
/// Initialize the fluid grid (dimensions, cell size, tuning, margin) and reset all state.
/// Returns 0 on success, 1 on invalid parameters.
#export_name("init")
pub fn fluid_init(
  nx_ : Int,
  ny_ : Int,
  cell_ : Double,
  buoyancy_ : Double,
  t_max_ : Double,
  source_radius_ : Double,
  vel_damping_ : Double,
  t_damping_ : Double,
  iterations_ : Int,
  margin_cells_ : Int,
) -> Int {
  // 防御面:cell 非正/NaN(采样除零产 NaN 场)与 margin 超界(sponge 越界写)不可接受;
  // 侧带以 m≤nx−2 钉在场内;顶带 j≤m 依赖满容量静态缓冲(grid_max_cells)吸收 m=ny 的极端
  guard nx_ >= 3 &&
    ny_ >= 3 &&
    nx_ <= grid_max_nx &&
    ny_ <= grid_max_ny &&
    cell_ > 0.0 &&
    margin_cells_ >= 0 &&
    margin_cells_ <= nx_ - 2 else {
    1
  }
  pm.nx = nx_
  pm.ny = ny_
  pm.cell = cell_
  pm.buoyancy = buoyancy_
  pm.t_max = t_max_
  pm.source_radius = source_radius_
  pm.vel_damping = vel_damping_
  pm.t_damping = t_damping_
  pm.iterations = iterations_
  pm.margin_cells = margin_cells_
  pm.ox = margin_cells_
  pm.oy = margin_cells_
  pm.sponge_vel_in = 0.999
  pm.sponge_vel_out = 0.97
  pm.sponge_t_out = 0.94
  pm.ambient_x = 0.0
  pm.ambient_y = 0.0
  pm.ambient_t = 0.0
  // 全量状态复位(P6 根因):init = 与全新实例等价——q1/q2 只在空气格写、回推采样会读固体格,
  // 跨关卡复用引擎时旧关残留会污染新关近壁修正项(实测轨迹发散);solid 清零使 init 期
  // build_air_lists 与 bake_ambient_basis 见全空气基线(与全新实例一致),真实地形随后经 rebuildSolid 重建
  clear()
  let fbytes = nx_ * ny_ * 4
  mem_fill(addr_of_f32(u0), 0, fbytes)
  mem_fill(addr_of_f32(v0), 0, fbytes)
  mem_fill(addr_of_f32(t0), 0, fbytes)
  mem_fill(addr_of_f32(q1), 0, fbytes)
  mem_fill(addr_of_f32(q2), 0, fbytes)
  mem_fill(addr_of_f64(div_h2), 0, nx_ * ny_ * 8)
  mem_fill(addr_of_u8(solid), 0, nx_ * ny_)
  pm.solid_count = 0
  build_air_lists()
  bake_ambient_basis()
  0
}

///|
/// Zero the velocity, temperature and pressure fields.
#export_name("clear")
pub fn clear() -> Unit {
  let bytes = pm.nx * pm.ny * 4
  mem_fill(addr_of_f32(u), 0, bytes)
  mem_fill(addr_of_f32(v), 0, bytes)
  mem_fill(addr_of_f32(t), 0, bytes)
  mem_fill(addr_of_f32(p), 0, bytes)
}

///|
/// Set the ambient wind velocity (x, y) and temperature bias.
#export_name("setAmbient")
pub fn set_ambient(x : Double, y : Double, temp : Double) -> Unit {
  pm.ambient_x = x
  pm.ambient_y = y
  pm.ambient_t = temp
}

// 环境风 = 预烘焙位流基场 × 强度:远场单位水平风、地面/顶面不可穿透、左右开边界。
// φ 初值 = x 坡道;SOR 求解 Laplace(左右边列 Dirichlet 坡道,地面/顶面 Neumann 镜像),
// 速度 = ∇φ 中心差分,固体邻居代入有效值使界面法向分量为零。Scratch 复用 p,结束清零。

///|
fn bake_ambient_basis() -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  for j in 0.. maxd {
          maxd = d
        }
        p[idx] = Float::from_double(np)
      }
    }
    if maxd < 1.0e-6 {
      break
    }
  }
  let bytes = nx * ny * 4
  mem_fill(addr_of_f32(fx_u), 0, bytes)
  mem_fill(addr_of_f32(fx_v), 0, bytes)
  for j in 1..<(ny - 1) {
    let row = j * nx
    for i in 1..<(nx - 1) {
      let idx = i + row
      if solid[idx] != b'\x00' {
        continue
      }
      let p_l : Double = if solid[idx - 1] != b'\x00' {
        if i - 1 == 0 {
          0.0
        } else {
          p[idx].to_double()
        }
      } else {
        p[idx - 1].to_double()
      }
      let p_r : Double = if solid[idx + 1] != b'\x00' {
        if i + 1 == nx - 1 {
          (nx - 1).to_double()
        } else {
          p[idx].to_double()
        }
      } else {
        p[idx + 1].to_double()
      }
      let p_u : Double = if solid[idx - nx] != b'\x00' {
        p[idx].to_double()
      } else {
        p[idx - nx].to_double()
      }
      let p_d : Double = if solid[idx + nx] != b'\x00' {
        p[idx].to_double()
      } else {
        p[idx + nx].to_double()
      }
      fx_u[idx] = Float::from_double((p_r - p_l) * 0.5)
      fx_v[idx] = Float::from_double((p_d - p_u) * 0.5)
    }
  }
  mem_fill(addr_of_f32(p), 0, bytes)
}

///|
fn build_air_lists() -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  mem_fill(addr_of_u8(bulk), 0, nx * ny)
  let mut c = 0
  let mut ce = 0
  let mut co = 0
  // 只收录内域格(1..nx-2 × 1..ny-2):与旧循环边界一致,越界邻居恒不出现
  for j in 1..<(ny - 1) {
    let row = j * nx
    for i in 1..<(nx - 1) {
      let idx = i + row
      if solid[idx] != b'\x00' {
        continue
      }
      air_list[c] = idx
      air_i[c] = i
      air_j[c] = j
      c = c + 1
      if ((i + j) & 1) == 0 {
        air_even[ce] = idx
        ce = ce + 1
      } else {
        air_odd[co] = idx
        co = co + 1
      }
      if solid[idx - 1] == b'\x00' &&
        solid[idx + 1] == b'\x00' &&
        solid[idx - nx] == b'\x00' &&
        solid[idx + nx] == b'\x00' {
        bulk[idx] = b'\x01'
      }
    }
  }
  pm.air_count = c
  pm.air_even_count = ce
  pm.air_odd_count = co
  // 无实体(纯空域)关卡:禁用双格 SIMD 快路径(JSC 对无地形全 bulk 的 gs_pair 误编译,
  // 实测 bun/JSC 速度场错误而 node/V8 位正确;有地形路径两引擎均位一致)——语义层门控,不牺牲 V8 性能
  if pm.solid_count == 0 {
    mem_fill(addr_of_u8(bulk), 0, nx * ny)
  }
}

///|
/// Rebuild the solid index from the terrain mask and rebake the ambient basis.
#export_name("rebuildSolid")
pub fn rebuild_solid() -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  let mut c = 0
  for j in 0.. Unit {
  let len = (fx * fx + fy * fy).sqrt()
  if len < 1.0e-6 {
    return
  }
  let dxu = fx / len
  let dyv = fy / len
  let nx = pm.nx
  let ny = pm.ny
  let gr = radius / pm.cell
  let gx = wx / pm.cell - 0.5 + pm.ox.to_double()
  let gy = wy / pm.cell - 0.5 + pm.oy.to_double()
  let mut x0 = (gx - gr).floor().to_int()
  if x0 < 1 {
    x0 = 1
  }
  let mut x1 = (gx + gr).ceil().to_int()
  if x1 > nx - 2 {
    x1 = nx - 2
  }
  let mut y0 = (gy - gr).floor().to_int()
  if y0 < 1 {
    y0 = 1
  }
  let mut y1 = (gy + gr).ceil().to_int()
  if y1 > ny - 2 {
    y1 = ny - 2
  }
  for j in y0..<(y1 + 1) {
    let row = j * nx
    for i in x0..<(x1 + 1) {
      let idx = i + row
      if solid[idx] != b'\x00' {
        continue
      }
      let dx = i.to_double() - gx
      let dy = j.to_double() - gy
      let d = (dx * dx + dy * dy).sqrt()
      if d >= gr {
        continue
      }
      let falloff = 1.0 - d / gr
      u[idx] = Float::from_double(u[idx].to_double() + amount * dxu * falloff)
      v[idx] = Float::from_double(v[idx].to_double() + amount * dyv * falloff)
    }
  }
}

///|
/// Inject heat at (wx, wy) within the source radius (negative cools), clamped to ±t_max.
#export_name("addHeat")
pub fn add_heat(wx : Double, wy : Double, amount : Double) -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  let gr = pm.source_radius / pm.cell
  let gx = wx / pm.cell - 0.5 + pm.ox.to_double()
  let gy = wy / pm.cell - 0.5 + pm.oy.to_double()
  let mut x0 = (gx - gr).floor().to_int()
  if x0 < 1 {
    x0 = 1
  }
  let mut x1 = (gx + gr).ceil().to_int()
  if x1 > nx - 2 {
    x1 = nx - 2
  }
  let mut y0 = (gy - gr).floor().to_int()
  if y0 < 1 {
    y0 = 1
  }
  let mut y1 = (gy + gr).ceil().to_int()
  if y1 > ny - 2 {
    y1 = ny - 2
  }
  for j in y0..<(y1 + 1) {
    let row = j * nx
    for i in x0..<(x1 + 1) {
      let idx = i + row
      if solid[idx] != b'\x00' {
        continue
      }
      let dx = i.to_double() - gx
      let dy = j.to_double() - gy
      let d = (dx * dx + dy * dy).sqrt()
      if d >= gr {
        continue
      }
      let falloff = 1.0 - d / gr
      let mut val = t[idx].to_double() + amount * falloff
      if val > pm.t_max {
        val = pm.t_max
      } else if val < -pm.t_max {
        val = -pm.t_max
      }
      t[idx] = Float::from_double(val)
    }
  }
}

///|
/// Sample the flow velocity at (wx, wy) (incl. ambient); result via out_x/out_y.
#export_name("sampleVelocity")
pub fn sample_velocity(wx : Double, wy : Double) -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  let mut gx = wx / pm.cell - 0.5 + pm.ox.to_double()
  let mut gy = wy / pm.cell - 0.5 + pm.oy.to_double()
  if gx < 0.0 {
    gx = 0.0
  } else if gx > nx.to_double() - 1.001 {
    gx = nx.to_double() - 1.001
  }
  if gy < 0.0 {
    gy = 0.0
  } else if gy > ny.to_double() - 1.001 {
    gy = ny.to_double() - 1.001
  }
  let i0 = gx.floor().to_int()
  let j0 = gy.floor().to_int()
  let fx = gx - i0.to_double()
  let fy = gy - j0.to_double()
  let a = i0 + j0 * nx
  let b = a + 1
  let c = a + nx
  let d = c + 1
  let w00 = (1.0 - fx) * (1.0 - fy)
  let w10 = fx * (1.0 - fy)
  let w01 = (1.0 - fx) * fy
  let w11 = fx * fy
  // 环境风 = 位流基场 × 强度;ambient_y 为裸叠加(关卡均未用非零垂直风,未烘焙垂直基)
  pm.out_vx = u[a].to_double() * w00 +
    u[b].to_double() * w10 +
    u[c].to_double() * w01 +
    u[d].to_double() * w11 +
    pm.ambient_x *
    (
      fx_u[a].to_double() * w00 +
      fx_u[b].to_double() * w10 +
      fx_u[c].to_double() * w01 +
      fx_u[d].to_double() * w11
    )
  pm.out_vy = v[a].to_double() * w00 +
    v[b].to_double() * w10 +
    v[c].to_double() * w01 +
    v[d].to_double() * w11 +
    pm.ambient_x *
    (
      fx_v[a].to_double() * w00 +
      fx_v[b].to_double() * w10 +
      fx_v[c].to_double() * w01 +
      fx_v[d].to_double() * w11
    ) +
    pm.ambient_y
}

///|
/// Sampled velocity X component (after sample_velocity).
#export_name("outX")
pub fn out_x() -> Double {
  pm.out_vx
}

///|
/// Sampled velocity Y component (after sample_velocity).
#export_name("outY")
pub fn out_y() -> Double {
  pm.out_vy
}

// 感受到的总温度 = 场温 + 环境偏置(与浮力消费同一事实源)

// 4 抽头双线性(gx/gy 已 clamp 的格坐标):操作数顺序固定 = 逐位契约,fluid/tracers 共用

///|
fn bilinear4(
  f : FixedArray[Float],
  nx : Int,
  gx : Double,
  gy : Double,
) -> Double {
  let i0 = gx.floor().to_int()
  let j0 = gy.floor().to_int()
  let fx = gx - i0.to_double()
  let fy = gy - j0.to_double()
  let a = i0 + j0 * nx
  f[a].to_double() * (1.0 - fx) * (1.0 - fy) +
  f[a + 1].to_double() * fx * (1.0 - fy) +
  f[a + nx].to_double() * (1.0 - fx) * fy +
  f[a + nx + 1].to_double() * fx * fy
}

///|
/// Sample the total temperature at (wx, wy) = field temperature + ambient bias.
#export_name("sampleTemp")
pub fn sample_temp(wx : Double, wy : Double) -> Double {
  let nx = pm.nx
  let ny = pm.ny
  let mut gx = wx / pm.cell - 0.5 + pm.ox.to_double()
  let mut gy = wy / pm.cell - 0.5 + pm.oy.to_double()
  if gx < 0.0 {
    gx = 0.0
  } else if gx > nx.to_double() - 1.001 {
    gx = nx.to_double() - 1.001
  }
  if gy < 0.0 {
    gy = 0.0
  } else if gy > ny.to_double() - 1.001 {
    gy = ny.to_double() - 1.001
  }
  bilinear4(t, nx, gx, gy) + pm.ambient_t
}

///|
fn copy_fields() -> Unit {
  let bytes = pm.nx * pm.ny * 4
  mem_copy(addr_of_f32(u0), addr_of_f32(u), bytes)
  mem_copy(addr_of_f32(v0), addr_of_f32(v), bytes)
  mem_copy(addr_of_f32(t0), addr_of_f32(t), bytes)
}

// 单趟半拉格朗日平流:sign=1 回溯 / -1 前推(gather 无法向量化,天然标量)

///|
fn advect_pass(
  dst : FixedArray[Float],
  src : FixedArray[Float],
  dt : Double,
  sign : Double,
) -> Unit {
  let nx = pm.nx
  let ny = pm.ny
  let dt0 = dt / pm.cell * sign
  let ac = pm.air_count
  for m in 0.. nx.to_double() - 1.5 {
      x = nx.to_double() - 1.5
    }
    if y < 0.5 {
      y = 0.5
    } else if y > ny.to_double() - 1.5 {
      y = ny.to_double() - 1.5
    }
    let i0 = x.to_int()
    let j0 = y.to_int()
    let fx = x - i0.to_double()
    let fy = y - j0.to_double()
    let a = i0 + j0 * nx
    let b = a + 1
    let c = a + nx
    let d = c + 1
    dst[idx] = Float::from_double(
      src[a].to_double() * (1.0 - fx) * (1.0 - fy) +
      src[b].to_double() * fx * (1.0 - fy) +
      src[c].to_double() * (1.0 - fx) * fy +
      src[d].to_double() * fx * fy,
    )
  }
}

// MacCormack 补偿单格:9 邻域 min/max 钳制防过冲,末乘阻尼(固体格由 enforce_boundary 清零,不写)

///|
fn correct_cell(
  idx : Int,
  dst : FixedArray[Float],
  src : FixedArray[Float],
  damping : Double,
) -> Unit {
  let nx = pm.nx
  let mut lo = src[idx].to_double()
  let mut hi = lo
  let mut s = src[idx - nx - 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx - nx].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx - nx + 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx - 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx + 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx + nx - 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx + nx].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  s = src[idx + nx + 1].to_double()
  if s < lo {
    lo = s
  } else if s > hi {
    hi = s
  }
  let mut val = q1[idx].to_double() +
    (src[idx].to_double() - q2[idx].to_double()) * 0.5
  if val < lo {
    val = lo
  } else if val > hi {
    val = hi
  }
  dst[idx] = Float::from_double(val * damping)
}

///|
fn advect_maccormack(
  dst : FixedArray[Float],
  src : FixedArray[Float],
  dt : Double,
  damping : Double,
) -> Unit {
  advect_pass(q1, src, dt, 1.0)
  advect_pass(q2, q1, dt, -1.0)
  let ac = pm.air_count
  for m in 0.. Unit {
  buoyancy2(pm.buoyancy * dt, pm.ambient_t)
}

// 边距吸收层:仅扫左/右/上三条边距带,系数随深入边距线性增强——开放大气的替身,
// 风与热流出地图后被吸收,不撞外壁反射回场内

///|
fn apply_sponge() -> Unit {
  let m = pm.margin_cells
  if m <= 0 {
    return
  }
  let nx = pm.nx
  let ny = pm.ny
  let md = m.to_double()
  for j in 1..<(ny - 1) {
    let row = j * nx
    for i in 1..<(m + 1) {
      let s = (md - i.to_double()) / md
      let kv = Float::from_double(
        pm.sponge_vel_in + (pm.sponge_vel_out - pm.sponge_vel_in) * s,
      )
      let kt = Float::from_double(1.0 + (pm.sponge_t_out - 1.0) * s)
      let l = i + row
      let r = nx - 1 - i + row
      u[l] = u[l] * kv
      v[l] = v[l] * kv
      t[l] = t[l] * kt
      u[r] = u[r] * kv
      v[r] = v[r] * kv
      t[r] = t[r] * kt
    }
  }
  for j in 1..<(m + 1) {
    let row = j * nx
    let s = (md - j.to_double()) / md
    let kv = Float::from_double(
      pm.sponge_vel_in + (pm.sponge_vel_out - pm.sponge_vel_in) * s,
    )
    let kt = Float::from_double(1.0 + (pm.sponge_t_out - 1.0) * s)
    for i in (m + 1)..<(nx - m - 1) {
      let idx = i + row
      u[idx] = u[idx] * kv
      v[idx] = v[idx] * kv
      t[idx] = t[idx] * kt
    }
  }
}

///|
fn project_div() -> Unit {
  let h = pm.cell
  let inv2h = 1.0 / (2.0 * h)
  let h2 = h * h
  let nx = pm.nx
  let ac = pm.air_count
  for m in 0.. Unit {
  let nx = pm.nx
  if bulk[idx] != b'\x00' {
    p[idx] = Float::from_double(
      (
        p[idx - 1].to_double() +
        p[idx + 1].to_double() +
        p[idx - nx].to_double() +
        p[idx + nx].to_double() -
        div_h2[idx]
      ) *
      0.25,
    )
  } else {
    let p_l : Double = if solid[idx - 1] != b'\x00' {
      p[idx].to_double()
    } else {
      p[idx - 1].to_double()
    }
    let p_r : Double = if solid[idx + 1] != b'\x00' {
      p[idx].to_double()
    } else {
      p[idx + 1].to_double()
    }
    let p_u : Double = if solid[idx - nx] != b'\x00' {
      p[idx].to_double()
    } else {
      p[idx - nx].to_double()
    }
    let p_d : Double = if solid[idx + nx] != b'\x00' {
      p[idx].to_double()
    } else {
      p[idx + nx].to_double()
    }
    p[idx] = Float::from_double((p_l + p_r + p_u + p_d - div_h2[idx]) * 0.25)
  }
}

///|
fn project_gs() -> Unit {
  for _ in 0.. Unit {
  let inv2h = 1.0 / (2.0 * pm.cell)
  let nx = pm.nx
  let ac = pm.air_count
  for m in 0.. Unit {
  project_div()
  project_gs()
  project_grad()
}

///|
/// Advance the fluid simulation by dt (seconds): buoyancy, advection, sponge, projection.
#export_name("step")
pub fn step(dt : Double) -> Unit {
  apply_buoyancy(dt)
  copy_fields()
  advect_maccormack(u, u0, dt, pm.vel_damping)
  advect_maccormack(v, v0, dt, pm.vel_damping)
  advect_maccormack(t, t0, dt, pm.t_damping)
  apply_sponge()
  project()
  enforce_boundary()
}

///|
fn enforce_boundary() -> Unit {
  for k in 0.. Int {
  addr_of_f32(u)
}

///|
/// Linear-memory address of the velocity Y field (Float32Array, nx*ny).
#export_name("fieldV")
pub fn field_v() -> Int {
  addr_of_f32(v)
}

///|
/// Linear-memory address of the temperature field (Float32Array, nx*ny).
#export_name("fieldT")
pub fn field_t() -> Int {
  addr_of_f32(t)
}

///|
/// Linear-memory address of the solid mask (Uint8Array, nx*ny; nonzero = solid).
#export_name("solidBuf")
pub fn solid_buf() -> Int {
  addr_of_u8(solid)
}

///|
/// Linear-memory address of the ambient basis velocity X field (Float32Array).
#export_name("fieldFxU")
pub fn field_fx_u() -> Int {
  addr_of_f32(fx_u)
}

///|
/// Linear-memory address of the ambient basis velocity Y field (Float32Array).
#export_name("fieldFxV")
pub fn field_fx_v() -> Int {
  addr_of_f32(fx_v)
}

///|
/// Maximum grid width (nx) supported by the fixed-capacity kernel.
#export_name("fMaxNx")
pub fn f_max_nx() -> Int {
  grid_max_nx
}

///|
/// Maximum grid height (ny) supported by the fixed-capacity kernel.
#export_name("fMaxNy")
pub fn f_max_ny() -> Int {
  grid_max_ny
}