cameraYawSmoothSpeed follows at the stored rate, not the slider's 90-270 (#1712)

`cameraYawSmoothSpeed` was clamped to 90-270 when applied, the Auto-Follow Speed slider's range, so `/console cameraYawSmoothSpeed 360` followed at 270. In 1.12.1 its validator `0x50c000` refuses only outside [0.1, 360] (`0x50b330`, `Value out of range` at `0x50b36c`) and the follow reader takes the stored rate as is (`[0xbe1070]` at `0x512d75`).

The camera now uses the stored value; the registry's existing check, which already matched `0x50b330` and also runs on the config load, does the refusing. `FOLLOW_SPEED_RANGE` is gone.

Test: 270, 360, 0.2 and 30 are stored and applied as given; 360.5, 400, 9000, 0, 0.05 and -30 are vetoed and keep 30. It fails with the clamp put back.

Fixes #323

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9e1b3e9ae9c7fd488ea0addd3f11460678cd61fc)

# Conflicts:
#	crates/benilla-app/src/cvars/mod.rs
#	crates/benilla-app/src/cvars/table.rs

Absorbed into the octo fork. Ported by hand: our cvars registry is one file, not upstream's cvars/ split, and it has no .checked() validators or SetOutcome::Vetoed, so the clamp cannot simply be removed — without a veto channel that would turn a clamp into an unvalidated accept. FOLLOW_SPEED_RANGE is widened from the slider's 90..=270 to the reference validator's 0.1..=360 instead, so /console cameraYawSmoothSpeed 360 now follows at 360; refusing (rather than clamping) outside that window is recorded as a deviation on the constant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
Sam Sung
2026-10-25 23:08:58 -06:00
committed by grom
co-authored by Claude Opus 5
parent 694ef5832d
commit 132776c197
2 changed files with 23 additions and 5 deletions
+10 -3
View File
@@ -436,7 +436,9 @@ pub(crate) const REGISTERED: &[Registered] = &[
same("cameraSmoothTrackingStyle", "1"),
// AUTO_FOLLOW_SPEED (`UIOptionsFrame.lua:88`), deg/s, registered "180.0" (`[0xbe1070]`): it
// sets the transition's duration (`|dyaw| / rate * factor`), an average rate, not a slew. The
// knob clamps it to `FOLLOW_SPEED_RANGE`. The stock Save also writes `cameraPitchSmoothSpeed`
// validator `0x50c000` refuses outside [0.1, 360] and the follow reader (`0x512d75`) uses the
// stored value as is; 90 to 270 is only the stock slider's range, and clamping to it made
// `/console cameraYawSmoothSpeed 360` follow at 270. The stock Save also writes `cameraPitchSmoothSpeed`
// at a quarter (`:353`), unregistered here: `FollowRig` has one rate.
same("cameraYawSmoothSpeed", "180"),
// `cameraPivot` `[0xbe10a4]` "1" (`0x50bda3`), smart pivot: gate `0x510690`, routing
@@ -4171,8 +4173,13 @@ mod tests {
FollowStyle::Smart,
"and only that one"
);
apply(&mut app, "cameraYawSmoothSpeed", "270");
assert_eq!(res::<FollowConfig>(&app).yaw_speed, 270.0);
// The validator's [0.1, 360] (`0x50c000`) is the range, not the stock slider's 90 to 270:
// a value inside it is stored and used as is (`0x512d75`). 360 is the case that was wrong.
for (text, want) in [("270", 270.0), ("360", 360.0), ("0.2", 0.2), ("30", 30.0)] {
apply(&mut app, "cameraYawSmoothSpeed", text);
assert_eq!(res::<FollowConfig>(&app).yaw_speed, want, "{text}");
}
// Outside it the reference refuses the set; we clamp to the edge (see FOLLOW_SPEED_RANGE).
apply(&mut app, "cameraYawSmoothSpeed", "9000");
assert_eq!(
res::<FollowConfig>(&app).yaw_speed,
+13 -2
View File
@@ -142,6 +142,8 @@ pub(crate) fn on_cvar(
// The stock dropdown writes 1 Smart, 2 Always, 0 Never (`UIOptionsFrame.lua:518`).
"camerasmoothstyle" => follow.style = FollowStyle::from_cvar(v),
"camerasmoothtrackingstyle" => follow.tracking_style = FollowStyle::from_cvar(v),
// The follow reader takes the stored rate as is (`0x512d75`); 90 to 270 is only the stock
// slider's range, so a `/console` value inside the validator's own stands.
"camerayawsmoothspeed" => {
follow.yaw_speed = v.clamp(*FOLLOW_SPEED_RANGE.start(), *FOLLOW_SPEED_RANGE.end());
}
@@ -202,8 +204,17 @@ impl LookConfig {
/// `cameraYawSmoothSpeed`, default 180°/s (`[0xbe1070]`, read at `0x512d75`): a duration divisor,
/// as a return lasts `|Δyaw| / rate × factor`. The AUTO_FOLLOW_SPEED slider, greyed under Never.
pub(crate) const FOLLOW_SPEED_DEFAULT: f32 = 180.0;
/// The 1.12 slider's own range for [`FollowConfig::yaw_speed`].
pub(crate) const FOLLOW_SPEED_RANGE: std::ops::RangeInclusive<f32> = 90.0..=270.0;
/// What the reference's validator `0x50c000` accepts for [`FollowConfig::yaw_speed`]: outside
/// [0.1, 360] it refuses the set outright (`0x50b330`, `Value out of range` at `0x50b36c`), and
/// the follow reader then uses the stored rate as is (`0x512d75`). 90 to 270 is the AUTO_FOLLOW_
/// SPEED slider's range alone, and clamping to it made `/console cameraYawSmoothSpeed 360` follow
/// at 270.
///
/// Deviation: the reference *refuses* an out-of-range set and keeps the old rate; we clamp to the
/// edge, because this registry has no veto channel — upstream's `Registered::checked` validators
/// and `SetOutcome::Vetoed` are not in this fork, so there is nowhere to report the refusal. The
/// accepted window is now the reference's; only what happens outside it still differs.
pub(crate) const FOLLOW_SPEED_RANGE: std::ops::RangeInclusive<f32> = 0.1..=360.0;
/// `cameraSmoothTimeMin`/`Max`: 0.1 s and 2.0 s (`[0xbe105c]`/`[0xbe1038]`, clamped at `0x510f4d`).
const FOLLOW_TIME_MIN: f32 = 0.1;
const FOLLOW_TIME_MAX: f32 = 2.0;