|
|
||
|---|---|---|
| .forgejo/workflows | ||
| docs | ||
| public | ||
| scripts | ||
| src | ||
| .gitignore | ||
| index.html | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
dive.pt3r.dev
An educational browser lab for certified divers exploring depth, gas use, and nitrogen/helium loading.
Develop
Install Node.js 22 or later and pnpm, then run:
pnpm install
pnpm dev
pnpm test
pnpm build
Built with TypeScript, React, and Vite. Static production output is in dist/.
Current milestone
Manual target-depth control, rate-limited travel, accelerated simulation, diver visualization, all 16 nitrogen compartments, recorded depth profile, ambient pressure, ppO₂, and adjustable constant-GF ceiling bounds. Pause, reset, and advance one simulated minute are available. Space toggles playback except while editing inputs, selects, or text. Imperial units (ft/psi) are the default; metric units (m/bar) are selectable and the preference is stored locally. Unit changes preserve the underlying metric/bar model state. Tissue calculations use simulated elapsed time independently of rendering.
Model assumptions
The default ZHL-16C uses nitrogen and helium half-times and coefficients, sea-level surface pressure 1.01325 bar, 10 m per bar, open-circuit inspired inert pressure (ambient pressure - 0.0627 bar) × inert fraction, configurable O₂/N₂/He mixtures, and compartments initially equilibrated on surface air. Constant-depth uptake uses exponential kinetics; linear travel uses the Schreiner equation. Open-circuit ppO₂ uses ambient absolute pressure; CCR uses the capped setpoint described below.
Coefficient reference: https://github.com/subsurface/subsurface/blob/master/core/deco.cpp (N₂ and He arrays). Equations are independently implemented; no source code from that project is copied.
For Bühlmann models, the NDL display projects additional time at the current depth followed by a direct ascent on the active gas with planned ascent switches at the configured ascent rate. It checks the GF-high ceiling throughout ascent in steps of at most ten simulated seconds, with a 240-minute hold horizon and binary search for the time limit. Warnings appear at ten and five minutes; zero indicates the projected ascent exceeds the selected GF-high limit. GF low is not used in this no-stop projection.
For Bühlmann models, the two ceiling values apply GF high and GF low separately. They are not an interpolated ascent ceiling or a decompression schedule. The tissue equilibrium percentage is not a measure of safety. GF defaults of 45/85 are illustrative and editable, not a recommendation.
Tests cover half-time uptake, surface equilibrium, linear-travel comparison to numerical integration, timestep consistency, off-gassing, and invalid inputs. These are mathematical checks, not operational validation against a certified dive computer.
Decompression projection
For Bühlmann models, a conditional decompression panel projects stops on the active gas with planned switches using current loading, ascent rate, and GF low/high. GF low is anchored at the rounded initial low-GF ceiling; GF increases linearly with ambient pressure toward the surface. Stop spacing is 10 ft in imperial mode and 3 m in metric mode; holds use whole minutes. Projection is limited to 24 hours and reports already-violated ceilings rather than suggesting recovery procedures. These schedules are educational estimates and have not been independently validated for operational use. Gas consumption/cylinders, profile planning, repetitive dives, sharing, and helium are implemented. Offline support and public deployment remain tracked at https://git.guresky.com/pedro/DiveSim/issues.
Use
Educational simulation only. Do not use for real dive planning or execution.
Gases and switches (issues 1 and 2)
Air, EAN32, EAN36, EAN50, oxygen, Trimix 21/35, Trimix 18/45, and Heliox 21/79 presets are available. Add custom oxygen/helium percentages; nitrogen is the balance. Manual switches take effect immediately without resetting tissue loading. Planned ascent switches split travel at the exact depth, including accelerated playback and simulated safety stops. Descent does not trigger them. Switch events are recorded on the profile and in history. NDL and decompression projections include the configured ascent switches and show gases used at stops. Gas selection is for educational exploration; gas suitability and oxygen exposure are not operationally validated.
Advanced Mode
The main page retains compact breathing-gas selection. Advanced Mode opens a native dialog with gas inventory, O₂/He/Ar composition fields, depth-based ascent switches, and model-specific decompression settings. Automatic ascent switches default off; the toggle affects live travel, safety-stop travel, NDL, and decompression projections without deleting configured switches. Closing the dialog preserves settings.
Helium uses separate exchange half-times. Bühlmann B/C ceilings use helium coefficients. Each compartment stores independent N₂ and He pressures; ceilings use pressure-weighted coefficients and total inert gas pressure. The tissue view can display either gas. Nitrogen-only inputs retain their original results. Argon is represented in the gas composition but nonzero argon is explicitly rejected: no argon model is implemented, and it is not treated as N₂ or He. Helium coefficients and mixed-gas weighting were checked against https://github.com/subsurface/subsurface/blob/master/core/deco.cpp.
Consumption and cylinders
Surface breathing rate (RMV) is litres/minute (or cubic feet/minute) at the model surface pressure, 1.01325 bar. RMV can be adjusted during an open-circuit dive; changes affect future consumption without rewriting prior gas usage. Cylinder demand equals RMV × absolute ambient/surface pressure. Integration uses the exact mean pressure for each linear depth segment and splits at gas switches; wall-clock playback speed does not change usage for the same simulated profile.
Cylinder setup starts with one Air cylinder in a collapsed expandable menu below Breathing gases. Add Cylinder creates another Air cylinder and its gas selector uses the Breathing gases inventory. Multiple cylinders may contain the same gas. Each cylinder has independent usage and an X removal button; unused cylinders can be added, edited, or removed during a dive; used cylinders retain locked gas/physical settings and removal until reset, while their labels remain editable. Select the active cylinder in Gas supply; changing cylinders also selects its gas. Gas changes use the selected cylinder if its mixture matches, otherwise the first matching cylinder. Empty cylinders do not automatically roll over to another cylinder. Gases without a cylinder show an explicit warning. The active cylinder shows GTR (Gas Time Remaining), estimated time to empty at the current depth and RMV; estimates assume those remain constant. Each added cylinder defaults to 11.1 L internal volume at 3000 psi with a 500 psi reserve. Capacity is internal water volume (0.392 ft³), not a rated 80 ft³ gas capacity. Inventory assumes an ideal gas at constant temperature, with no compressibility correction, leaks or regulator losses. Remaining surface gas is pressure × internal volume / surface pressure. Reserve is a configurable threshold, not a calculated ascent or emergency reserve.
Reset refills inventory and unlocks RMV/cylinder setup. Reserve and empty warnings remain visible; after depletion, remaining gas clamps to zero and unmet demand is reported. The tissue simulation deliberately continues for educational exploration. Decompression projections do not assess gas availability.
Profile planner
Planner Mode opens a separate dialog. Add, edit, reorder, and remove segments with target depth, hold time, ascent/descent rates, and a cylinder. The chosen gas applies before that segment's travel. Explicit segment gases take precedence over live automatic ascent switches. Profiles validate depth (0–300 m), holds (0–1440 min), positive travel rates up to 100 m/min, gas composition/availability, and total duration (maximum 24 hours). Editing a plan resets its replay position. Authored profiles gain ascent stages only when explicitly requested.
Planner Mode is a separate menu with plan-specific cylinders and RMV, independent of the live dive inventory. Assign a cylinder to each segment (including duplicate mixtures). Click or drag on the graph, or use the keyboard-accessible Profile timeline slider, to inspect depth, ppO₂, NDL, ceiling, tissues, and each cylinder inventory at the selected time. Replay animates these planner stats at the selected playback speed; it does not replace the manual dive. Pause/Resume replay and Show final result control the planner timeline. Closing Planner Mode lets replay continue; starting manual playback or resetting the live dive stops replay. The live NDL slowdown also limits the shared playback speed.
Auto ascent appends required decompression stages on the final segment cylinder, or a three-minute safety stop at 5 m (metric) / 15 ft (imperial), followed by surfacing. End the authored profile at depth before adding it. The live safety-stop button now runs a timed ascent and three-minute hold through normal playback, then ascends to the surface and pauses. The three-minute hold starts on arrival, so changing the ascent rate does not shorten the hold. Changing the target or changing units to a different stop depth cancels the sequence.
Planned decompression and comparisons
For Bühlmann models, Planner Mode has independent GF low/high controls and projected ascent cylinder switches. A projection starts at the end of the authored profile and uses the final segment's ascent rate. It shows stop depths, whole-minute holds, gases, gas switches, total ascent time, and runtime at the surface. Profile, gas, cylinder-switch, unit-spacing, and GF edits recompute the current projection. Save comparison freezes a copy of the current profile description, settings, and schedule beside the changing current plan. Append projected ascent creates editable segment stages and clears the projected switch list; it does not modify the live dive.
For Bühlmann models, GF low anchors at the initial low-GF ceiling rounded up to a 3 m / 10 ft stop. GF interpolates linearly with ambient pressure (equivalently depth here) toward GF high at the surface. Stops are whole-minute holds with checks along travel at intervals no longer than ten seconds. Planned gas-switch depths become additional travel boundaries. Profiles already shallower than the GF-high ceiling, invalid switch/cylinder settings, and projections exceeding 24 hours produce explicit errors. Cylinder availability does not limit calculated stop times; resulting stages show reserve/depletion through timeline inventory.
Verification includes separate one-second midpoint numerical reference integration for air and trimix cases, checking every ascent point against the GF-adjusted tolerated pressure without calling the production tissue update or ceiling functions. Coefficient arrays use the previously checked Subsurface ZHL-16C data. Integration reference cases use a 40 m, 32-minute runtime (2-minute descent and 30-minute hold), ascent 9 m/min, GF 30/85, and both air and trimix 18/45. A multi-gas regression checks a 21 m EAN50 switch, appended stage duration, final surface clearance, cylinder allocation, and preservation of the original profile/state. This is numerical verification, not a claim of equivalence to commercial dive computers or operational validation.
Oxygen partial-pressure warnings
Live ppO₂ and Planner Mode warn above 1.4 ATA (working-dive caution) and above 1.6 ATA (high warning), following DAN's breathing-gas guidance. ATA thresholds convert at 1.01325 bar/ATA, so the exact internal thresholds are 1.41855 and 1.6212 bar regardless of selected display units. Warnings are based on unrounded values and are shown in the selected pressure unit (psi or bar) and ATA. Planner checks each segment's deepest exposure on its selected gas, the currently scrubbed state, and projected ascent peak (including gas switches and saved comparisons). These are instantaneous partial-pressure warnings; cumulative CNS/OTU exposure is not yet modeled.
Surface intervals and repetitive dives
The main page applies adjustable surface-air intervals only at depth zero. Nitrogen and helium off-gas through the same compartment engine; cylinder inventories are preserved and no breathing-gas demand is added. Elapsed time and profile history include the interval. Start next dive refills cylinder inventory explicitly while preserving residual tissues and chronological history. Reset to surface equilibrium clears the session.
Planner Mode can finish a profile at the surface, apply an adjustable interval, and create the next planned dive seeded with the remaining tissues. Each new planned dive uses full cylinders and its own local runtime. Completed dive summaries retain duration, maximum depth, and interval duration. Reset planned tissue history restores an air-equilibrated starting state. Intervals must be positive and no longer than 1440 minutes. Tests verify split intervals match one continuous interval, inventory preservation, helium off-gassing, and repetitive-profile equivalence to continuous simulation.
Breathing system
Advanced Mode switches the live simulator and Planner Mode between open circuit and ideal closed-circuit rebreather (CCR). CCR defaults to a 1.3 bar oxygen setpoint, 1 L/min surface-equivalent metabolic oxygen consumption, and an 8 L loop. Setpoint, oxygen use, and loop volume are adjustable. Changes preserve live tissue loading and affect future live exposure; planner profiles recompute using the selected configuration. Mode switching pauses live playback.
In CCR, the chosen mixture is diluent. Loop ppO₂ follows the setpoint, capped by ambient pressure minus 0.0627 bar water vapour. Remaining inspired inert pressure is divided between N₂ and He in the diluent's inert-gas ratio. Exact linear uptake integration splits travel at the depth where the capped setpoint becomes achievable. Tissue graphs, oxygen warnings, NDL, decompression projections, profile replay, and comparisons use this CCR gas model. Surface intervals still off-gas on surface air.
CCR cylinder estimates model oxygen at the configured metabolic rate, independent of depth, plus diluent required to maintain loop volume during descent. An oxygen supply cylinder is added automatically; live CCR oxygen supply can be selected separately from the active diluent cylinder. Planner uses its dedicated oxygen supply cylinder. GTR uses oxygen rate for oxygen supply and current loop-compression rate for diluent; at a hold or ascent there is no modeled diluent consumption. Open-circuit RMV and its depth consumption multiplier are inactive in CCR.
This is an idealized educational CCR model: it does not model loop priming, oxygen added with loop flushing/compression, leaks, flushing, control/sensor faults, scrubber duration, or bailout. Gas depletion triggers warnings but does not change the ideal loop/setpoint physiology. The concept of setpoint versus diluent is consistent with Shearwater's technical manual; the simulator's consumption assumptions are its own simplified model, not device predictions.
Save, import, export, and share
Planner Mode's Save, import & share menu keeps up to 20 named plans in this browser's local storage, without an account. Saving the same name replaces that local entry. Load restores and recalculates a plan; each entry can also be removed. Browser storage restrictions and quota failures are reported. These saves do not synchronize across browsers or devices.
JSON exports use format: "pt3r.dive", version: 2, and the selected model ID (ZHL-16C, ZHL-16B or VPM-B). Version-1 ZHL-16C files remain readable. Version-2 residual loading uses a versioned engine-state envelope; VPM-B includes conservatism and bubble history as well as dissolved pressures. Model/state/settings mismatches are rejected. They include display units, canonical gas fractions and CCR configuration, cylinders, depth/hold/rate segments, RMV, GF low/high, projected ascent switches, residual tissue seeds, repetitive-dive summaries, and the surface-interval draft setting. Values are stored in metres, litres, bar, and minutes independently of display units. Import validates versions, model, finite values and ranges, gas/cylinder references, CCR consistency and oxygen supply, projected switches, profile runtime, residual loading, and list sizes before changing the planner. Import limit: 200 KB; up to 200 segments, 30 gases, 50 cylinders, 50 switches, and 100 interval summaries. Loading also restores the shared gas list, breathing system, and unit settings used by the app; live playback pauses.
Share links contain UTF-8 JSON encoded as URL-safe base64 in #plan=...; opening one validates it and opens Planner Mode. Plan content stays in the fragment and is not posted to a server. The app caps the encoded payload at 16,000 characters; browser, messaging, and URL-length limits can be lower, so use JSON export for large plans. Links based on localhost work only on the computer running this dev server. A public deployment will allow links with its public origin. Round-trip tests cover open-circuit and CCR plans including residual loading, and ensure restored profiles reproduce tissue states and gas usage.
Release preparation
Run pnpm verify for calculation, type, build, and static bundle checks; pnpm release also creates a deployable archive and SHA-256 manifest. See deployment instructions and the release QA record. The public site is hosted on Cloudflare Pages at dive.pt3r.dev, with Forgejo deployment triggered by commits pushed to main. The project owner has confirmed the deployment works; each new revision still needs its hosted smoke check after deployment. Cross-browser and physical-device validation remain incomplete.
Advanced Mode includes six illustrative slider presets: Low 50/95, Moderate-low 40/90, Default 45/85, Moderate 30/85, Moderate-high 30/75, and High 30/65. GF combinations involve tradeoffs rather than a universal ordering of risk or stop time. Manual GF edits show Custom unless they match a preset. Presets affect the live simulation; Planner Mode retains independent GF settings. These labels are not validated safety ratings or recommended dive settings.
Model expansion architecture
The registry supports ZHL-16C (default), ZHL-16B and experimental VPM-B. The main-page header opens a model selection dialog with descriptions and implementation quirks; changing it after exposure pauses playback and asks for confirmation to reset the current dive. Rejecting keeps the existing model and dive. Planner Mode has its own model selection dialog, locked when residual history is present. B/C use gradient factors; VPM-B uses conservatism 0–4 and hides GF controls. VPM-B live, timeline and authored-profile schedule projections run in workers at up to four submissions per second, with adaptive CPU headroom between calculations. Closing Planner Mode stops its projection workers and pauses replay. Completed values remain visible with their source dive time and result age while updating; the status area keeps a fixed size. Exact projection and exposure-factor caches, direct pressure checks, algebraic unrelaxed ceilings, and warm Boyle roots reduce repeated work. Planner checkpoints make scrubbing evolve only the selected partial segment. Frame-aligned playback and compositor motion run independently of predictions. The live VPM-B ceiling is an unrelaxed bubble estimate, distinct from the iterative ascent schedule. See model conventions, original implementation and validation limits and architecture.
Live dive completion and review
Safety stops show a separate status banner above the ocean with a minutes:seconds countdown, progress bar, and paused status. Travel to the stop and ascent afterward have distinct statuses. Returning to the surface pauses playback, plays a generated splash when browser audio is unlocked, and opens “Dive complete.” View dive stats opens a summary with duration, maximum depth, gas supplied, peak ppO₂, a scrubbable recorded profile, tissue pressures, and gas/status/warning events. Unresolved warnings remain visible on surfacing. The latest summary can be reopened from the main page; it is held in memory and cleared by Reset or a page reload. Each repetitive dive starts a separate recording and excludes its preceding surface interval. Times use minutes:seconds; duration fields accept the same format.
The first opening in each browser-tab session shows an educational-use notice, including “Dive at your own risk.” Dismissal is remembered in session storage when available. Click outside Advanced Mode, Planner Mode, the completion popup, or the dive summary to close it; clicks within a dialog leave it open.
The dive summary plots all sixteen nitrogen/helium compartment pressures with a fixed scale across the recorded dive. Its timeline also updates pressure gauges for every recorded cylinder, including reserve markers and active-cylinder labels. Dive events start collapsed. Cylinder pressures are recorded from the simulation's actual per-cylinder usage, including separate oxygen and diluent supplies in rebreather mode.
New simulations and plans default to GF 45/85 and a surface breathing rate of 15 L/min (displayed in ft³/min when imperial units are selected). Existing saved plans retain their saved settings. Compartment hover readouts show pressure only, in the selected units.
Automatic playback slowdown starts at NDL 5:00 or less. Resuming accelerated playback (using Override or selecting a fast timescale at the limit) disables further automatic slowdown for that dive, even if NDL rises and falls again. Reset, a new dive, or explicitly restoring automatic slowdown re-enables it. Reserve pressure triggers a low descending chime once per cylinder until refilled above reserve, distinct from the sharp NDL beeps. Tooltips fit their content, with longer explanations capped for readability.
Labels and consistent controls
Live and planned cylinders support an optional label of up to 80 characters. Labels appear in configuration, supply selectors, planner segment/ascent-switch selectors, inventory readouts, and recorded summaries. Unnamed cylinders use a consistent numbered fallback. Planner save/import/share preserves labels; older version-1 files without labels remain valid. Main information panels and disclosure menus use triangles at the left of their headings. RMV fields accept/display two decimal places in the selected volume units, preserving canonical L/min calculations. The yellow reserve marker is hoverable and keyboard-focusable and shows the configured reserve in psi or bar. Planner starting-state text follows residual loading; assumptions follow OC/CCR mode, and profile-validation depth/rate ranges follow display units.
Reset to Defaults in the header opens an on-page accept/reject confirmation, then resets all live/planner settings and session progress to their initial values, including imperial units, GF 45/85, RMV 15 L/min, and one unnamed Air cylinder. It removes the active shared-plan fragment to avoid reloading that plan, preserves the saved plan library and the current-session welcome dismissal, and closes the old audio context. Empty helium bars/hit areas are omitted from the summary chart; air-only states display one nitrogen bar per compartment.
After NDL is exhausted, the live tissue graph marks compartments red when their combined nitrogen/helium pressure exceeds their GF high surface limit. For VPM-B, red marks the unrelaxed bubble-gradient surface threshold. Red marks a surface threshold, not a ceiling breach at the current depth.
User-entered names and labels are treated as plain text and escaped by React, never interpreted as HTML, JavaScript, or shell commands. Labels are capped at 80 characters and control/bidirectional override characters are removed. Imported/shared plan JSON rejects prototype-mutating keys at every nesting level and validates numerical ranges, references, and size limits before loading. The saved library also validates entry sizes and names. Cloudflare Pages serves the security headers in public/_headers, including a Content Security Policy that blocks inline scripts, plugins, framing, and form submissions. Inline styles remain allowed for dynamic graph positions; the existing Google Fonts sources are explicitly allowed. These headers take effect after deployment and should be checked on the hosted response.
The main Dive time readout measures the current dive, freezes on surfacing, and resets to 0:00 after applying a surface interval. Surface waiting is excluded from the next dive. The cumulative session clock and model exposure history remain intact for residual loading and repetitive-dive calculations.