A diagnostic helper for measuring view tilt requested by recoil vs what was actually rendered. It tracks a configured number of shots, logs the requested kick and requested tilt gains when a probe is started, records per-shot requested values via Asked, samples the shown lean each Frame to record the peak, and logs warnings about common failure modes.
using Sandbox;
using System;
namespace NZombies;
/// <summary>
/// `nz_tilt_probe [shots]` — what the model lean was ASKED for, and what it actually reached.
///
/// ⛔ IT EXISTS BECAUSE "THE VERTICAL DOES BASICALLY NOTHING" HAS AT LEAST FOUR CAUSES AND THEY ARE
/// INDISTINGUISHABLE FROM IN FRONT OF THE MONITOR: the kick's pitch arriving near zero, the gain
/// being multiplied out somewhere, the spring never reaching the target, or the rotation being
/// applied about an axis that does not read on screen. Reasoning narrowed it to "all four look
/// correct", which is the point at which INSTRUCTIONS.md §7 applies and guessing stops paying.
///
/// ⚠️ IT PRINTS BOTH ENDS OF THE CHAIN, WHICH IS THE WHOLE DESIGN. One line at the shot says what
/// was requested; one line when the lean dies says what was reached. If the request is 1.2° and the
/// peak is 1.2°, the maths is fine and the problem is that a pitch of that size simply does not
/// read against a camera that is pitching too — a different fix entirely from a number being lost.
///
/// ⚠️ COUNTED IN SHOTS, NOT SECONDS. A burst is the only way to judge a lean, and a timed probe
/// either stops mid-burst or buries the interesting lines under a hundred idle frames.
/// </summary>
public static class TiltProbe
{
static int _left;
static Vector3 _peak;
static bool _watching;
public static bool On => _left > 0 || _watching;
/// <summary>Log the next few shots' lean, both requested and delivered.</summary>
[ConCmd( "nz_tilt_probe" )]
public static void Cmd( int shots = 5 )
{
_left = shots < 1 ? 5 : shots;
_peak = Vector3.Zero;
_watching = false;
Log.Info( $"[nz-tilt] probing the next {_left} shot(s)" );
Log.Info( "[nz-tilt] 'asked' is the lean the shot requested, in degrees" );
Log.Info( "[nz-tilt] 'peak' is the most the spring actually reached before it died" );
Log.Info( $"[nz-tilt] gains: up ×{GlobalHandling.VisualFollowUp:0.##}"
+ $" side ×{GlobalHandling.VisualFollowSide:0.##}"
+ $" roll ×{GlobalHandling.VisualFollowRoll:0.##}"
+ $" soften {GlobalHandling.VisualTiltSoften:0.###}s" );
}
/// <summary>Called at the shot, with the kick that drove it and the lean it produced.</summary>
public static void Asked( Angles kick, float up, float side, float roll, bool following )
{
if ( _left <= 0 ) return;
_left--;
_watching = true;
_peak = Vector3.Zero;
Log.Info( $"[nz-tilt] kick pitch {kick.pitch:0.000} yaw {kick.yaw:0.000}"
+ $" -> asked up {up:0.000}° side {side:0.000}° roll {roll:0.000}°"
+ (following ? "" : " (NOT following — authored path)") );
// ⚠️ A ZERO KICK IS CALLED OUT RATHER THAN LEFT TO BE SPOTTED IN THE NUMBERS, because it is
// the one cause with a completely different fix: nothing downstream can make a lean out of
// a shot that did not push the view.
if ( MathF.Abs( kick.pitch ) < 0.0001f )
Log.Warning( "[nz-tilt] ⛔ the shot's PITCH is zero — the vertical lean has nothing to"
+ " scale. Check nz_recoil (vertical base, RecoilScale) before touching the tilt." );
}
/// <summary>Called every frame the lean is alive, with what is being drawn.</summary>
public static void Frame( Vector3 shown )
{
if ( !_watching ) return;
_peak.x = MathF.Max( _peak.x, MathF.Abs( shown.x ) );
_peak.y = MathF.Max( _peak.y, MathF.Abs( shown.y ) );
_peak.z = MathF.Max( _peak.z, MathF.Abs( shown.z ) );
if ( shown.LengthSquared > 1e-8f ) return;
// the lean has died — report what it managed
_watching = false;
Log.Info( $"[nz-tilt] peak up {_peak.x:0.000}° side {_peak.y:0.000}° roll {_peak.z:0.000}°" );
if ( _peak.x < 0.02f && _peak.y > 0.05f )
Log.Warning( "[nz-tilt] ⚠ the vertical reached nothing while the horizontal did —"
+ " the loss is upstream of the spring, not in it." );
}
}