Deco Engine
The DiveSuite decompression engine implements the Buhlmann ZHL-16C algorithm using the dive-deco Rust crate, compiled to WebAssembly. WASM is disabled on the web today, and there is no native module on iOS yet. Until Rust computes there, a TypeScript fallback computes plans on every platform, within its refusal scope (bridge-fallback-scope.ts). The fallback is not validated against Subsurface. Decision D27 makes Rust the only deco engine (planned); see Engine availability.
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 (see the default gradient factors under Input Limits)
- 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)
- VPM-B is not available: the GPL-derived port was removed in #433, and a clean-room version is planned (D5, P1-17). A VPM-B request is refused with an
AlgorithmUnavailableerror; it is never answered with a Buhlmann result
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 -> TypeScript Bridge -> AppOn iOS the same Rust code is meant to run through a native UniFFI module (modules/deco-engine-native/). That module has not shipped yet.
Engine availability
Section titled “Engine availability”Today: the TypeScript fallback computes plans on every platform, because WASM is disabled on the web. It refuses every input it cannot model and never supplies stops of its own. It is not extended. The bridge turns every Rust error except LEVEL_ABOVE_CEILING into a fallback plan.
Planned (D27): Rust becomes the only deco engine.
- Rust computes on the web, and the fallback is removed as soon as it does. Native iOS follows with the UniFFI module (P1-15).
- A persistent fallback notice shows while the fallback is active (P1-19).
- Every Rust error maps to a refusal (P1-18).
- Where no Rust engine runs (iOS until the native module ships, Android until after Release 1, Expo Go, a failed load or a crashed instance), the planner and every other screen that needs the engine show only a persistent, non-dismissible “engine unavailable” card that says nothing was checked (DIV-115). No plan, value or gas warning is shown in its place, not even from cache.
- Tools whose Rust path has no reference test are hidden.
Module Structure
Section titled “Module Structure”rust-engine/├── src/│ ├── lib.rs # Public API│ ├── engine.rs, engine/ # Plan calculation on dive-deco: ascent, deco phases,│ │ # gas switches, NDL, level ceiling, profile check,│ │ # gas consumption, warnings, solver limits│ ├── contract.rs, contract/ # Typed error and warning contract│ ├── error.rs # DecoError│ ├── types.rs, types/ # Input and output types│ ├── validation.rs # Input validation│ ├── gas.rs, gas/ # Gas physics (MOD, END, EAD, density, HPNS, ICD)│ ├── cns_otu.rs # CNS% and OTU oxygen toxicity tracking│ ├── altitude.rs, altitude/ # Altitude adjustments│ ├── best_mix.rs # Best mix calculator│ ├── blending.rs # Gas blending (partial pressure fill plans)│ ├── ccr.rs, scr.rs # Closed-circuit and semi-closed rebreather planning│ ├── dpv.rs # DPV planning│ ├── reverse_planning.rs # Reverse planning and turn pressure│ ├── surface_interval.rs # Tissue state, off-gassing, no-fly time│ └── wasm/ # WASM exports├── tests/ # Validation, property-based and robustness tests├── pkg/ # Generated WASM output└── Cargo.tomlThere is no VPM-B module. See the algorithm section above.
API Contract
Section titled “API Contract”The input and output types are defined once, in Rust (rust-engine/src/types.rs and rust-engine/src/contract.rs). The TypeScript bridge in src/core/engine/ mirrors them. The target (planned, P1-18) is that every failure path in the bridge refuses and returns no output. Today most Rust errors still become a fallback plan.
Input Limits
Section titled “Input Limits”The safety specification sets these limits. What each layer enforces today differs for altitude:
| Parameter | Limit |
|---|---|
| Depth | 0 to 300 m |
| Ascent rate | 1 to 18 m/min |
| Descent rate | 5 to 30 m/min |
| Altitude | specified and decided (D19): at most 4000 m |
| Gradient factors | GF Low at most GF High |
For altitude, the TypeScript input validation refuses values above 4000 m. The Rust validation (MAX_ALTITUDE) still accepts up to 5000 m. This is a known engine bug that is being fixed; the limit is 4000 m. Other inputs are rejected outside range in Rust.
The app’s default gradient factors are 40/85 for recreational divers, 30/85 for technical and 35/80 for CCR (specification section 6.1).
Pressure and MOD
Section titled “Pressure and MOD”- Tissues, ceiling and no-fly time use 1.01325 bar, adjusted for altitude.
- Bottom-gas MOD uses the site pressure (1.01325 bar adjusted for altitude). For example, EAN32 at ppO2 1.4 bar and 2000 m gives about 35.8 m.
- Deco-gas MOD uses the 1-bar convention (D20).
The bottom-gas MOD warning in the Rust engine and the WASM calculate_mod still use 1 bar. They have to follow the site-pressure rule (D27) and do not yet.
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 - Falls back to a single
ascent_rateif no profile is defined
Validation
Section titled “Validation”Engine changes need an explicit owner request, their own PR, an independent safety review and validation against the references below. Every Rust function needs a unit test, with a target of 95%+ coverage, and invariants are covered by property-based tests (proptest).
Reference Validation
Section titled “Reference Validation”| Reference | What is compared | Target tolerance | Status |
|---|---|---|---|
dive-deco crate |
Buhlmann ZHL-16C coefficients | Coefficient match | Tested on engine changes |
| Subsurface (same algorithm, same gradient factors) | Deco schedules | Within 1 min per stop and 2 min total runtime (D5) | Planned (golden set, P1-22) |
| Subsurface | No-stop limits (NDL) | Within 1 min | Planned (golden set, P1-22) |
| PADI RDP, US Navy, NOAA tables | No-stop limits | Upper bound with no allowance: the engine’s NDL is never longer than the shortest published table value | Planned (golden set, P1-22) |
The golden set and the table bound are not running in CI yet (P1-22). When they run, the tables are a test bound and not a runtime clamp. The tables are not an accuracy target: PADI RDP uses a different algorithm (DSAT). The TypeScript fallback is not validated against Subsurface at all.
Tools that use VPM-B, such as V-Planner, produce different schedules by design and are not a reference for Buhlmann results.
Error Handling
Section titled “Error Handling”The engine returns errors as typed values and does not panic on bad input. DecoError (rust-engine/src/error.rs) covers, among others:
- invalid depth, bottom time, gas mix, O2 fraction, gradient factor, ascent rate, altitude and tank values
- a ppO2 above the limit and a diver-set switch depth deeper than the gas’s MOD at 1.6 bar (refused, never clamped)
- a plan that cannot be computed within solver limits
AlgorithmUnavailable, for example for VPM-B
A refused input produces no plan. The engine never returns a plausible-looking plan in its place.