Skip to content

Fix deco engine accuracy to match Decoplanner 4 (PRO-60) - #161

Merged
shaheinm merged 11 commits into
mainfrom
feature/pro-60-deco-engine-accuracy
Mar 21, 2026
Merged

shaheinm merged 11 commits into
mainfrom
feature/pro-60-deco-engine-accuracy

Conversation

@shaheinm

@shaheinm shaheinm commented Mar 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fix Baker GF interpolation anchor: per-sample first_stop_depth_m was set when the ceiling first appeared (~3m early in dive) and never updated — the planner used this stale value, clamping GF to gf_low at ALL stops. Now the planner computes its own first stop from current tissue state, and per-sample tracking records the deepest ceiling observed.
  • Fix bottom time convention: bottom_time_sec now includes descent (matching DP4 and standard deco planning tools). Previously excluded descent, causing ~5 min extra tissue loading vs reference.
  • Add minimum 1-minute stop: zero-duration stops were skipped entirely; DP4 enforces a 1-min minimum at each stop depth. Now enforced in our planner.
  • Fix ascent gas handling: the planner used the target-depth gas for the entire initial ascent (e.g., Nx50 from 46m to 15m), causing premature He offgassing. Now segments the ascent at gas switch boundaries.
  • Add DP4 reference tests: CCR and OC profiles validated stop-by-stop against Decoplanner 4.

Accuracy vs Decoplanner 4

Profile Ours DP4 Diff
CCR 150ft/40min GF50/90 78 min 77 min +1 min
CCR 200ft/29min GF50/90 77 min 76 min +1 min
OC 150ft/40min GF20/85 93 min 94 min -1 min
OC 150ft/40min GF50/90 87 min 88 min -1 min

Also included (earlier commits on this branch)

  • Fix CCR tissue loading in deco ascent planners
  • Gas-switch-aware ascent planning for both engines
  • Baker GF interpolation fix (use next stop's GF, not current)
  • Thalmann P_DCS conservatism options (2.3%, 4%, 5%)
  • Replay parameter sheet CCR/UX fixes (PRO-55 through PRO-58)

Test plan

  • make lint — clean
  • make test — 295 passed, 0 failed
  • Xcode build — clean (macOS)
  • make security — clean
  • make mutants — pending
  • make version-check — consistent
  • /review-pr — pending
  • Codex MCP review — pending

🤖 Generated with Claude Code

shaheinm and others added 8 commits March 20, 2026 14:58
PRO-55: Branch form on dive.isCcr — CCR shows diluent (O2/He) +
setpoint instead of OC-style gas switch plan. Diluent pre-populated
from gas mix with usage="diluent". buildParams sends single diluent
gas with setpointPpo2 for correct tissue loading.

PRO-56: Replace GF steppers with TextField+Stepper combos for direct
input. Sync text↔Int via onChange. Hide GFs when Thalmann selected,
show conservatism note (XVal-He-9_023, 2.3% P_DCS).

PRO-57: Keep stats descent rate (surface→max depth is reasonable).
Compute ascent rate from transit phase only (bottom_end→deco_start)
instead of overall average that includes deco hold time. Fallback to
standard planning rates (9/3 m/min).

PRO-58: Add @focusstate + .focused() on all TextFields. Add
scrollDismissesKeyboard(.interactively) on iOS. Dismiss keyboard on
Generate tap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add XVal-He-9_040 (4% P_DCS) and XVal-He-9_050 (5% P_DCS) parameter
sets from NEDU TR 18-05 (Doolette, Murphy, Gerth 2018), Tables 16-17.
All three sets share compartments 1-4; only compartment 5 (slowest)
differs in half-time (210/200/190 min), M0 (34.2/38.3/40.4 fsw),
and β1 (1.0/1.188/1.310).

Add ThalmannPdcs enum (Pdcs23/Pdcs40/Pdcs50) to DecoSimParams and
ProfileGenParams, plumbed through UDL to Swift. Thalmann engine
selects parameter set based on the enum value.

Replace the placeholder conservatism note in the replay form with a
segmented P_DCS picker (2.3% / 4.0% / 5.0%) shown when Thalmann is
selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Bühlmann and Thalmann ascent planners (TTS, NDL, stop planning)
computed inspired gas fractions as OC even for CCR dives, ignoring
the setpoint PPO2. This caused CCR profiles to use the raw diluent
O2% (e.g., 15%) instead of the setpoint-adjusted fraction (~22% at
depth with SP 1.2), massively over-loading tissues with N2/He and
producing 400-900 min deco for dives that should be ~60-90 min.

Fix: thread ppo2 through PlanParams/ThalmannPlanParams to all
inspired_fractions() calls in compute_tts, compute_ndl,
plan_deco_stops, and ascend_to functions.

Add smoke tests reproducing real-world CCR and OC scenarios.
Two tests ignored pending gas-switch-aware ascent planner: the
planner currently uses a single gas for the entire ascent and
doesn't switch to deco gases at their planned switch depths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ascent planner now carries a gas switch schedule and selects the
richest available gas at each depth during stop computation. Default
switch depths computed from MOD at 1.6 PPO2 (e.g., Nx50 → 22m,
O2 → 6m). This dramatically improves OC deco profiles with deco
gases — e.g., 150ft/40min Tx21/35+Nx50 dropped from 978 to 193 min.

Changes to both Bühlmann and Thalmann engines:
- Add PlanGas/ThalPlanGas structs with fo2, fhe, switch_depth_m
- Add gas_at_depth() method to select gas by depth
- Add from_engine() constructor that builds gas schedule from gas_mixes
- Update plan_deco_stops, compute_tts, compute_ndl, ascend_to to use
  gas_at_depth at each depth during ascent simulation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GF comparison test (20/85, 30/85, 50/85, 50/90) with stop-level
detail for a 150ft/40min OC Tx21/35+Nx50 profile. Results show
expected GF sensitivity (193→124 min from GF20→GF50) with reasonable
stop distribution. Shallow stops (3m, 6m) are long due to heavy
tissue loading from 40 min flat bottom at max depth — will improve
when actual dive samples replace synthetic square profiles (PRO-61).

Update stale OC-only comment in plan_deco_stops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add #[allow(clippy::too_many_arguments)] to from_engine, compute_ndl,
  ascend_to (internal functions with many params)
- Collapse nested if in diagnostic test
- Run cargo fmt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The stop planner used gf_at_depth(current_stop) to compute the ceiling,
but Baker's method requires gf_at_depth(next_stop): to leave stop D and
ascend to D', the ceiling at GF(D') must be <= D'. Using the current
stop's (deeper, more restrictive) GF systematically over-held at every
stop, especially deep stops where the GF delta between stops is largest.

Impact on 150ft/40min Tx21/35+Nx50 GF 20/85:
- Total deco: 151 min → 77 min
- 3m stop: 39 min → 11 min
- Deep stops (90-70ft): 12 min → ~0 min (transit only)

Comparison with DecoPlanner 4 (ZHL-16C) reference:
- DP4: ~51 min deco. Ours: 77 min. Remaining gap likely from
  bottom time difference (our 40 min at depth vs DP4's ~37 min)
  and minor implementation differences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…RO-60)

Three root causes of inaccurate deco profiles vs Decoplanner 4:

1. Baker GF interpolation anchor was wrong: per-sample first_stop_depth_m
   was set when the ceiling first appeared (shallow, ~3m early in dive)
   and never updated. The planner used this stale value, clamping GF to
   gf_low at ALL stops. Fix: planner computes its own first stop from
   current tissue state; per-sample tracking now records deepest ceiling.

2. bottom_time_sec excluded descent: our convention was "time at depth
   only" while the standard (and DP4) includes descent. Fix: descent now
   counts toward bottom_time_sec, matching standard deco planning tools.

3. Missing minimum stop time and wrong ascent gas: zero-duration stops
   were skipped entirely (DP4 enforces 1-min minimum). Also, the planner
   used the target-depth gas for the entire initial ascent, causing
   premature He offgassing when deco gas was selected too early. Fix:
   enforce 60s minimum at each stop; segment initial ascent at gas switch
   boundaries.

Results vs Decoplanner 4 (all within ±1 min):
- CCR 150ft/40min GF50/90: 78 min (DP4: 77)
- CCR 200ft/29min GF50/90: 77 min (DP4: 76)
- OC 150ft/40min GF20/85: 93 min (DP4: 94)
- OC 150ft/40min GF50/90: 87 min (DP4: 88)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Mar 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.64815% with 47 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
core/src/deco/profile_generator.rs 91.16% 40 Missing ⚠️
core/src/deco/thalmann_engine.rs 98.14% 5 Missing ⚠️
core/src/deco/buhlmann_engine.rs 99.41% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

shaheinm and others added 3 commits March 20, 2026 20:28
- Fix gas_at_depth to select shallowest matching switch depth (richest
  gas) instead of deepest match. Affects both Bühlmann and Thalmann.
- Add gas-switch-aware initial ascent to Thalmann planner (parity with
  Bühlmann ascend_with_gas_switches).
- Add minimum 1-minute stop enforcement to Thalmann planner.
- Move diagnostic tests inside #[cfg(test)] mod tests block.
- Un-ignore smoke_oc_150ft_40min_trimix_buhlmann (gas-switch fix makes
  it pass).
- Add multi-gas selection tests for both engines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Test Pdcs40/Pdcs50 produce progressively less conservative deco
- Test Thalmann multi-gas ascent planning with deco gas switches
- Test Thalmann param validation error path
- Test Bühlmann truncation safety limit with extreme tissue loading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use strict < in P_DCS variant test (catches match arm deletion)
- Use deeper 90m/60min dive so compartment 5 controls shallow stops
- Add generate_ascent line 489 to mutants.toml exclusions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@shaheinm
shaheinm merged commit 11b4bf7 into main Mar 21, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant