[P2] VPM-B no-stop search checks only the current state and a 240-minute hold, so it reports no limit (>240) right before a heliox switch transient forces NDL to 0 #64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Priority: P2
Type: bug
Source: full codebase review requested 2026-10-31 (independently reproduced against
2cac7cf)Location:
src/models/vpmb.ts:656,src/models/vpmb.ts:658,src/models/vpmb.ts:662,src/models/vpmb.ts:668,src/models/projection.ts:49Problem and evidence
findNoStopLimit (src/models/vpmb.ts:601-670) returns 0 if the current state is not clear. Otherwise it returns null (no limit, shown as >240) when a 240-minute hold probe is clear. If that probe is not clear, it runs a 16-step bisection on [0, 240]. Both steps assume clearance is monotone in hold time. After switching from air to a helium-rich mix, helium counterdiffusion causes a temporary rise in the VPM-B unrelaxed bubble ceiling. Short holds therefore fail the clear() probe: planAscent returns "Current depth is shallower than the unrelaxed bubble ceiling", and clear() treats that as not clear for non-current states. Later holds pass again. The search skips that interval, so the projection shows NDL = null right before NDL drops to 0 within 0.25-1 min. The ZHL engines got an interval-certified chronological search for #48. docs/MODEL-ARCHITECTURE.md:15 says that work "do[es] not change VPM's separate no-stop planner", so VPM-B still has the old monotone assumption. Correction to the original claim: I could not reproduce its first example (air 25 m/15 min, then Heliox 21/79 at 12 m). Its VPM-B NDL fell steadily from 48.6 with no zero window. In every case I reproduced, the transient appears as a null (>240) NDL, not a finite NDL. The ndl=0 holds that follow mostly carry the "Current depth is shallower than the unrelaxed bubble ceiling" error, not a stop list. The bisection could in principle also hide a transient behind a finite NDL, but my scan found no such case.
I wrote scratch scripts in /tmp/divesim-verify-J13TVq (t.mts to t4.mts) and ran them with node --import tsx against the real source. They use createSimulation("VPM-B", c).advance with 20 m/min descent and 9 m/min ascent, then switchGas to gasFromFractions(21,79), then calculateProjection({model:"VPM-B", ascent:9, spacing:3, ...}) after holds at the switch depth.
Heliox and trimix are user-configurable in the live app (App.tsx:2473, "Configure trimix/heliox, multiple gases, and ascent switches"), so these profiles can be reached through the UI.
Suggested correction
Replace the monotone bisection in findNoStopLimit with a time-ordered search, as the ZHL fix for #48 does. Return the first hold time at which clear() fails, scanning from 0 upward, and never return null or move the lower bound past an interval whose clearance has not been proven. Without an analytic VPM envelope, a bounded scan works. Probe a geometric or fixed grid of short holds (for example every 10 s for the first 10-15 min after a gas change, then coarser), stop at the first failure, then bisect only between the last clear probe and that first failure. Treat a clear 240-minute probe as "no limit" only when every grid probe before it is also clear. Keep the existing 24-hour-horizon handling, where a probe beyond the horizon counts as needing stops.
Acceptance criteria
Add VPM-B cases to tests/unit/ndl-transient.test.ts:
(a) Air 15 m/45 min, Heliox 21/79 at 4.5 m, conservatism 3: the NDL at the switch must be finite, between 0.25 and 0.4 min. Holding for that NDL must leave clear() true, and holding NDL + a small epsilon must be not clear.
(b) Air 30 m/10 min, Heliox at 9 m, conservatism 3: the NDL must be below 0.5 min and not null.
(c) After holding 0.5 min in (b), the remaining NDL must equal the original NDL minus 0.5, or 0 when that is negative.
(d) Single-gas air sweeps must give the same NDLs as before, within the bisection precision.
(e) The worst-case search cost must stay bounded, with a probe cap similar to the ZHL search's 256 projections. pnpm verify passes.
Scope: maintenance of the educational simulator. Preserve historical R1 acceptance and operational-validation limits.