Skip to content

CCR deco/TTS computed as open-circuit first-tank gas (understates decompression) #455

Description

@readme42

Summary

For CCR (rebreather) dives, decompression and TTS are computed as if the diver breathed the first tank's gas open-circuit, ignoring the constant-ppO2 loop. This understates nitrogen loading, so the app reports a shorter TTS / shallower obligation than reality.

Example: dive 003 (test/dives/003_ccr_with_setpoint_switch_and_calculated_po2.ssrf.xml), GF 45/75, at minute 40 (44.1 m, loop ppO2 1.26 bar):

  • App shows TTS 17 min
  • Subsurface shows TTS 24 min

Root cause

A rebreather holds ppO2 at a constant setpoint, so the inspired inert-gas partial pressure is ambient − ppO2 (split by the diluent's He:N2 ratio). The app instead uses a fixed open-circuit fraction from the first tank:

  • lib/features/dive_log/presentation/providers/profile_analysis_provider.dart (~L648): o2Fraction is taken from dive.tanks.first.gasMix — for dive 003 that is EAN40 → fN2 = 0.60.
  • lib/features/dive_log/data/services/profile_analysis_service.dart (~L573-585): gas segments are only built for DiveMode.oc; for CCR/SCR it calls processProfile(fN2, fHe) with that fixed fraction.
  • The resolved rebreatherPpO2Curve is passed into analyze(...) but used only for CNS/OTU, never for inert-gas loading. BuhlmannAlgorithm has no CCR mode.

Modeling the loop as EAN40 (60% N2) loads far less nitrogen than the real air-diluent loop at a constant ~1.3 bar setpoint, so deco comes out too short.

Evidence (probe)

Parsing dive 003 and running the deco algorithm directly at minute 40:

Model fN2 TTS @ min 40
App's current CCR path (EAN40, first tank) 0.60 16 min (matches displayed 17)
Air diluent as fixed OC fraction 0.79 60 min
CCR constant-ppO2 (correct physics) ambient − ppO2 ~24 min (Subsurface)

The app's 16 min reproduces its displayed 17, confirming it models the CCR dive as open-circuit EAN40.

Proposed fix

Make the deco path CCR-aware:

  1. Drive inert-gas loading from inspired_inert_pp = ambient − loopPpO2, split by the diluent cylinder's He:N2 ratio (not the first tank).
  2. Hold ppO2 at the setpoint through the TTS ascent simulation as well (inert fraction changes with depth as ppO2 stays constant), mirroring Subsurface.
  3. ppO2 source: measured O2 cells / dc-supplied ppO2, falling back to the setpoint — consistent with the existing CNS/OTU ppO2 source rule (resolveRebreatherPpO2). The curve is already resolved and passed into analyze(...).

Scope: CCR first, its own PR. SCR is a follow-up (depth-varying loop FO2 rather than a fixed setpoint).

Notes

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions