fix: frame-rate-independent velocity spring (60 Hz-anchored)#76
Merged
Conversation
SpringResolver divided corrections by delta and applied a constant per-tick lerp weight, so the fraction of error closed per second — and the residual velocity that feeds damping and the max-velocity clamp — scaled with the physics tick rate: stiffer/twitchier at 120 Hz, mushier at 30 Hz, with all tuning implicitly calibrated to 60 Hz. The plugin advertises "Godot 4.7+" with no tick-rate caveat. Fix, staying within the documented velocity-based architecture (NOT a PD rewrite): normalize both the velocity target and the blend weight to a 60 Hz reference via _fr_weight(w, delta) = 1 - pow(1 - clamp(w,0,1), delta*60). At 60 Hz this is the identity (delta*60 == 1), so existing tuning is preserved bit-for-bit; at other rates the convergence per unit wall-clock time is constant. - spring_resolver.gd: angular + pin lerps use _REFERENCE_HZ targets and _fr_weight. - test_spring_math.gd (new, +3): identity at 60 Hz; frame-rate-independent decay at 30/60/120; out-of-range weight clamps (no NaN). - ROADMAP/CHANGELOG: resolves the last "Still open" hardening item. Validation: 112/112 GUT (the 109 pre-existing physics tests pass unchanged, proving 60 Hz behaviour is preserved), stable x4; clean scene-smoke on the active-ragdoll demos. Feel at 30/60/120 Hz pending in-editor visual review. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The headline correctness item from the audit.⚠️ Touches the physics-feel path — please give it an in-editor visual pass before merge.
Problem
SpringResolverdivided corrections bydeltaand blended velocity by a constant per-tick weight. Over a fixed wall-clock time you apply that weighttick_ratetimes, so the effective stiffness — and the residual velocity that then feeds the strength-scaled damping and themax_angular/linear_velocityclamp — scaled with the physics tick rate: stiffer/twitchier at 120 Hz, mushier at 30 Hz, with all tuning implicitly calibrated to 60 Hz. The plugin advertises "Godot 4.7+" with no tick-rate requirement.Fix (stays velocity-based — not a PD rewrite)
Normalize both the velocity target and the blend weight to a 60 Hz reference:
At 60 Hz,
delta * 60 == 1, so_fr_weightis the identity and the velocity targeterror * 60 == error / delta— i.e. bit-identical to the old behaviour at the default tick rate (existing tuning unchanged). At other rates the convergence per unit wall-clock time is constant. I deliberately did not rewrite the spring into a force/torque PD controller — that would contradict the documented velocity-based architecture.Validation
test_spring_math.gd(new, +3) proves the math: identity at 60 Hz, frame-rate-independent decay at 30/60/120, and out-of-range weights clamp (no NaN).demo,euphoria_showcase,shooting_range.ROADMAP.md.🔬 Visual review checklist (please)
Project Settings → Physics → Common → Physics Ticks/Second = 120): characters should no longer feel stiffer/twitchier than 60 Hz.euphoria_showcase.tscnis the best scene to feel it (stagger sway + active resistance).🤖 Generated with Claude Code