@@ -21,6 +21,8 @@ namespace lua::hades::tethers
2121 constexpr float REDUCED_GRAVITY = 200 .0f ;
2222 constexpr float STRAIGHTEN_SPEED_SCALE = 0 .5f ;
2323 constexpr float DEFAULT_RETRACT_SPEED = 500 .0f ;
24+ constexpr float MAX_TETHER_DT = 1 .0f / 30 .0f ;
25+
2426 // H2 sgg::Thing partial struct for field access via raw offsets.
2527 struct Thing_H2
2628 {
@@ -106,6 +108,8 @@ namespace lua::hades::tethers
106108 // Global tether update
107109 static void update_all_tethers (float dt)
108110 {
111+ dt = std::min (dt, MAX_TETHER_DT );
112+
109113 for (auto &[id, data] : g_tether_data)
110114 {
111115 if (data.links .empty ())
@@ -351,12 +355,26 @@ namespace lua::hades::tethers
351355 {
352356 std::scoped_lock l (g_tether_mutex);
353357
354- // Run once per frame. The engine passes the same dt to every thing in a
355- // single physics tick, so float equality is a reliable frame guard here.
358+ // Run once per physics tick. The engine passes the same dt to every Thing in a tick, so a dt change means a new tick.
359+ // With Vsync consecutive ticks can share identical dt values, so we also track the first Thing ID.
360+ // When it reappears with the same dt, a new tick has begun.
356361 static float s_last_dt = -1 .0f ;
362+ static int s_first_thing_id = -1 ;
363+
364+ bool new_tick = false ;
357365 if (elapsedSeconds != s_last_dt)
358366 {
367+ new_tick = true ;
359368 s_last_dt = elapsedSeconds;
369+ s_first_thing_id = thing->mId ;
370+ }
371+ else if (thing->mId == s_first_thing_id)
372+ {
373+ new_tick = true ;
374+ }
375+
376+ if (new_tick)
377+ {
360378 cleanup_stale_tethers ();
361379 update_all_tethers (elapsedSeconds);
362380 }
0 commit comments