Skip to content

Deco Engine

The DiveSuite decompression engine implements the Buhlmann ZHL-16C algorithm using the dive-deco Rust crate, compiled to WebAssembly for cross-platform execution.

We implement the Buhlmann ZHL-16C dissolved gas model via the dive-deco library (v6.0.6):

  • 16 tissue compartments with half-times from 4 to 635 minutes
  • M-values determine maximum tolerable tissue pressure
  • Gradient factors (GF) allow conservative adjustment (default: GF 30/85)
  • Trimix support for helium-based mixes

Why Buhlmann?

  • Well-documented, peer-reviewed algorithm
  • Used by most modern dive computers
  • Open specification (no licensing issues)
  • Subsurface uses the same algorithm (validation reference)

The deco engine is written in Rust and compiled to WebAssembly:

Rust Source -> wasm-pack -> WASM Binary -> React Native
^
TypeScript Bridge

Why Rust?

  • Memory safety without garbage collection
  • Catches many bugs at compile time
  • Excellent WASM support
  • High performance for real-time calculations
rust-engine/
├── src/
│ ├── lib.rs # Public API, WASM exports
│ ├── engine.rs # Core calculation engine (dive-deco wrapper)
│ ├── gas.rs # Gas physics (MOD, END, EAD, density, HPNS, ICD)
│ ├── types.rs # Shared types (input/output contracts)
│ ├── error.rs # Error handling
│ ├── validation.rs # Input validation
│ ├── altitude.rs # Altitude diving calculations
│ ├── best_mix.rs # Best mix calculator (optimal gas for depth)
│ ├── blending.rs # Gas blending (partial pressure fill plans)
│ ├── ccr.rs # CCR gas consumption, flush, hypoxic envelope, setpoint optimizer, bailout optimizer
│ ├── scr.rs # SCR gas consumption
│ ├── cns_otu.rs # CNS% and OTU oxygen toxicity tracking
│ ├── dpv.rs # DPV/scooter planning (range, bailout)
│ ├── reverse_planning.rs # Reverse planning and turn pressure
│ ├── surface_interval.rs # Tissue state, off-gassing, no-fly time
│ └── vpmb.rs # VPM-B bubble model algorithm
├── tests/
│ ├── engine_validation.rs # First-principles deco validation
│ ├── validation_deco.rs # Deco stop validation
│ ├── validation_integration.rs # Integration tests
│ ├── validation_cns_otu.rs # CNS/OTU tests
│ └── validation_surface_interval.rs # Repetitive dive tests
├── pkg/ # Generated WASM output
└── Cargo.toml
// TypeScript interface (src/core/engine/types.ts)
interface DecoEngineInput {
depth: number; // meters
bottomTime: number; // minutes
gasMix: {
o2: number; // fraction (0.21 = 21%)
he: number; // fraction
n2: number; // fraction (computed)
};
altitude: number; // meters above sea level
gfLow: number; // 0-100
gfHigh: number; // 0-100
sacRate: number; // L/min at surface
tankSize: number; // liters
startPressure: number; // bar
ascentRate: number; // m/min
}
interface DecoEngineOutput {
ndl: number; // minutes (0 if in deco)
mod: number; // max operating depth
end: number; // equivalent narcotic depth
plan: DivePlanPhase[]; // dive phases
gasConsumption: {
required: number; // liters
available: number; // liters
reserve: number; // liters remaining
warning: 'ok' | 'low' | 'critical';
};
tissueLoading: number[]; // 16 compartment values
noFlyTime: number; // minutes
warnings: string[]; // safety warnings
}

The CCR module (ccr.rs) provides specialized calculations for closed-circuit rebreather diving:

FeatureFunctionDescription
Gas Consumptioncalculate_ccr_gas()Metabolic O2, diluent, scrubber tracking, bailout
Diluent Flushcalculate_diluent_flush()Exponential decay model for loop composition change
Hypoxic Envelopecalculate_hypoxic_envelope()Safe depth range for hypoxic trimix diluents
Setpoint Optimizercalculate_optimal_setpoint()Phase-based setpoint schedule for O2 savings
Bailout Optimizercalculate_bailout_optimizer()ICD/density/narcosis scoring for bailout gases

The DPV module (dpv.rs) calculates:

  • Maximum penetration distance (gas-limited or battery-limited)
  • Gas consumption during tow (reduced SAC: 60-70% of normal)
  • Swim-back bailout gas requirements if scooter fails at max penetration

The engine supports depth-segmented ascent rates via AscentRateSegment:

  • Each segment defines a rate for depths from 0 to max_depth
  • Presets available: Standard (9/3), GUE Deep (10/6/3), IANTD (9/6/3)
  • Falls back to single ascent_rate if no profile is defined
MetricTargetCurrent
Total tests457+
Line coverage95%+95%+
Property tests1414
Validation tests22+29
CCR tests20
DPV tests8

Invariants that must hold for all valid inputs (see rust-engine/tests/properties.rs):

proptest! {
// NDL always decreases or stays same as depth increases
#[test]
fn ndl_decreases_with_depth(depth in 10.0f64..50.0) {
let shallow = calculate_ndl_for_test(depth, GasMix::air(), 0.30, 0.85);
let deep = calculate_ndl_for_test(depth + 3.0, GasMix::air(), 0.30, 0.85);
prop_assert!(deep <= shallow);
}
// Higher GF means more lenient (longer) NDL
#[test]
fn higher_gf_means_longer_ndl(depth in 15.0f64..40.0, gf in 0.5f64..0.95) {
let conservative = calculate_ndl_for_test(depth, GasMix::air(), gf * 0.5, gf);
let liberal = calculate_ndl_for_test(depth, GasMix::air(), (gf + 0.05) * 0.5, gf + 0.05);
prop_assert!(liberal >= conservative);
}
// Gas consumption always positive and increases with depth/time
#[test]
fn gas_consumption_always_positive(
depth in 1.0f64..60.0,
time in 1.0f64..120.0,
sac in 10.0f64..30.0
) {
let gas = calculate_gas_consumption_for_test(depth, time, sac);
prop_assert!(gas > 0.0);
}
}

Cross-reference against known implementations (see rust-engine/tests/validation.rs):

ReferenceAlgorithmExpected Match
Buhlmann ZHL-16CBuhlmann ZHL-16CExact
SubsurfaceBuhlmann ZHL-16CNear exact
MultiDecoBuhlmann ZHL-16CNear exact
V-PlannerVPM-BDifferent (bubble model)
PADI RDPDSATDifferent (different algorithm)

Test vectors with GF 100/100 (no conservatism):

#[test]
fn validate_buhlmann_ndl_values() {
// Buhlmann ZHL-16C reference values at GF 100/100
let test_cases = [
(12.0, 124, 5), // ~124 min NDL at 12m
(15.0, 72, 5), // ~72 min NDL at 15m
(18.0, 52, 3), // ~52 min NDL at 18m
(21.0, 37, 2), // ~37 min NDL at 21m
(24.0, 27, 2), // ~27 min NDL at 24m
(27.0, 22, 2), // ~22 min NDL at 27m
(30.0, 18, 2), // ~18 min NDL at 30m
(33.0, 15, 2), // ~15 min NDL at 33m
(36.0, 13, 2), // ~13 min NDL at 36m
(40.0, 10, 2), // ~10 min NDL at 40m
(42.0, 9, 2), // ~9 min NDL at 42m
];
for (depth, expected_ndl, tolerance) in test_cases {
let result = calculate_ndl_for_test(depth, GasMix::air(), 1.0, 1.0);
assert!((result - expected_ndl).abs() <= tolerance,
"Mismatch at {}m: got {} expected {} (+-{})", depth, result, expected_ndl, tolerance);
}
}

The deco engine never panics. All errors are returned as Result:

#[derive(Debug, thiserror::Error)]
pub enum DecoError {
#[error("Invalid depth: {0}m (must be 0-300m)")]
InvalidDepth(f64),
#[error("Invalid gas mix: O2={o2}, He={he}, N2={n2}")]
InvalidGasMix { o2: f64, he: f64, n2: f64 },
#[error("ppO2 exceeds limit: {0} (max {1})")]
PpO2Exceeded(f64, f64),
#[error("Calculation overflow at depth {0}m")]
CalculationOverflow(f64),
}

The engine is optimized for real-time calculation:

OperationTargetNotes
Single NDL calculation<5msTypical recreational dive
Full deco plan (60m, 30min)<50msMulti-gas tech dive
What-if comparison (3 plans)<150msSide-by-side comparison

Benchmarks run on every PR to prevent regressions.