VPM -B Gradient Factors#19
Open
VlasovAlexey wants to merge 55 commits into
Open
Conversation
Primarily to fix this: #2 The fix involves using a different table for displaying deco information, rather than reusing the same segments table used for adding segments.
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi,
how to use GF for VPM-B? Any code axamples or explanation?
Thanks.