-
Notifications
You must be signed in to change notification settings - Fork 0
Algo 02 NDL Calculation
The No-Decompression Limit is the maximum bottom time at a given depth such that the diver can ascend directly to the surface without triggering a mandatory deco stop. DecoJS finds it by binary search.
// js/decoModel.js:741 (signature)
export function calculateNDL(depth, n2Fraction = N2_FRACTION, gfLow = 1.0)Returns:
{
ndl: 20, // minutes, floor-rounded for display
ndlExact: 20.3, // exact (pre-rounding) value for comparison
controllingCompartment: 7, // TC id whose M-value bites first on direct ascent
descentTime: 2 // minutes to reach depth at DESCENT_SPEED = 20 m/min
}The NDL returned is bottom time — time at maximum depth after descent completes. Total dive duration is descentTime + ndl.
A common confusion: surely NDL should use
// js/decoModel.js:802
const { ceilingDepth } = getDiveCeiling(testPressures, gfLow);Default gfLow = 1.0 (100 %) — raw Bühlmann, no conservatism. Typical planning values like gfLow = 0.30, which tightens NDL substantially.
// js/decoModel.js:754-767 (descent)
const descentTime = depth / DESCENT_SPEED;
const descentRate = (alveolarN2 - getAlveolarN2Pressure(SURFACE_PRESSURE, n2Fraction)) / descentTime;
const afterDescent = {};
COMPARTMENTS.forEach(comp => {
afterDescent[comp.id] = schreinerEquation(
initialN2,
getAlveolarN2Pressure(SURFACE_PRESSURE, n2Fraction),
descentRate,
descentTime,
comp.halfTime
);
});Descent is simulated once via Schreiner at DESCENT_SPEED = 20 m/min (js/decoModel.js:720). That gives tissue state at the start of bottom time.
// js/decoModel.js:791-809
while (maxTime - minTime > 0.1) {
const testTime = (minTime + maxTime) / 2;
const testPressures = {};
COMPARTMENTS.forEach(comp => {
testPressures[comp.id] = haldaneEquation(afterDescent[comp.id], alveolarN2, testTime, comp.halfTime);
});
const { ceilingDepth } = getDiveCeiling(testPressures, gfLow);
if (ceilingDepth > 0) {
maxTime = testTime; // Needs deco, reduce time
} else {
minTime = testTime; // No deco, can go longer
}
}For each candidate
Two early exits bracket the search:
- If ceiling is already > 0 immediately after descent, return
ndl = 0(very deep dive — descent alone triggers deco). - If ceiling at 5 h is still 0, return
ndl = Infinity(very shallow — NDL is effectively unbounded).
30 m on air,
- Descent: 30 m at 20 m/min = 1.5 min. Schreiner loads all 16 tissues.
- After descent, TC7 (
$T_{1/2} = 54$ min) is leading at roughly 0.85 bar. - Binary search converges on
ndlExact ≈ 15.6 min;ndl = 15(floored). - Controlling compartment: TC7 (medium-fast; typical for 20–40 m air dives).
For default raw Bühlmann (
NDL is the pivot in the top-level dive planner:
// js/diveSetup.js:346-367
const { ndl, controllingCompartment } = calculateNDL(maxDepth, bottomGas.n2, gfLowDec);
const descentTime = roundUp(maxDepth / DESCENT_SPEED);
const requiresDeco = bottomTime > ndl;
if (!requiresDeco) {
const waypoints = generateSimpleProfile(maxDepth, bottomTime, safetyStop, options);
waypoints[1].gasId = bottomGas.id;
return {
waypoints, ndl, requiresDeco: false,
decoStops: [], totalDecoTime: 0, controllingCompartment
};
}
// else: proceed to generateDecoSchedule()If bottomTime ≤ ndl, the planner returns a no-stop profile (optionally with a safety stop). Only when deco is unavoidable does the expensive generateDecoSchedule() pipeline run.
- Algo-03-First-Stop-Ramped-GF — what happens when NDL is exceeded.
-
Model-04-M-Values — the ceiling equation underlying
getDiveCeiling. -
Model-05-Gradient-Factors — why the
$GF_{low}$ threshold is the right one for NDL.