-
Notifications
You must be signed in to change notification settings - Fork 0
Algo 01 Ascent Simulation
Replay a full dive waypoint array over all 16 ZH-L16 compartments and produce a time series of tissue pressures. This drives both chart rendering (the tissue-loading plot) and downstream ceiling computation.
// js/decoModel.js:1178 (signature)
export function calculateTissueLoading(profile, surfaceInterval = 60, options = {})Returns:
{
timePoints: [...], // minutes, at CALC_INTERVAL (10 s) resolution
depthPoints: [...], // meters, interpolated between waypoints
ambientPressures: [...], // bar
alveolarN2Pressures: [...], // bar, post water-vapor correction
n2Fractions: [...], // reflects gas switches
gasNames: [...],
gasSwitches: [...], // explicit {time, depth, fromGasName, gasName, gasId} events
compartments: { 1: { pressures: [...], halfTime, label, color }, ..., 16: {...} }
}CALC_INTERVAL = 10 seconds (js/decoModel.js:15); every 10 s a new row is pushed. Smaller steps do not meaningfully change the final tissue state — the Schreiner equation is exact for a constant rate, so the step only affects where samples are taken for plotting, not numerical accuracy.
// js/decoModel.js:93-95
export function getInitialTissueN2(n2Fraction = N2_FRACTION) {
return getAlveolarN2Pressure(SURFACE_PRESSURE, n2Fraction);
}For air this evaluates to
// js/decoModel.js:1254-1259
const currentPressures = {};
const initialN2Fraction = getN2FractionAtTime(0);
const initialN2 = getInitialTissueN2(initialN2Fraction);
COMPARTMENTS.forEach(comp => {
currentPressures[comp.id] = initialN2;
});Inside the main loop, DecoJS avoids calling Schreiner with rate = 0 (a numerically bad idea — the Schreiner form divides by
// js/decoModel.js:1399-1424
const ambientRate = (nextAmbient - currentAmbient) / stepDuration;
const avgN2Fraction = (stepN2Fraction + nextN2Fraction) / 2;
const alveolarRate = ambientRate * avgN2Fraction;
COMPARTMENTS.forEach(comp => {
if (Math.abs(alveolarRate) < 0.0001) {
// Constant depth - use Haldane equation
currentPressures[comp.id] = haldaneEquation(
currentPressures[comp.id], currentAlveolar, stepDuration, comp.halfTime
);
} else {
// Depth change - use Schreiner equation
currentPressures[comp.id] = schreinerEquation(
currentPressures[comp.id], currentAlveolar, alveolarRate, stepDuration, comp.halfTime
);
}
});Threshold 0.0001 bar/min is tight enough that stop segments (rate exactly 0) always hit the Haldane branch. For a 3 m ascent at 10 m/min the alveolar rate is ≈ 0.079 bar/min — safely in the Schreiner branch.
The loop does not simply advance by 10 s — it snaps to the next waypoint time if a straight 10 s step would cross one:
// js/decoModel.js:1337-1352
let nextTime = currentTime + intervalMinutes;
const nextWaypointTime = (waypointIndex < profile.length - 1)
? profile[waypointIndex + 1].time
: totalTime + 1;
if (currentTime < nextWaypointTime && nextTime > nextWaypointTime) {
nextTime = nextWaypointTime;
}
if (currentTime < lastWaypoint.time && nextTime > lastWaypoint.time) {
nextTime = lastWaypoint.time;
}This guarantees each segment is driven by exactly one (wp_i, wp_{i+1}) pair — no half-step spans a descent/level boundary, which would blur the rate into something neither Haldane nor Schreiner models exactly.
Each waypoint may carry an optional gasId field. calculateTissueLoading reads this via the getN2FractionAtTime() closure (js/decoModel.js:1188-1208) — the current gas sticks until a waypoint with a new gasId is seen. Tissue pressure is continuous across a switch; only the alveolar target
The surfaceInterval parameter (default 60 min) appends depth = 0 time after the final waypoint, using air (N2_FRACTION = 0.7902) regardless of the final in-water gas:
// js/decoModel.js:1301-1303
const currentN2Fraction = currentTime > lastWaypoint.time
? N2_FRACTION // Surface interval uses air
: getN2FractionAtTime(currentTime);This lets unit tests chain dives, though the current chart layer renders only dives[0] (see CLAUDE.md — repetitive-dive UI is on the roadmap).
- Model-02-Haldane-Equation and Model-03-Schreiner-Equation — the equations being dispatched.
-
Algo-06-Ceiling-Time-Series — consumes the
resultsobject fromcalculateTissueLoadingto overlay ceilings. -
Architecture —
calculateTissueLoadingis the main hot-path consumed by every chart component.