Skip to content

Algo 03 First Stop Ramped GF

matejhron edited this page May 10, 2026 · 4 revisions

Algo-03 — First Stop and the GF Ramp Anchor

The first decompression stop is where two things coincide: it is the deepest mandatory stop on the 3 m grid, and it is the anchor of the GF ramp. Same depth, two roles. The deco loop then ascends from there toward the surface.

Convention

DecoJS implements the standard Baker GF convention:

The first stop is the shallowest stop-grid depth at which the dive ceiling at $GF_{low}$ — the maximum ceiling across all 16 compartments — is satisfied after a simulated ascent from current depth.

pAnchor is the ambient pressure at that depth. Below it the active GF is clamped at $GF_{low}$; above it the GF ramps linearly to $GF_{high}$ at the surface (see Model-05-Gradient-Factors).

findFirstStopAtGFLow

// js/decoModel.js (signature)
export function findFirstStopAtGFLow(
    tissuePressures, currentDepth, n2Fraction, gfLow,
    stopIncrement = STOP_INCREMENT, ascentRate = ASCENT_SPEED, gasSwitchPoints = null
)

Returns { anchorDepth, pAnchor, tissuesAtAnchor }.

Method

Iterate the stop grid surface-up. For each candidate depth $d$:

  1. Simulate the ascent from currentDepth to $d$ (Schreiner integration with gas switches if applicable).
  2. Compute the dive ceiling at $GF_{low}$getDiveCeiling(simTissues, gfLow).
  3. If ceilingDepth ≤ d, the diver can arrive at $d$ within $GF_{low}$; this is the first stop.
// js/decoModel.js findFirstStopAtGFLow
for (let candidate = 0; candidate <= currentDepth + 1e-9; candidate += stopIncrement) {
    let simTissues;
    if (safeGases) {
        simTissues = _simulateAscentWithGasSwitches(
            tissuePressures, currentDepth, candidate, n2Fraction, safeGases
        );
    } else {
        const ascentTime = (currentDepth - candidate) / ascentRate;
        simTissues = ascentTime > 0
            ? simulateDepthChange({ ...tissuePressures }, currentDepth, candidate, ascentTime, n2Fraction)
            : { ...tissuePressures };
    }
    const { ceilingDepth } = getDiveCeiling(simTissues, gfLow);
    if (ceilingDepth <= candidate + 1e-9) {
        anchorDepth = candidate;
        tissuesAtAnchor = simTissues;
        break;
    }
}

stopIncrement is 3 m by default (matching dive-computer practice) and 0.1 m in continuous-deco mode (educational visualization).

Why "dive ceiling" and not "leading-compartment ceiling"

getDiveCeiling takes the maximum across all 16 compartments. The compartment with the highest instantaneous GF is not always the same as the compartment with the deepest GF_low ceiling — the two can disagree, especially after a gas switch shifts which compartment is loading vs off-gassing. The convention takes the deepest of all per-compartment ceilings, so the diver isn't sent shallower than safe.

Gas switches

The simulated ascent within the search uses _simulateAscentWithGasSwitches when deco gases are provided. Switching to a richer mix at its MOD changes $f_{N_2}$ and therefore the off-gassing rate — without that, a pAnchor computed against bottom-gas alveolar pressures would be too deep.

// js/decoModel.js _simulateAscentWithGasSwitches
function _simulateAscentWithGasSwitches(tissuePressures, fromDepth, toDepth, startN2, gasSwitchPoints) {
    let tissues = { ...tissuePressures };
    let currentDepth = fromDepth;
    let currentN2 = startN2;

    const relevantSwitches = gasSwitchPoints
        .filter(sp => sp.switchDepth < currentDepth && sp.switchDepth >= toDepth)
        .sort((a, b) => b.switchDepth - a.switchDepth);

    for (const sp of relevantSwitches) {
        const segmentTime = (currentDepth - sp.switchDepth) / ASCENT_SPEED;
        if (segmentTime > 0) {
            tissues = simulateDepthChange(tissues, currentDepth, sp.switchDepth, segmentTime, currentN2);
        }
        currentDepth = sp.switchDepth;
        currentN2 = sp.n2;
    }

    if (currentDepth > toDepth) {
        const segmentTime = (currentDepth - toDepth) / ASCENT_SPEED;
        tissues = simulateDepthChange(tissues, currentDepth, toDepth, segmentTime, currentN2);
    }

    return tissues;
}

NDL edge case

If candidate = 0 (surface) already satisfies the ceiling check, the diver is within NDL. findFirstStopAtGFLow returns anchorDepth = 0 and pAnchor = SURFACE_PRESSURE. The deco loop then takes the no-deco branch (js/decoModel.js no-deco path).

Worked example

40 m on air, 25 min bottom time, $GF = 30/85$.

  • After descent + 25 min at 40 m, the deepest GF_low ceiling lands somewhere around 12-13 m.
  • Iterate the 3 m grid: candidates at 0, 3, 6, 9, 12 all fail (ceilingDepth > candidate after simulated ascent).
  • Candidate 15 passes: dive ceiling at $GF_{low}$ on the post-ascent tissues is $\le 15$ m.
  • First stop = 15 m. $pAnchor = 1.01325 + 1.5 = 2.51$ bar.
  • The deco loop then runs from 15 m, with interpolateGF giving GF rising from 0.30 at 15 m to 0.85 at the surface.

Cross-references

Clone this wiki locally