// 示踪粒子内核(assembly/tracers.ts 的 Moonbit 同构移植):粒子状态全量驻留 wasm 内存,
// 宿主每 tick 只写热源表并单次调用推进——风场采样直调流体(同包零跨界)、地形走宿主烘焙 SDF 场。
// 粒子纯视觉不涉判定:PRNG 用确定性 mulberry32(宿主播种),与 AS 位级一致。

///|
let t_count : Int = 400

///|
let t_trail_len : Int = 24

///|
let t_trail_sample : Double = 0.45

///|
let t_respawn_tries : Int = 8

///|
let t_plume_radius : Double = 1.6

///|
let t_plume_life_min : Double = 0.9

///|
let t_plume_life_span : Double = 1.2
// 自然死亡转生羽流的概率:稳态羽流密度 ≈ 死亡率×概率×羽流寿命,与原强夺式注入同量级

///|
let t_plume_chance : Double = 0.7

///|
let t_plume_tries : Int = 4
// 触地淡出时长:与宿主包络 FADE_OUT 同源(app/sim/particles.ts envelope)——
// 尾迹可见度被 env 调制,同一常数保证粒子与尾迹同步溶散
// 地形场容量 = 流体网格上限(grid.mbt 单点),超限 init 拒绝

///|
let t_fade_out : Double = 0.7

///|
let sdf_capacity : Int = grid_max_cells

///|
let src_capacity : Int = 32

///|
let tx : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let ty : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let t_life : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let t_max_life : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let t_odo : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let t_last_odo : FixedArray[Float] = FixedArray::make(t_count, 0.0)

///|
let trail_x : FixedArray[Float] = FixedArray::make(t_count * t_trail_len, 0.0)

///|
let trail_y : FixedArray[Float] = FixedArray::make(t_count * t_trail_len, 0.0)

///|
let trail_t : FixedArray[Float] = FixedArray::make(t_count * t_trail_len, 0.0)

///|
let trail_n : FixedArray[Byte] = FixedArray::make(t_count, b'\x00')
// 地形 SDF 场:宿主烘焙后原样上传(与流体掩码/飞机碰撞同源),格心值、双线性采样

///|
let t_sdf : FixedArray[Float] = FixedArray::make(sdf_capacity, 0.0)

///|
let src_buf : FixedArray[Float] = FixedArray::make(src_capacity * 2, 0.0)

///|
priv struct TState {
  mut time : Double
  mut world_w : Double
  mut world_h : Double
  mut margin : Double
  mut snx : Int
  mut sny : Int
  mut scell : Double
  mut sox : Double
  mut soy : Double
  mut rng_state : UInt
  mut src_now : Int
}

///|
let ts : TState = TState::{
  time: 0.0,
  world_w: 0.0,
  world_h: 0.0,
  margin: 0.0,
  snx: 0,
  sny: 0,
  scell: 1.0,
  sox: 0.0,
  soy: 0.0,
  rng_state: 0x9e3779b9U,
  src_now: 0,
}

// mulberry32:UInt 算术自然回绕、>> 为逻辑右移,与 JS/AS 的 imul + >>> 位级等价

///|
fn rnd() -> Double {
  ts.rng_state = ts.rng_state + 0x6d2b79f5U
  let mut z = ts.rng_state
  z = (z ^ (z >> 15)) * (z | 1U)
  z = z ^ (z + (z ^ (z >> 7)) * (z | 61U))
  (z ^ (z >> 14)).to_double() / 4294967296.0
}

// 双线性采样烘焙场:clamp 约定与流体 sample 同构(域外取边缘值 = 地形延展)

///|
fn sdf_at(x : Double, y : Double) -> Double {
  let mut gx = x / ts.scell - 0.5 + ts.sox
  let mut gy = y / ts.scell - 0.5 + ts.soy
  if gx < 0.0 {
    gx = 0.0
  } else if gx > ts.snx.to_double() - 1.001 {
    gx = ts.snx.to_double() - 1.001
  }
  if gy < 0.0 {
    gy = 0.0
  } else if gy > ts.sny.to_double() - 1.001 {
    gy = ts.sny.to_double() - 1.001
  }
  bilinear4(t_sdf, ts.snx, gx, gy)
}

///|
fn reset_trail(i : Int) -> Unit {
  t_odo[i] = 0.0
  t_last_odo[i] = 0.0
  trail_n[i] = b'\x00'
}

///|
fn record_trail(i : Int) -> Unit {
  let base = i * t_trail_len
  let n = trail_n[i].to_int()
  if n < t_trail_len {
    trail_x[base + n] = tx[i]
    trail_y[base + n] = ty[i]
    trail_t[base + n] = Float::from_double(ts.time)
    trail_n[i] = (n + 1).to_byte()
  } else {
    for k in 0..<(t_trail_len - 1) {
      trail_x[base + k] = trail_x[base + k + 1]
      trail_y[base + k] = trail_y[base + k + 1]
      trail_t[base + k] = trail_t[base + k + 1]
    }
    trail_x[base + t_trail_len - 1] = tx[i]
    trail_y[base + t_trail_len - 1] = ty[i]
    trail_t[base + t_trail_len - 1] = Float::from_double(ts.time)
  }
  t_last_odo[i] = t_odo[i]
}

// 有热源时自然死亡按概率转生为羽流:粒子本已淡出完毕,无 alpha 突变;
// 旧式强夺活粒子会让被夺者可见轨迹瞬消

///|
fn respawn(i : Int, scatter : Bool) -> Unit {
  if !scatter && ts.src_now > 0 && rnd() < t_plume_chance {
    for _ in 0.. Int {
  if count != t_count || trail_len != t_trail_len {
    return 1
  }
  // scell 非正/NaN(采样除零/NaN 场)拒绝;!(>0) 同时捕获 NaN(NaN 比较恒 false)
  if snx < 2 || sny < 2 || snx * sny > sdf_capacity || !(scell > 0.0) {
    return 2
  }
  ts.world_w = world_w
  ts.world_h = world_h
  ts.margin = margin
  ts.snx = snx
  ts.sny = sny
  ts.scell = scell
  ts.sox = sox
  ts.soy = soy
  ts.rng_state = seed
  ts.time = 0.0
  for i in 0.. Unit {
  ts.time = ts.time + dt
  ts.src_now = src_count
  let m = ts.margin
  for i in 0.. ts.world_w + m - 1.0 {
      if t_life[i].to_double() > t_fade_out {
        t_life[i] = Float::from_double(t_fade_out)
      }
      continue
    }
    let dx = nx - tx[i].to_double()
    let dy = ny - ty[i].to_double()
    t_odo[i] = Float::from_double(
      t_odo[i].to_double() + (dx * dx + dy * dy).sqrt(),
    )
    tx[i] = Float::from_double(nx)
    ty[i] = Float::from_double(ny)
    if t_odo[i].to_double() - t_last_odo[i].to_double() >= t_trail_sample {
      record_trail(i)
    }
  }
}

///|
/// Current simulation time (seconds).
#export_name("tTime")
pub fn t_time() -> Double {
  ts.time
}

///|
/// Linear-memory address of the tracer X positions (Float32Array, count).
#export_name("tXBuf")
pub fn t_x_buf() -> Int {
  addr_of_f32(tx)
}

///|
/// Linear-memory address of the tracer Y positions (Float32Array, count).
#export_name("tYBuf")
pub fn t_y_buf() -> Int {
  addr_of_f32(ty)
}

///|
/// Linear-memory address of the remaining lifetime per tracer (Float32Array, count).
#export_name("tLifeBuf")
pub fn t_life_buf() -> Int {
  addr_of_f32(t_life)
}

///|
/// Linear-memory address of the max lifetime per tracer (Float32Array, count).
#export_name("tMaxLifeBuf")
pub fn t_max_life_buf() -> Int {
  addr_of_f32(t_max_life)
}

///|
/// Linear-memory address of the trail X control points (Float32Array, count*trailLen).
#export_name("tTrailXBuf")
pub fn t_trail_x_buf() -> Int {
  addr_of_f32(trail_x)
}

///|
/// Linear-memory address of the trail Y control points (Float32Array, count*trailLen).
#export_name("tTrailYBuf")
pub fn t_trail_y_buf() -> Int {
  addr_of_f32(trail_y)
}

///|
/// Linear-memory address of the trail write times (Float32Array, count*trailLen).
#export_name("tTrailTBuf")
pub fn t_trail_t_buf() -> Int {
  addr_of_f32(trail_t)
}

///|
/// Linear-memory address of the trail length per tracer (Uint8Array, count).
#export_name("tTrailNBuf")
pub fn t_trail_n_buf() -> Int {
  addr_of_u8(trail_n)
}

///|
/// Linear-memory address of the tracer SDF field (Float32Array, snx*sny).
#export_name("tSdfBuf")
pub fn t_sdf_buf() -> Int {
  addr_of_f32(t_sdf)
}

///|
/// Tracer SDF field capacity (in cells, equals the fluid grid upper bound).
#export_name("tSdfCap")
pub fn t_sdf_cap() -> Int {
  sdf_capacity
}

///|
/// Linear-memory address of the hot/cold source table (Float32Array, src_cap*6).
#export_name("tSrcBuf")
pub fn t_src_buf() -> Int {
  addr_of_f32(src_buf)
}

///|
/// Source table capacity (in source slots).
#export_name("tSrcCap")
pub fn t_src_cap() -> Int {
  src_capacity
}