/**
 * Pressure solver selection. Both solve the same A·p = -div system over the
 * same neighbour-mask topology; the difference is convergence rate and per-iter
 * cost.
 *
 *   - `"sor"` — red-black Successive Over-Relaxation, our long-standing
 *     default. One sweep is cheap (~3-5 µs/cell at 128³), convergence is
 *     O(N) iterations for fixed accuracy.
 *
 *   - `"mic-pcg"` — Modified Incomplete Cholesky preconditioned Conjugate
 *     Gradient. ~2× the per-iter cost of SOR, but converges in O(√N) iters
 *     (typically 8-12 vs SOR's 30+ at 128³), so a net 1.5-2× speedup at
 *     quality parity for production grids. Allocates 5 additional N-sized
 *     scratch buffers (r, z, s, As, precon).
 */
export type PressureSolver = string;
export namespace PressureSolver {
    let SOR: string;
    let MICPCG: string;
}
/**
 * Cell-centered 3D Stable Fluids solver (Stam, 1999/2003).
 *
 * Holds tuning knobs plus transient working memory (per-step velocity snapshots,
 * divergence, ping-pong scratch). None of that working memory carries information
 * across step boundaries — its value is overwritten before being read on the next
 * step. Replacing the simulator instance between two steps of the same field
 * produces an identical result (the only cross-step state, `pressure`, lives on
 * the field). Scratch is cached by cell-count so per-step allocation stays at zero.
 *
 * Per step:
 *   1. Apply external forces (effectors mutate field velocity).
 *   2. Diffuse velocity (viscosity; skipped when `velocity_diffusion_rate == 0`).
 *   3. Project (pressure solve + gradient subtract) to enforce ∇·v ≈ 0.
 *   4. Self-advect velocity (semi-Lagrangian back-trace).
 *   5. Project again (advection introduces small divergence).
 *   6. For each scalar attribute on the field: optionally diffuse, then advect.
 *
 * Pressure is warm-started from the previous step's solution (`field.pressure`),
 * which converges much faster than starting from zero once the flow reaches a
 * quasi-steady state.
 *
 * Compute model: synchronous, main-thread. Cost scales linearly with cell count and
 * with iteration counts. The pressure SOR sweep count is auto-scaled to grid size
 * (see {@link pressure_iteration_scale}) so the per-cell budget stays roughly flat
 * across resolutions instead of over-spending at 32³ and under-spending at 256³.
 */
/**
 * Minimum iteration count when `pressure_iteration_scale > 0`. Picked so very
 * small grids (8³, 16³) still get enough sweeps to converge — at those sizes
 * `scale * max_res` underflows the practical "useful work" zone. For grids
 * larger than `floor / scale` the scale formula dominates and this constant
 * stops mattering. Caller may inspect / override if a per-deployment minimum
 * makes sense.
 *
 * @type {number}
 */
export const PRESSURE_ITERATIONS_FLOOR: number;
export class FluidSimulator {
    /**
     * Implicit-diffusion coefficient (= viscosity * dt-like scaling). 0 disables.
     * @type {number}
     */
    velocity_diffusion_rate: number;
    /**
     * Jacobi iterations for the velocity diffusion solve. Only consulted when
     * `velocity_diffusion_rate > 0`.
     * @type {number}
     */
    velocity_diffusion_iterations: number;
    /**
     * Implicit-diffusion coefficient applied uniformly to all scalar attributes. 0
     * disables. Semi-Lagrangian advection already introduces visible numerical
     * diffusion, so leaving this at 0 is usually fine.
     * @type {number}
     */
    scalar_diffusion_rate: number;
    /**
     * @type {number}
     */
    scalar_diffusion_iterations: number;
    /**
     * Which pressure solver to use. `PressureSolver.SOR` (default) is the
     * baseline red-black Successive Over-Relaxation. `PressureSolver.MICPCG`
     * switches to Modified Incomplete Cholesky preconditioned Conjugate
     * Gradient, which typically converges in 1/3 the iterations at 2× per-iter
     * cost (net ~1.5-2× faster wall time at quality parity on production
     * grids). MIC-PCG allocates an additional 5 N-sized scratch buffers, so
     * peak memory is higher.
     *
     * Switching at runtime is supported but flushes the warm-started pressure
     * meaning for one step (both solvers warm-start from `field.pressure`,
     * but the convergence trajectory is different — first frame after a
     * switch will look "fresh", not warm).
     *
     * @type {string}
     */
    pressure_solver: string;
    /**
     * Run the pressure projection *before* self-advection in addition to after.
     *
     * The Stam (1999/2003) reference solver projects twice per step:
     *
     *     [forces] → [diffuse] → **project₁** → [advect] → **project₂** → [scalars]
     *
     * `project₁` ensures the velocity field that's *fed into* semi-Lagrangian
     * advection is divergence-free, so back-traced trajectories follow a physical
     * flow. `project₂` cleans up the small divergence advection re-introduces.
     *
     * Skipping `project₁` (this flag = false) cuts the pressure-solve cost roughly
     * in half — at default settings, pressure is ~80% of step() time, and the two
     * projections share that ~evenly. The cost is that effectors' splatted
     * impulses (which inject divergence) and any post-diffusion residual get
     * carried into advection un-projected, then have to be cleaned up by the
     * single post-advection projection.
     *
     * In practice:
     *   - If diffusion is OFF (rate=0) and effectors are gentle (gentle wind, no
     *     big impulses), the visual quality cost is small. The post-advection
     *     project handles everything in roughly the same number of iterations.
     *   - If diffusion is ON, or you have strong impulses (explosions, large
     *     wakes), the second-only solver is asked to fix everything in one pass
     *     and may visibly under-converge — flicker, "pumping", or divergent
     *     vortices.
     *
     * Default `true` (= Stam reference behavior) for correctness; flip to `false`
     * if profiling shows pressure as a bottleneck and quality A/B looks acceptable.
     *
     * @type {boolean}
     */
    project_before_advection: boolean;
    /**
     * Adaptive scaling factor for the pressure Poisson SOR sweep count. The actual
     * iteration count per `project()` call is
     *
     *     iters = max(PRESSURE_ITERATIONS_FLOOR, ceil(pressure_iteration_scale * max(rx, ry, rz)))
     *
     * unless `pressure_iteration_scale <= 0`, in which case projection is skipped
     * entirely (useful for tests that need to inspect un-projected velocity or
     * tear-down scenarios).
     *
     * Why scaled by resolution? Red-black SOR with the optimal omega converges
     * geometrically with a rate that depends on grid size — the iterations needed
     * to reach a given residual scale roughly as `O(N)` where N is the max grid
     * extent. A fixed iteration count therefore over-spends at small grids and
     * under-spends at large ones; the default value of 30 (from the pre-adaptive
     * era) was right for ~128³ but wasteful at 32³ and marginal at 256³.
     *
     * Default scale `0.25` plus floor `8` chosen empirically against the bench
     * sweep in `bench.spec.js` (`pressure_iterations convergence + step() cost`)
     * — it's the smallest setting that keeps a stress-test divergent velocity
     * field stable under continuous stepping across the 32³ → 128³ range. For a
     * gentler real-world flow you can drop the scale further; for cold-start
     * spawn frames or scenes with heavy impulses, bump it up.
     *
     * @type {number}
     */
    pressure_iteration_scale: number;
    /**
     * Resolve the per-call SOR iteration count from {@link pressure_iteration_scale}
     * and the field's resolution. Public so callers can budget frame time or query
     * the actual cost in advance.
     *
     *   - `scale <= 0` → 0 (projection disabled).
     *   - `scale > 0`  → `max(PRESSURE_ITERATIONS_FLOOR, ceil(scale * max(rx, ry, rz)))`.
     *
     * @param {number} res_x
     * @param {number} res_y
     * @param {number} res_z
     * @return {number} non-negative integer iteration count.
     */
    resolvePressureIterations(res_x: number, res_y: number, res_z: number): number;
    /**
     * Project the field's velocity to be (approximately) divergence-free. Exposed
     * because tests and external code may need to project on demand (e.g., after
     * splatting an out-of-band impulse). `field.pressure` is read as the warm-start
     * initial guess and written with the new solution.
     * @param {FluidField} field
     */
    project(field: FluidField): void;
    /**
     * Advance the simulation by one timestep.
     *
     * @param {FluidField} field
     * @param {number} time_delta            seconds (or grid-cells-per-velocity-unit;
     *                                       caller defines the convention)
     * @param {AbstractFluidEffector[]} effectors    Pass `[]` for no external forces.
     * @param {Float32Array|number[]} world_to_grid  Column-major 4×4 affine sending
     *                                               world-space points into this
     *                                               field's grid space. Passed through
     *                                               to each effector's `apply`. Pass
     *                                               `M4_IDENTITY` when effectors are
     *                                               already configured in grid space.
     */
    step(field: FluidField, time_delta: number, effectors: AbstractFluidEffector[], world_to_grid: Float32Array | number[]): void;
    #private;
}
//# sourceMappingURL=FluidSimulator.d.ts.map