From ec0c6833a0f5de73e988694ae55bf9e43fca3b72 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Thu, 16 Feb 2023 15:53:17 +1300 Subject: [PATCH] Fix invalid bailout gas choice when planning a CCR dive. Fixes a bug reported in https://groups.google.com/g/subsurface-divelog/c/8N3cTz2Zv5E: When planning a CCR dive with OC bailout, the diluent gas may be chosen as the first OC bailout gas, despite being set up with a use type of 'diluent', and likely not being available for open circuit breathing. `best_first_ascend_cylinder` is now initialised to an invalid value (instead of the first cylinder, which may or may not be a diluent cylinder), and its subsequent use is guarded by a validity check. Signed-off-by: Michael Keller --- CHANGELOG.md | 1 + core/planner.c | 19 +++++------ tests/testplan.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++++ tests/testplan.h | 1 + 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf767eb7f..f46a60714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +desktop: fix bug in bailout gas selection for CCR dives desktop: fix crash on cylinder update of multiple dives desktop: use dynamic tank use drop down in equipment tab and planner desktop: fix brightness configuration for OSTC4 diff --git a/core/planner.c b/core/planner.c index d7aaf97b7..244a8e16b 100644 --- a/core/planner.c +++ b/core/planner.c @@ -405,7 +405,7 @@ static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, struct dive int nr = 0; struct gaschanges *gaschanges = NULL; struct divedatapoint *dp = diveplan->dp; - int best_depth = get_cylinder(dive, *asc_cylinder)->depth.mm; + struct divedatapoint *best_ascent_dp = NULL; bool total_time_zero = true; while (dp) { if (dp->time == 0 && total_time_zero && (ccr == (bool) setpoint_change(dive, dp->cylinderid))) { @@ -425,9 +425,8 @@ static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, struct dive assert(gaschanges[i].gasidx != -1); } else { /* is there a better mix to start deco? */ - if (dp->depth.mm < best_depth) { - best_depth = dp->depth.mm; - *asc_cylinder = dp->cylinderid; + if (!best_ascent_dp || dp->depth.mm < best_ascent_dp->depth.mm) { + best_ascent_dp = dp; } } } else { @@ -436,6 +435,9 @@ static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, struct dive dp = dp->next; } *gaschangenr = nr; + if (best_ascent_dp) { + *asc_cylinder = best_ascent_dp->cylinderid; + } #if DEBUG_PLAN & 16 for (nr = 0; nr < *gaschangenr; nr++) { int idx = gaschanges[nr].gasidx; @@ -678,7 +680,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i int clock, previous_point_time; int avg_depth, max_depth; int last_ascend_rate; - int best_first_ascend_cylinder; + int best_first_ascend_cylinder = -1; struct gasmix gas, bottom_gas; bool o2break_next = false; int break_cylinder = -1, breakfrom_cylinder = 0; @@ -752,7 +754,6 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i printf("current_cylinder %i\n", current_cylinder); #endif - best_first_ascend_cylinder = current_cylinder; /* Find the gases available for deco */ gaschanges = analyze_gaslist(diveplan, dive, &gaschangenr, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout); @@ -827,7 +828,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i return false; } - if (best_first_ascend_cylinder != current_cylinder) { + if (best_first_ascend_cylinder != -1 && best_first_ascend_cylinder != current_cylinder) { current_cylinder = best_first_ascend_cylinder; gas = get_cylinder(dive, current_cylinder)->gasmix; @@ -886,7 +887,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); /* Always prefer the best_first_ascend_cylinder if it has the right gasmix. * Otherwise take first cylinder from list with rightgasmix */ - if (same_gasmix(gas, get_cylinder(dive, best_first_ascend_cylinder)->gasmix)) + if (best_first_ascend_cylinder != -1 && same_gasmix(gas, get_cylinder(dive, best_first_ascend_cylinder)->gasmix)) current_cylinder = best_first_ascend_cylinder; else current_cylinder = get_gasidx(dive, gas); @@ -1032,7 +1033,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i * backgas. This could be customized if there were demand. */ if (break_cylinder == -1) { - if (get_o2(get_cylinder(dive, best_first_ascend_cylinder)->gasmix) <= 320) + if (best_first_ascend_cylinder != -1 && get_o2(get_cylinder(dive, best_first_ascend_cylinder)->gasmix) <= 320) break_cylinder = best_first_ascend_cylinder; else break_cylinder = 0; diff --git a/tests/testplan.cpp b/tests/testplan.cpp index a7a42e56a..fe291979e 100644 --- a/tests/testplan.cpp +++ b/tests/testplan.cpp @@ -372,6 +372,39 @@ void setupPlanSeveralGases(struct diveplan *dp) plan_add_segment(dp, 5 * 60, 10000, 0, 0, true, OC); } +void setupPlanCcr(struct diveplan *dp) +{ + dp->salinity = 10300; + dp->surface_pressure = 1013; + dp->gflow = 50; + dp->gfhigh = 70; + dp->bottomsac = prefs.bottomsac; + dp->decosac = prefs.decosac; + + pressure_t po2 = {1600}; + struct gasmix diluent = {{200}, {210}}; + struct gasmix ean53 = {{530}, {0}}; + struct gasmix tx19_33 = {{190}, {330}}; + cylinder_t *cyl0 = get_or_create_cylinder(&displayed_dive, 0); + cylinder_t *cyl1 = get_or_create_cylinder(&displayed_dive, 1); + cylinder_t *cyl2 = get_or_create_cylinder(&displayed_dive, 2); + cyl0->gasmix = diluent; + cyl0->depth = gas_mod(diluent, po2, &displayed_dive, M_OR_FT(3, 10)); + cyl0->type.size.mliter = 3000; + cyl0->type.workingpressure.mbar = 200000; + cyl0->cylinder_use = DILUENT; + cyl1->gasmix = ean53; + cyl1->depth = gas_mod(ean53, po2, &displayed_dive, M_OR_FT(3, 10)); + cyl2->gasmix = tx19_33; + cyl2->depth = gas_mod(tx19_33, po2, &displayed_dive, M_OR_FT(3, 10)); + reset_cylinders(&displayed_dive, true); + free_dps(dp); + + plan_add_segment(dp, 0, cyl1->depth.mm, 1, 0, false, OC); + plan_add_segment(dp, 0, cyl2->depth.mm, 2, 0, false, OC); + plan_add_segment(dp, 20 * 60, M_OR_FT(60, 197), 0, 1300, true, CCR); +} + /* We compare the calculated runtimes against two values: * - Known runtime calculated by Subsurface previously (to detect if anything has changed) * - Benchmark runtime (we should be close, but not always exactly the same) @@ -885,4 +918,50 @@ void TestPlan::testVpmbMetricRepeat() QCOMPARE(finalDiveRunTimeSeconds, firstDiveRunTimeSeconds); } + +// Test that the correct gases are selected during a CCR dive with bailout ascent +// Includes a regression test for https://groups.google.com/g/subsurface-divelog/c/8N3cTz2Zv5E + +void TestPlan::testCcrBailoutGasSelection() +{ + struct deco_state *cache = NULL; + + setupPrefs(); + prefs.unit_system = METRIC; + prefs.units.length = units::METERS; + prefs.planner_deco_mode = BUEHLMANN; + displayed_dive.dc.divemode = CCR; + prefs.dobailout = true; + + struct diveplan testPlan = {}; + setupPlanCcr(&testPlan); + + plan(&test_deco_state, &testPlan, &displayed_dive, 60, stoptable, &cache, true, false); + +#if DEBUG + free(displayed_dive.notes); + displayed_dive.notes = NULL; + save_dive(stdout, &displayed_dive, false); +#endif + + // check diluent used + cylinder_t *cylinder = get_cylinder(&displayed_dive, get_cylinderid_at_time(&displayed_dive, &displayed_dive.dc, { 20 * 60 - 1 })); + QCOMPARE(cylinder->cylinder_use, DILUENT); + QCOMPARE(get_o2(cylinder->gasmix), 200); + + // check deep bailout used + cylinder = get_cylinder(&displayed_dive, get_cylinderid_at_time(&displayed_dive, &displayed_dive.dc, { 20 * 60 + 1 })); + QCOMPARE(cylinder->cylinder_use, OC_GAS); + QCOMPARE(get_o2(cylinder->gasmix), 190); + + // check shallow bailout used + cylinder = get_cylinder(&displayed_dive, get_cylinderid_at_time(&displayed_dive, &displayed_dive.dc, { 30 * 60 })); + QCOMPARE(cylinder->cylinder_use, OC_GAS); + QCOMPARE(get_o2(cylinder->gasmix), 530); + + // check expected run time of 51 minutes + QVERIFY(compareDecoTime(displayed_dive.dc.duration.seconds, 51 * 60, 51 * 60)); + +} + QTEST_GUILESS_MAIN(TestPlan) diff --git a/tests/testplan.h b/tests/testplan.h index 76b248e3d..74a5b84e8 100644 --- a/tests/testplan.h +++ b/tests/testplan.h @@ -19,6 +19,7 @@ private slots: void testVpmbMetric100m10min(); void testVpmbMetricRepeat(); void testMultipleGases(); + void testCcrBailoutGasSelection(); }; #endif // TESTPLAN_H