graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: playback builds the scene for the frame the playbar shows
The frame-change check in tick_frame ran before the widget ticks, and the
Playbar's tick is what advances the frame during playback — so every tick
rebuilt the scene for the frame the playbar showed LAST tick and then
advanced the readout, leaving the whole scene one frame behind the number
on the playbar for as long as it played. The check now runs after the
ticks, and a frame that moved asks for a redraw on its own.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/app.rs | 35 ++++++++++++++++++++---------------
1 file changed, 20 insertions(+), 15 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 13baf8b..1fba694 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -9739,20 +9739,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
}
}
- // A simnet's geometry is a function of the frame, so advancing the
- // timeline invalidates the scene the way editing a node does. Gated on
- // the graph actually containing one: without this, every frame of
- // playback would rebuild the scene for a graph that cannot have
- // changed.
- let frame_now = self.sim_frame();
- if frame_now != self.last_sim_frame {
- self.last_sim_frame = frame_now;
- if crate::geometry::contains_simnet(&self.fs_root) {
- self.rebuild_scene_geometry();
- self.viewport_dirty = true;
- }
- }
-
// A detached window the user closed hands its pane back here, so a
// closed window cannot strand the pane as a stub nothing can revive.
let reclaimed = self.poll_detached_children();
@@ -9842,6 +9828,25 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
}
}
}
+ // A simnet's geometry is a function of the frame, so advancing the
+ // timeline invalidates the scene the way editing a node does. Gated on
+ // the graph actually containing one: without this, every frame of
+ // playback would rebuild the scene for a graph that cannot have
+ // changed. AFTER the widget ticks, because the Playbar's tick is what
+ // advances the frame during playback: until 2026-09-28 this ran
+ // before them, so every tick rebuilt the scene for the frame the
+ // playbar showed LAST tick and then advanced the readout — the whole
+ // scene a frame behind the number on the playbar, for as long as it
+ // played.
+ let frame_now = self.sim_frame();
+ let frame_moved = frame_now != self.last_sim_frame;
+ if frame_moved {
+ self.last_sim_frame = frame_now;
+ if crate::geometry::contains_simnet(&self.fs_root) {
+ self.rebuild_scene_geometry();
+ self.viewport_dirty = true;
+ }
+ }
// Every slot (and, through the adapter, its embedded children) was
// just ticked by hand. The runner ticks the context's tick_receivers
// right after Application::tick, which would tick the Graph,
@@ -9987,7 +9992,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
self.read_panel_offsets();
}
- tick_changed || panned || reclaimed || glow_animating
+ tick_changed || panned || reclaimed || glow_animating || frame_moved
}
/// Flush CPU-staged mesh updates to the renderer's persistent meshes.