Skip to content

fix(sim3d): re-derive slider position when the playback range shrinks - #392

Open
knipknap wants to merge 2 commits into
mainfrom
fix/370-slider-pinned-at-end
Open

fix(sim3d): re-derive slider position when the playback range shrinks#392
knipknap wants to merge 2 commits into
mainfrom
fix/370-slider-pinned-at-end

Conversation

@knipknap

@knipknap knipknap commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #370

Problem

Follow-up report from the issue: the 3D playback slider sits all the way to the right at 100% while the toolpath reveal is still at the beginning of the job ("not all ops are drawn yet").

Root cause

Not a time-model or stall problem: reconstructing the reported job from the debug archive and driving the actual pipeline headlessly shows the playhead, laser interpolation, vertex reveal, and op map are all correctly aligned and advance smoothly.

The defect is slider bookkeeping in PlaybackOverlay across job regenerations:

  • The slider spans G-code line numbers (set_op_map() / set_player()). When a job regenerates (any edit; the reported session shows the job shrinking repeatedly, e.g. 332 -> 318 -> 190 commands), the new extent is often smaller than the current slider value.
  • Gtk.Range.set_range() clamps the stored value into the new range, silently pinning the slider at the new upper bound - visually exactly 100%.
  • The existing re-sync in _refresh_slider_range() (_set_slider_for_op(current_index)) silently no-ops exactly when it is needed: a freshly built player sits on the first LAYER_START (a command with no G-code output, span_for_op() count 0 -> None), and a playhead carried over from the old job can be out of range in the new map (IndexError). The bogus 100% therefore persists while the reveal is reset to the beginning.
  • Same mechanism when the player is cleared (set_player(None)): the range shrinks to [0, 1], any prior value clamps to 1 = 100%, and the subsequent _set_slider_for_op(0) computes value 2, which clamps right back to 1.

Changes

  • _refresh_slider_range() re-derives the slider value from the playhead after every range change, falling back to the start of the job for a pristine playhead or an output-less command.

  • update_ops_range() pins the disabled slider (no player) explicitly at 0 instead of letting the stale value clamp to 1 of [0, 1].

  • Bounded debug diagnostics for the case where this does not resolve the users issue (second commit):

    • player and op map swaps (set/cleared, with command and line counts) - a mismatched pair is the prime desync suspect;
    • the range-shrink correction itself (clamped value, new extent, re-derived position);
    • playback start (sim time, total, extent, speed) and every stop with an explicit reason (finished, user-pause, canvas-unrealized, player-lost, destroy) plus wall-clock duration;
    • a one-shot warning when the playhead target regresses during forward playback;
    • a heartbeat every ~5 s of playback with sim time, target, slider value, and speed (~0.2 lines/s, DEBUG only).

    All events are rare or strictly rate-limited; nothing logs per tick.

Testing

  • Three new regression tests in tests/ui_gtk/sim3d/test_playback_overlay.py, all failing on the old code (one reproduces value == 1 on range [0, 1], i.e. the reported 100% slider) and passing with the fix:
    • map shrink with an out-of-range playhead index,
    • map shrink where the current command has no G-code output,
    • player cleared while an op map is still set.
  • Log volume verified: a 320 s playback produces exactly 1 start + 59 heartbeats + 1 stop record at DEBUG level.
  • Full sim3d UI suite: 206 passed. Simulator + encoder suites: 160 passed. ruff and flake8 (79 cols) clean; pyright reports no new findings beyond the pre-existing environment import-resolution noise.

The playback slider could stay pinned at 100% while the 3D view
showed a fresh, barely-started playhead (issue #370 follow-up).

Gtk.Range.set_range() clamps the stored value into the new range,
so whenever a regenerated job produced a smaller G-code extent, the
slider from the previous (larger or further-advanced) job was
clamped to the new upper bound: 100%. The re-sync in
_refresh_slider_range() relied on _set_slider_for_op(), which
silently no-ops exactly in the cases where it was needed: a fresh
player sits on the first LAYER_START (a command with no G-code
output, span count 0), and a playhead from the old job can be out
of range in the new map (IndexError). The bogus 100% therefore
persisted while the toolpath reveal was reset to the beginning.

- _refresh_slider_range() now re-derives the slider value from the
  playhead after every range change, falling back to the start of
  the job for a pristine playhead or an output-less command.
- Clearing the player (set_player(None)) now pins the disabled
  slider explicitly at 0 instead of letting the stale value clamp
  to 1 of [0, 1] - which renders as 100%.

Regression tests reproduce all three paths on the old code:
map shrink with an out-of-range playhead, map shrink with an
output-less current command, and player cleared with a stale map.

Fixes #370
In case the slider fix does not resolve the reported issue, add
debug-level diagnostics that make a failing session visible in the
log without flooding it:

- player and op map swaps (set/cleared, with command and line
  counts) - a mismatched pair is the prime suspect for desync;
- the range-shrink correction itself, logging the clamped value,
  the new extent, and the re-derived position;
- playback start (sim time, total, extent, speed) and every stop
  with an explicit reason (finished, user pause, canvas unrealized,
  player lost, destroy) plus wall-clock duration;
- a one-shot warning when the playhead target regresses during
  forward playback;
- a heartbeat every ~5 s of playback with sim time, target, slider
  value, and speed (~0.2 lines/s, DEBUG only).

All events are rare or strictly rate-limited; nothing logs per
tick.
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.

Delay issue with 3D playback

1 participant