/* SPDX-License-Identifier: MIT-0 */ #include #include #include #include "schedule.h" #define STOPLEN_ROUGH 10 #define STOPLEN_FINE 1 int SWITCH_INTERMEDIATE = SWITCH_INTERMEDIATE_DEFAULT; int LAST_STOP_AT_SIX = LAST_STOP_AT_SIX_DEFAULT; static void emit_waypoint(const decostate_t *ds, segtype_t type, const waypoint_callback_t *wp_cb) { if (wp_cb && wp_cb->fn) wp_cb->fn(ds, type, wp_cb->arg); } const gas_t *best_gas(double depth, const gas_t *gasses, int nof_gasses) { const gas_t *best = NULL; double mod_best = -1; for (int i = 0; i < nof_gasses; i++) { double mod = gas_mod(&gasses[i]); if (depth - mod < 1E-2 && (mod_best == -1 || mod < mod_best)) { best = &gasses[i]; mod_best = mod; } } return best; } int direct_ascent(const decostate_t *ds, double ascrate) { decostate_t ds_ = *ds; assert(ds_.firststop == -1); add_segment_ascdec(&ds_, ds_.depth, abs_depth(0), gauge_depth(ds_.depth) / ascrate, ds_.gas); return gauge_depth(ceiling(&ds_, ds_.gfhi)) <= 0; } void simulate_dive(decostate_t *ds, const waypoint_t *waypoints, int nof_waypoints, const waypoint_callback_t *wp_cb) { for (int i = 0; i < nof_waypoints; i++) { double d = waypoints[i].depth; double t = waypoints[i].time; const gas_t *g = waypoints[i].gas; if (d != ds->depth) add_segment_ascdec(ds, ds->depth, d, t, g); else add_segment_const(ds, d, t, g); emit_waypoint(ds, SEG_DIVE, wp_cb); } } double calc_ndl(decostate_t *ds, double ascrate) { double ndl = 0; /* rough steps */ decostate_t ds_ = *ds; while (ndl < 360) { add_segment_const(&ds_, ds_.depth, STOPLEN_ROUGH, ds_.gas); if (!direct_ascent(&ds_, ascrate)) break; ndl = ds_.runtime - ds->runtime; } /* fine steps */ ds_ = *ds; if (ndl) add_segment_const(&ds_, ds_.depth, ndl, ds_.gas); while (ndl < 360) { add_segment_const(&ds_, ds_.depth, STOPLEN_FINE, ds_.gas); if (!direct_ascent(&ds_, ascrate)) break; ndl = ds_.runtime - ds->runtime; } return ndl; } static void deco_stop(decostate_t *ds, double next_stop, double current_gf) { double stoplen = 0; /* rough steps */ decostate_t ds_ = *ds; for (;;) { add_segment_const(&ds_, ds_.depth, STOPLEN_ROUGH, ds_.gas); if (ceiling(&ds_, current_gf) <= next_stop) break; stoplen = ds_.runtime - ds->runtime; } if (stoplen) add_segment_const(ds, ds->depth, stoplen, ds->gas); /* fine steps */ while (ceiling(ds, current_gf) > next_stop) add_segment_const(ds, ds->depth, STOPLEN_FINE, ds->gas); } static int surfaced(decostate_t *ds) { return ds->depth - SURFACE_PRESSURE < 1E-2; } decoinfo_t calc_deco(decostate_t *ds, const gas_t *deco_gasses, int nof_gasses, const waypoint_callback_t *wp_cb) { const double runtime_start = ds->runtime; const double asc_per_min = msw_to_bar(9); double next_stop; double current_gf; const gas_t *best; bool deco_started = false; /* check if direct ascent is possible */ if (direct_ascent(ds, asc_per_min)) return (decoinfo_t){.tts = 0, .ndl = calc_ndl(ds, asc_per_min)}; /* prepare for the first stop */ next_stop = abs_depth(ds->ceil_multiple * (ceil(gauge_depth(ds->depth) / ds->ceil_multiple))); while (next_stop >= ds->depth) next_stop -= ds->ceil_multiple; current_gf = get_gf(ds, next_stop); /* alternate between ascending and stopping until we surface */ for (;;) { /* ascend */ while (ceiling(ds, current_gf) < next_stop && !surfaced(ds)) { /* switch to better gas if available */ best = best_gas(ds->depth, deco_gasses, nof_gasses); if (SWITCH_INTERMEDIATE && best && best != ds->gas) { /* emit waypoint because we're about to switch gas */ emit_waypoint(ds, deco_started ? SEG_TRAVEL : SEG_ASCENT, wp_cb); /* switch gas */ add_segment_const(ds, ds->depth, 1, best); emit_waypoint(ds, SEG_GAS_SWITCH, wp_cb); continue; } /* ascend to next stop */ add_segment_ascdec(ds, ds->depth, next_stop, fabs(ds->depth - next_stop) / asc_per_min, ds->gas); /* make next stop shallower */ next_stop -= ds->ceil_multiple; if (LAST_STOP_AT_SIX && next_stop < abs_depth(msw_to_bar(6))) next_stop = SURFACE_PRESSURE; /* recalculate gf */ current_gf = get_gf(ds, next_stop); } if (ds->firststop == -1) { ds->firststop = ds->depth; /* * if the first stop wasn't know yet during the previous call to * get_gf, the result was inaccurate and needs to be recalculated */ current_gf = get_gf(ds, next_stop); /* if the new gf also allows us to ascend further, continue ascending */ if (ceiling(ds, current_gf) < next_stop) continue; } /* terminate if we surfaced */ if (surfaced(ds)) { emit_waypoint(ds, SEG_SURFACE, wp_cb); return (decoinfo_t){.ndl = 0, .tts = ds->runtime - runtime_start}; } emit_waypoint(ds, deco_started ? SEG_TRAVEL : SEG_ASCENT, wp_cb); deco_started = true; /* switch to better gas if available */ best = best_gas(ds->depth, deco_gasses, nof_gasses); if (best) ds->gas = best; /* stop until ceiling rises above next stop */ deco_stop(ds, next_stop, current_gf); emit_waypoint(ds, SEG_DECO_STOP, wp_cb); } }