Algo 03 First Stop Ramped GF - matej-hron/decojs GitHub Wiki
Algo-03 — First Stop and the GF Ramp Anchor
Before creating an anchor, DecoJS attempts a direct ascent to the surface on
the bottom gas and checks the surfaced tissues at $GF_{high}$. A successful
ascent is an NDL dive and has anchorDepth = 0.
If that ascent fails, the algorithm finds the first decompression level and uses it as the GF-ramp anchor. Standard mode uses Decotengu-style iterative 3 m ceiling steps and therefore holds at the anchor for at least one minute. Adaptive and continuous study modes can assign zero waiting time to the anchor; their first recorded stop can therefore be shallower.
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 level. 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, surfacePressure = SURFACE_PRESSURE
)
Returns { anchorDepth, pAnchor, tissuesAtAnchor }.
The optional surface pressure makes both the simulated ascent and the resulting
anchor altitude-aware while preserving sea level by default.
Adaptive and continuous study method
Iterate the stop grid surface-up. For each candidate depth $d$:
- Simulate the ascent from
currentDepthto $d$ (Schreiner integration with gas switches if applicable). - Compute the dive ceiling at $GF_{low}$ —
getDiveCeiling(simTissues, gfLow). - If
ceilingDepth ≤ d, the diver can arrive at $d$ within $GF_{low}$; this is the anchor level.
// 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;
}
}
Standard staged method
Standard mode starts from the current GF Low ceiling rounded up to the 3 m grid. It simulates ascent to that level, recomputes and rounds the ceiling, and continues only when the new rounded level is shallower. The first level that cannot be skipped by this iterative process becomes the anchor and receives the standard minimum one-minute stay.
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 pre-check
generateDecoSchedule performs the GF High direct-ascent check before either
first-stop search. This prevents a non-zero GF Low anchor from being created
for a dive that can surface without any mandatory stop.
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 > candidateafter 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
interpolateGFgiving GF rising from 0.30 at 15 m to 0.85 at the surface.
Cross-references
- Model-05-Gradient-Factors — what $pAnchor$ does once it's been found.
- Algo-04-Deco-Stop-Loop — consumes
firstStopDepthandtissuesAtAnchor. - Algo-05-Multi-Gas-Switching — how
gasSwitchPointsis constructed before being passed in.