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.
Algorithm
Section titled “Algorithm”Buhlmann ZHL-16C
Section titled “Buhlmann ZHL-16C”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)
Not DSAT
Section titled “Not DSAT”Implementation
Section titled “Implementation”Rust + WASM
Section titled “Rust + WASM”The deco engine is written in Rust and compiled to WebAssembly:
Rust Source -> wasm-pack -> WASM Binary -> React Native ^ TypeScript BridgeWhy Rust?
- Memory safety without garbage collection
- Catches many bugs at compile time
- Excellent WASM support
- High performance for real-time calculations
Module Structure
Section titled “Module Structure”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.tomlAPI Contract
Section titled “API Contract”// 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}CCR Engine Features
Section titled “CCR Engine Features”The CCR module (ccr.rs) provides specialized calculations for closed-circuit rebreather diving:
| Feature | Function | Description |
|---|---|---|
| Gas Consumption | calculate_ccr_gas() | Metabolic O2, diluent, scrubber tracking, bailout |
| Diluent Flush | calculate_diluent_flush() | Exponential decay model for loop composition change |
| Hypoxic Envelope | calculate_hypoxic_envelope() | Safe depth range for hypoxic trimix diluents |
| Setpoint Optimizer | calculate_optimal_setpoint() | Phase-based setpoint schedule for O2 savings |
| Bailout Optimizer | calculate_bailout_optimizer() | ICD/density/narcosis scoring for bailout gases |
DPV Planning
Section titled “DPV Planning”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
Custom Ascent Profiles
Section titled “Custom Ascent Profiles”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_rateif no profile is defined
Validation
Section titled “Validation”Test Coverage
Section titled “Test Coverage”| Metric | Target | Current |
|---|---|---|
| Total tests | — | 457+ |
| Line coverage | 95%+ | 95%+ |
| Property tests | 14 | 14 |
| Validation tests | 22+ | 29 |
| CCR tests | — | 20 |
| DPV tests | — | 8 |
Property-Based Tests
Section titled “Property-Based Tests”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); }}Reference Validation
Section titled “Reference Validation”Cross-reference against known implementations (see rust-engine/tests/validation.rs):
| Reference | Algorithm | Expected Match |
|---|---|---|
| Buhlmann ZHL-16C | Buhlmann ZHL-16C | Exact |
| Subsurface | Buhlmann ZHL-16C | Near exact |
| MultiDeco | Buhlmann ZHL-16C | Near exact |
| V-Planner | VPM-B | Different (bubble model) |
| PADI RDP | DSAT | Different (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); }}Error Handling
Section titled “Error Handling”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),}Performance
Section titled “Performance”The engine is optimized for real-time calculation:
| Operation | Target | Notes |
|---|---|---|
| Single NDL calculation | <5ms | Typical recreational dive |
| Full deco plan (60m, 30min) | <50ms | Multi-gas tech dive |
| What-if comparison (3 plans) | <150ms | Side-by-side comparison |
Benchmarks run on every PR to prevent regressions.