[P2] The replay uses the departure GF even when GF was changed mid-dive, so its decompression verdict, NDL and ceilings contradict the live dive #109

Open
opened 2026-10-31 19:47:40 +00:00 by pedro · 0 comments
Owner

Priority: P2
Type: bug
Source: full codebase review requested 2026-10-31 (independently reproduced against 2cac7cf)

Location: src/App.tsx:801-804, src/App.tsx:884-889, src/App.tsx:2902-2904, src/App.tsx:2952-2990, src/App.tsx:728-737, src/replay/diveReplay.ts:186-190, src/replay/diveReplay.ts:216, src/replay/diveReplay.ts:274, src/replay/diveReplay.ts:314-315, src/replay/diveReplay.ts:340-341, src/replay/diveReplay.ts:367-368, src/replay/diveReplay.ts:412, src/replay/diveReplay.ts:427, src/replay/DiveReplayComparison.tsx:439-447

Problem and evidence

For Bühlmann models, the Conservativeness slider and the GF low/high inputs (App.tsx:2952-2990) stay enabled underwater. The VPM conservatism slider is disabled once the dive has started (App.tsx:2902-2904). The live ceilings, NDL and the 'Uncleared decompression at surface' warning use the current gfLow/gfHigh (App.tsx:728-737, 874).

The replay seed is a ReplayContext holding a single low/high pair (App.tsx:801-804). It is captured once, from the frame before departure (replayStart.current = previousReplay.current, App.tsx:888). DiveSample does not record GF.

replayDive applies seed.low/seed.high to the whole dive: per-point tissueCeilings and ceiling (diveReplay.ts:186-190, 216, 274), the final decompressionRequired verdict (314-315), the VPM tail plan (340-341), the Complete decompression ascent plan and its final check (367-368, 412), and bottomNdl (427). The unmodified 'Original reconstruction', and the 'Modified replay' built from it, therefore evaluate the dive with a GF the diver had stopped using. The comparison UI never shows which GF it used.

Effects:

  • Unsafe direction: if the diver tightens GF mid-dive and surfaces with uncleared decompression under the live GF, the replay reports no decompression requirement, offers no 'Complete decompression ascent' and shows NDL time remaining.
  • Opposite direction: a live dive that surfaced clean gets the 'Modified replay has uncleared decompression requirements' alert even though nothing was modified.
  1. Browser reproduction on the HEAD production build (/tmp/divesim-review-dist-SqbiNx), served on 127.0.0.1 and driven by Playwright, using a copy of the reviewer's pw-gf.mjs in /tmp/divesim-verify-ozE7jk:

    • Metric units. In Advanced Mode, set Conservativeness to Low (GF 50/95). Click 30 m, then +1:00 x16.
    • In Advanced Mode, set Conservativeness to High (GF 30/65). The page then says 'NDL is 5:00 or less'.
    • Click Surface, step until the dive ends, open View dive stats.
    • Result: the summary alert reads 'Returned to the surface with unresolved warnings: Uncleared decompression at surface'.
    • Compare & replay -> Calculate comparison, with no setting changes: replayAlerts=[] and there are 0 'Complete decompression ascent' buttons. Both columns show 'NDL at end of deep portion 3:11'. No page errors.
  2. Reverse direction, from the reviewer's node harness (/tmp/divesim-review-TPAVti/gf.ts, run from the worktree with tsx against the real replayDive). This harness models the App recorder rather than driving the real App:

    • Depart at 30/65, switch to 50/95 after 28 min at 30 m, and follow the GF-95 stops (6 m 2 min, 3 m 6 min).
    • The recorded final ceiling is 0.00, but the replay returns decompressionRequired=true with finalCeiling 2.22 m.
    • The worst per-sample ceiling mismatch is 3.53 m (recorded 3.22 vs replay 6.75).
  3. Code trace confirms every replay calculation reads seed.low/seed.high, and the seed is frozen at departure (App.tsx:884-889).

Suggested correction

Simplest consistent fix: disable the Bühlmann Conservativeness slider and the GF low/high inputs while underwater (dive.minutes > 0 || dive.history.some(p => p.depth > 0)), the same way VPM conservatism is already locked. Then the departure seed matches the GF used for the whole dive.

If mid-dive GF changes must stay possible:

  • Record gfLow/gfHigh on each DiveSample and push a sample when they change.
  • In replayDive, use each leg's own GF for readouts and ceilings, the GF at the bottom sample for bottomNdl, and the GF at the last submerged or ascent-start sample for decompressionRequired and completeDeco.
  • At minimum, show the GF used by the replay and warn when it differs from a GF used during the dive.

Acceptance criteria

Regression test, ZHL-16C live dive in the browser or an App-level test:

  • Depart at GF 50/95, hold 30 m for 16 min, switch to GF 30/65, then surface directly.
  • Either the GF controls are disabled underwater, or the original reconstruction reports decompressionRequired=true, offers 'Complete decompression ascent' and computes bottomNdl and ceilings with GF 30/65, consistent with the live 'Uncleared decompression at surface' warning.

Reverse case:

  • Depart at 30/65, switch to 50/95 underwater and complete the GF-95 stops.
  • The unmodified replay must not raise 'uncleared decompression requirements'.
  • With the GF-recording approach, per-sample replay ceilings match the recorded sample ceilings within a small tolerance. pnpm verify passes.

Scope: maintenance of the educational simulator. Preserve historical R1 acceptance and operational-validation limits.

Priority: P2 Type: bug Source: full codebase review requested 2026-10-31 (independently reproduced against `2cac7cf`) Location: `src/App.tsx:801-804`, `src/App.tsx:884-889`, `src/App.tsx:2902-2904`, `src/App.tsx:2952-2990`, `src/App.tsx:728-737`, `src/replay/diveReplay.ts:186-190`, `src/replay/diveReplay.ts:216`, `src/replay/diveReplay.ts:274`, `src/replay/diveReplay.ts:314-315`, `src/replay/diveReplay.ts:340-341`, `src/replay/diveReplay.ts:367-368`, `src/replay/diveReplay.ts:412`, `src/replay/diveReplay.ts:427`, `src/replay/DiveReplayComparison.tsx:439-447` Problem and evidence For Bühlmann models, the Conservativeness slider and the GF low/high inputs (App.tsx:2952-2990) stay enabled underwater. The VPM conservatism slider is disabled once the dive has started (App.tsx:2902-2904). The live ceilings, NDL and the 'Uncleared decompression at surface' warning use the current gfLow/gfHigh (App.tsx:728-737, 874). The replay seed is a ReplayContext holding a single low/high pair (App.tsx:801-804). It is captured once, from the frame before departure (`replayStart.current = previousReplay.current`, App.tsx:888). DiveSample does not record GF. replayDive applies seed.low/seed.high to the whole dive: per-point tissueCeilings and ceiling (diveReplay.ts:186-190, 216, 274), the final decompressionRequired verdict (314-315), the VPM tail plan (340-341), the Complete decompression ascent plan and its final check (367-368, 412), and bottomNdl (427). The unmodified 'Original reconstruction', and the 'Modified replay' built from it, therefore evaluate the dive with a GF the diver had stopped using. The comparison UI never shows which GF it used. Effects: - Unsafe direction: if the diver tightens GF mid-dive and surfaces with uncleared decompression under the live GF, the replay reports no decompression requirement, offers no 'Complete decompression ascent' and shows NDL time remaining. - Opposite direction: a live dive that surfaced clean gets the 'Modified replay has uncleared decompression requirements' alert even though nothing was modified. 1. Browser reproduction on the HEAD production build (/tmp/divesim-review-dist-SqbiNx), served on 127.0.0.1 and driven by Playwright, using a copy of the reviewer's pw-gf.mjs in /tmp/divesim-verify-ozE7jk: - Metric units. In Advanced Mode, set Conservativeness to Low (GF 50/95). Click 30 m, then +1:00 x16. - In Advanced Mode, set Conservativeness to High (GF 30/65). The page then says 'NDL is 5:00 or less'. - Click Surface, step until the dive ends, open View dive stats. - Result: the summary alert reads 'Returned to the surface with unresolved warnings: Uncleared decompression at surface'. - Compare & replay -> Calculate comparison, with no setting changes: replayAlerts=[] and there are 0 'Complete decompression ascent' buttons. Both columns show 'NDL at end of deep portion 3:11'. No page errors. 2. Reverse direction, from the reviewer's node harness (/tmp/divesim-review-TPAVti/gf.ts, run from the worktree with tsx against the real replayDive). This harness models the App recorder rather than driving the real App: - Depart at 30/65, switch to 50/95 after 28 min at 30 m, and follow the GF-95 stops (6 m 2 min, 3 m 6 min). - The recorded final ceiling is 0.00, but the replay returns decompressionRequired=true with finalCeiling 2.22 m. - The worst per-sample ceiling mismatch is 3.53 m (recorded 3.22 vs replay 6.75). 3. Code trace confirms every replay calculation reads seed.low/seed.high, and the seed is frozen at departure (App.tsx:884-889). Suggested correction Simplest consistent fix: disable the Bühlmann Conservativeness slider and the GF low/high inputs while underwater (`dive.minutes > 0 || dive.history.some(p => p.depth > 0)`), the same way VPM conservatism is already locked. Then the departure seed matches the GF used for the whole dive. If mid-dive GF changes must stay possible: - Record gfLow/gfHigh on each DiveSample and push a sample when they change. - In replayDive, use each leg's own GF for readouts and ceilings, the GF at the bottom sample for bottomNdl, and the GF at the last submerged or ascent-start sample for decompressionRequired and completeDeco. - At minimum, show the GF used by the replay and warn when it differs from a GF used during the dive. Acceptance criteria Regression test, ZHL-16C live dive in the browser or an App-level test: - Depart at GF 50/95, hold 30 m for 16 min, switch to GF 30/65, then surface directly. - Either the GF controls are disabled underwater, or the original reconstruction reports decompressionRequired=true, offers 'Complete decompression ascent' and computes bottomNdl and ceilings with GF 30/65, consistent with the live 'Uncleared decompression at surface' warning. Reverse case: - Depart at 30/65, switch to 50/95 underwater and complete the GF-95 stops. - The unmodified replay must not raise 'uncleared decompression requirements'. - With the GF-recording approach, per-sample replay ceilings match the recorded sample ceilings within a small tolerance. pnpm verify passes. Scope: maintenance of the educational simulator. Preserve historical R1 acceptance and operational-validation limits.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: pedro/DiveSim#109
No description provided.