Skip to content

VPM -B Gradient Factors#19

Open
VlasovAlexey wants to merge 55 commits into
openexchangerates:masterfrom
archisgore:master
Open

VPM -B Gradient Factors#19
VlasovAlexey wants to merge 55 commits into
openexchangerates:masterfrom
archisgore:master

Conversation

@VlasovAlexey

Copy link
Copy Markdown

Hi,
how to use GF for VPM-B? Any code axamples or explanation?
Thanks.

Archis Gore and others added 25 commits February 15, 2016 10:40
…Deco Planner"

The site was broken because cdn.alloyui.com is a dead host and no working
mirror exists for AlloyUI / YUI3 (Yahoo discontinued YUI in 2014). Rather
than vendor an archived bundle and keep extending a dead UI framework,
rip out the entire YUI3-dependent layer and rebuild:

- New UI in vanilla JavaScript + Tailwind (Play CDN). No build step,
  still a plain static site GitHub Pages can serve as-is.
- The decompression engine (src/scuba-dive.browserified.js) is preserved
  byte-for-byte. That's the actual value of this repo. All logic
  unchanged; only the UI around it is new.
- Renamed everywhere from "Online Web-based Dive Planning" /
  "Scuba Diving Planning Software" to "Deco Planner". CNAME unchanged
  (still deco-planner.archisgore.com).

UX improvements over the original:
- Single-page form (no tabs to hide things behind)
- Sane defaults preloaded so new visitors can hit Calculate immediately
- Per-row remove buttons that don't require modal dialogs
- Result table colour-codes phases (descent / bottom / ascent / stop)
- Pre-render sanity check rejects pathological engine output
  (NaN / Infinity / negative time) instead of silently displaying it
- Mobile-responsive

Dropped (only needed by the old YUI-based JS-editor and REPL tabs):
- src/sandbox-console.js, src/sandbox.css
- src/github-markdown.css
- src/libs/{jquery,underscore,backbone,backbone-localStorage}.min.js
- demo-resources/ (lettering.js, prettify.js, DAT.GUI.min.js, fonts, img)
- src/api/ (was unused YUI API docs scaffolding)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Engine (src/scuba-dive.js): apply the 14 numerical-stability findings from
the code review. Logic preserved byte-for-byte; only guards and explicit
defaults added.

- schreinerEquation, depthChangeInBarsPerMinute: throw on non-positive
  time / halfTime instead of returning Infinity/NaN that silently
  corrupts every downstream tissue calc.
- calculateCeiling: short-circuit to 0 when pTotal <= 0 or b <= 0
  (defends against NaN ceilings from corrupted serialized state).
- getCeiling: cap the "round up to next 3m" loop at 1000 iterations so a
  bad input can never hang the browser.
- calculateDecompression: replace `gfLow || 1.0` (which silently
  promotes a deliberate 0) with explicit undefined/null checks; throw
  on out-of-range GFs; early-return when fromDepth <= 0.
- VPM critical_volume: guard sqrt against negative/NaN discriminants.
- VPM calc_surface_phase_volume_time: guard log() against non-positive
  arguments and zero time-constant differences.
- VPM all four `while (true)` convergence loops: add max-iteration
  counters so a non-converging input throws instead of hanging.
- VPM calculateDecompression shim: stop swallowing exceptions and
  returning the exception object as the "plan"; let it propagate so the
  UI can show a real error.
- VPM gas-fraction sum check: replace `!== 1` exact float compare with
  abs(sum - 1) > 1e-9 so IEEE-754 sums like 0.21+0.44+0.35 don't fail.
- modInMeters: throw on fO2 <= 0 instead of returning Infinity.
- eadInMeters: return 0 for pure-helium gas (narcIndex == 0).

Bundle (src/scuba-dive.browserified.js): rebuilt from updated source via
browserify -r ./src/scuba-dive.js:/scuba-dive.js.

UI (index.html, src/deco-planner.js):
- Metric/imperial units toggle. Engine works in meters internally; display
  is translated at the I/O boundary. Toggling converts existing segment
  depths and the END input in place.
- Standard-gas dropdown (Air, 32%, 50%, O2, 30/30, 21/35, 18/45, 15/55,
  10/70) next to each "+ Custom" button.
- Phase labels: "bottom" only applies to constant-depth segments before
  the first ascent. Any hold after ascent has begun is labeled "deco
  stop". Descents and ascents classified by depth direction.
- Input validation: time > 0, fO2 > 0, fHe >= 0, fO2 + fHe <= 1.
- Calculate button disabled while computation is in flight to prevent
  double-click corrupting in-memory tissue state.
- Replaced @apply CSS-class chain (which Tailwind Play CDN can't reliably
  resolve when one component class @-applies another) with inline utility
  classes on each element.

Repo hygiene: .gitignore now excludes node_modules/, package*.json,
.env, .DS_Store. browserify is dev-only and not committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ilable

If a user removes a bottom or deco gas that an existing segment was using,
segments[i].gasName held a stale reference to the deleted gas. The dropdown
in the row would visually fall back to the first option but the in-memory
state still had the dead name, so Calculate would throw 'Gasname must only
be one of registered gasses'.

Fix: syncSegmentGases() repoints any segment whose gas isn't in the
registered list to the first available gas. Called whenever the bottom or
deco lists mutate (remove or rename), and defensively at the top of
calculate() as a backstop.

Bump cache-bust query to 20260609b.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Whenever the deco plan transitions from one breathing gas to another (e.g.
21/35 → 50% at the 21 m stop), insert a synthetic 'gas switch' row in
the plan table:
  - depth: same as the start of the next segment (switch happens in place)
  - gas column: 'old → new' with a Unicode arrow
  - time: 2 minutes — operational overhead the engine doesn't model but
    the diver actually needs to spool a deco bottle, get the reg in,
    and confirm the mix
  - phase pill: kelp-green, distinct from descent/bottom/ascent/deco-stop

Total and Deco time tallies now include the gas-switch minutes.

Bump cache-bust query to 20260609c.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the units toggle is set to imperial, the displayed deco plan rounds
each From/To depth to the nearest 10-foot increment. Matches how divers
actually mark stops on dive computers and tables. Metric output is
unchanged (still 1-decimal resolution).

This is display-only — the engine continues to compute in meters; only
the rendering of the result table rounds. Bump cache-bust to 20260609d.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o mix

Root cause: syncSegmentGases() reassigned stranded segments to the first
gas in bottomGases.concat(decoGases). If the user removed the only bottom
gas (e.g. 21/35), segments got repointed at 50% — a deco mix — and the
engine then computed a deco plan as if the dive was breathed on 50%.
The math was technically correct; the inputs were silently wrong.

Fix:
- syncSegmentGases now only reassigns to a bottom gas. If no bottom gas
  exists, leave the segment with its stale name so calculate() throws
  the explicit 'Gasname must only be one of registered gasses' error
  instead of quietly producing nonsense.
- syncSegmentGases returns the list of (idx, from, to) changes.
- New showInfo() banner surfaces every reassignment to the user, so
  segment gas changes never happen silently again.
- calculate() refuses to run if no bottom gas is registered, with a
  clear error message.

Bump cache-bust to 20260609e.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renaming a gas in the gas tables (typing in the name field) used to
leave the segment-row dropdowns stuck with the old label, so picking the
renamed gas from a segment was impossible without manually adding/
removing something to force a full re-render.

Split render() into renderGases() and renderSegments(). On gas-name
input changes, call renderSegments() only — this rebuilds the segment
table with the fresh option labels but doesn't touch the gas tables,
so the cursor stays in the name field you were typing in.

Bump cache-bust to 20260609f.
After Calculate succeeds, the full set of inputs (units, algorithm, GFs,
ppO2, max END, bottom + deco gases, all dive segments) is serialized to
the URL hash via history.replaceState. The URL becomes the plan: send it
to a buddy and they see the same numbers.

Loading a URL with #plan=... hydrates the state and auto-runs Calculate
so the recipient lands on the result, not a blank form.

New 'Copy share link' button next to Reset writes the current URL to
the clipboard (with a textarea fallback for older browsers).

Hash, not query string — keeps the server unaware of plan data and
allows essentially unlimited length. State is plain URL-encoded JSON
so it's debuggable/inspectable.

Bump cache-bust to 20260609g.
… gases

The old 'enter startDepth == endDepth to mean a flat segment' was
unintuitive — people kept treating the bottom segment like a travel
segment with two identical numbers. Now each segment has an explicit
kind, and the UI looks different based on kind:

- Descent / Ascent: two depth fields (from -> to) with an arrow.
  Validation enforces direction (descent must go deeper, ascent must
  go shallower).
- Flat / bottom: a single depth field. start == end is set automatically.

The 'Add' button is split into three: + Descent (brine/blue),
+ Flat / bottom (slate), + Ascent (amber). Each row picks up a
matching coloured pill and subtle row tint so the profile reads at
a glance and matches the colours used in the deco-plan output.

Each segment has its own gas dropdown — so divers can plan travel
gases (e.g. air on descent, 21/35 on bottom, 50% on ascent) and the
engine sees the right mix per segment.

Segments now carry a  field. Legacy segments and old shared
URLs that don't have it get a kind inferred from depth direction,
so existing share links keep working.

Bump cache-bust to 20260609h.
New 'SAC rate' input in the Algorithm & conservatism card. Units track
the metric/imperial toggle: cu ft/min in imperial, L/min in metric.
Default 28.3 L/min metric / 1.0 cu ft/min imperial (1 cu ft/min is a
conservative recreational planning default).

Each row in the computed plan now shows the gas volume consumed at
that segment (SAC * pressure-ata at avg depth * minutes). The header
above the plan shows total consumption plus a per-gas breakdown
('21/35 4248 L', '50% 1500 L', 'O2 200 L', ...) so you can size each
tank independently.

Gas-switch rows attribute 50% of the 2-minute switch volume to the
old gas and 50% to the new — matches what actually happens when a
diver swaps regs over a couple of minutes at depth.

URL share-link now also captures the SAC value.
…column

Altitude diving (Buhlmann):
- Depth inputs accept negative values; negative depth means 'above sea
  level'. Banner under the segments table explains the convention.
- The most-negative depth in the profile is treated as the water
  surface. International Standard Atmosphere gives the surface
  atmospheric pressure for that altitude (e.g. ~0.846 bar at 1500 m).
- That pressure is fed into the engine two ways: dive.constants
  .altitudePressure.current() so depth-to-bar conversions are correct,
  and as the absPressure to the Buhlmann plan() constructor so initial
  tissue saturation reflects the altitude. Deco is then computed back
  up to the at-altitude surface pressure, not 1 ATA.
- Gas consumption is also altitude-aware (ambient pressure at depth
  is computed relative to the actual surface pressure).
- Engine sees engine-meters below the water surface, so the depths
  passed in are user_depth - water_surface_depth. Engine output is
  inverted back to the user's coordinate when rendered, so the
  displayed values stay in the same convention the user typed.

Segment cascade:
- After any depth field changes, the next segment's start is auto-
  updated to match, so the profile stays connected (no gaps or
  teleports). The DOM input is patched directly (not via re-render)
  so the cursor stays where the user is typing.

Plan table layout:
- 'From' and 'To' columns merged into a single 'Depth(s)' column.
  Flat rows (bottom, deco stop, gas switch) show one depth value;
  travel rows (descent, ascent) show 'start -> end' with arrow.
- Time and Running columns are now formatted as M:SS instead of
  decimal minutes (matches how dive computers show time).
- Header totals updated to the same M:SS format.

Bump cache-bust to 20260609k.
The previous commit added altitude support but I left an old validation
check ('depths must be >= 0') that immediately rejected the very inputs
the new feature was meant to allow. Removed.

Bump cache-bust to 20260609l.
Per request: pull the user-facing altitude feature for now.
- min='0' restored on all three depth inputs
- 'depths must be >= 0' validation restored in calculate()
- Altitude banner under the segments table removed

The internal altitude plumbing (atmPressureBarAtAltitudeM,
toEngineMeters/engineToDisplay, profileAltitudeMeters, surfaceAtmBar
param on consumeSegment) is left in place but inert. With min=0
enforced everywhere, profileAltitudeMeters always returns 0, so
every altitude code path reduces to the original sea-level behaviour.
Re-enabling is just dropping min=0 + the validation when ready.

Bump cache-bust to 20260609m.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants