feat(camera): add CameraDiagnostics static tunable owner

Six knobs for the upcoming retail chase camera: UseRetailChaseCamera
master toggle (env ACDREAM_RETAIL_CHASE), AlignToSlope (env
ACDREAM_CAMERA_ALIGN_SLOPE, default on), TranslationStiffness +
RotationStiffness (both 0.45 retail default), MouseLowPassWindowSec
(0.25), CameraAdjustmentSpeed (40.0). DebugPanel mirror lands later;
this commit just stands up the static surface + defaults + tests.

Per spec docs/superpowers/specs/2026-05-18-retail-chase-camera-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-18 19:29:11 +02:00
parent 73dee43d14
commit 5945f1d915
2 changed files with 110 additions and 0 deletions

View file

@ -0,0 +1,64 @@
using System;
namespace AcDream.Core.Rendering;
/// <summary>
/// Runtime-tunable knobs for the retail-faithful chase camera. Mirrors
/// the <see cref="AcDream.Core.Physics.PhysicsDiagnostics"/> pattern:
/// static fields seeded from env vars at process start, runtime-settable
/// via property setters that the DebugPanel writes to.
///
/// <para>
/// Spec: <c>docs/superpowers/specs/2026-05-18-retail-chase-camera-design.md</c>.
/// </para>
/// </summary>
public static class CameraDiagnostics
{
/// <summary>
/// Master toggle. When false (default) the legacy
/// <c>AcDream.App.Rendering.ChaseCamera</c> is the active camera;
/// when true, the retail-faithful <c>RetailChaseCamera</c> is.
/// Initial state from <c>ACDREAM_RETAIL_CHASE=1</c>.
/// </summary>
public static bool UseRetailChaseCamera { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_RETAIL_CHASE") == "1";
/// <summary>
/// When true (default), the camera basis follows the player's
/// 5-frame averaged velocity vector — tilts with the terrain on
/// hills. When false, the basis is built from a flat (yaw, 0) vector
/// and the camera stays horizontal even on slopes. Initial state
/// from <c>ACDREAM_CAMERA_ALIGN_SLOPE</c>; default-on if unset.
/// </summary>
public static bool AlignToSlope { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_CAMERA_ALIGN_SLOPE") != "0";
/// <summary>
/// Per-frame translation damping rate. Retail default 0.45. Higher
/// (→ 1.0) snaps faster; lower (→ 0.0) lags more. Formula per frame:
/// <c>alpha = clamp(TranslationStiffness * dt * 10, 0, 1)</c>.
/// </summary>
public static float TranslationStiffness { get; set; } = 0.45f;
/// <summary>
/// Per-frame rotation damping rate. Independent of translation —
/// can be tuned higher so the camera swings to look at you faster
/// than it physically catches up. Retail default 0.45.
/// </summary>
public static float RotationStiffness { get; set; } = 0.45f;
/// <summary>
/// Mouse-delta low-pass window (seconds). Mouse deltas spaced
/// closer than this are averaged with the previous delta before
/// being fed to pitch/yaw adjustments. Smooths out jitter on
/// high-DPI mice. Retail default 0.25.
/// </summary>
public static float MouseLowPassWindowSec { get; set; } = 0.25f;
/// <summary>
/// Per-second rate that held-key offset adjustments
/// (CameraZoomIn/Out, CameraRaise/Lower) integrate into the
/// camera's Distance / Pitch. Retail default 40.0.
/// </summary>
public static float CameraAdjustmentSpeed { get; set; } = 40.0f;
}

View file

@ -0,0 +1,46 @@
using AcDream.Core.Rendering;
using Xunit;
namespace AcDream.Core.Tests.Rendering;
public class CameraDiagnosticsTests
{
// NOTE: These tests assume the env vars ACDREAM_RETAIL_CHASE and
// ACDREAM_CAMERA_ALIGN_SLOPE are NOT set when running the test
// suite. Static class is initialised on first access; values reflect
// the env state at that time.
[Fact]
public void Defaults_AreRetailValues()
{
// Reset to defaults explicitly (test isolation; another test may
// have flipped these earlier in the run).
CameraDiagnostics.TranslationStiffness = 0.45f;
CameraDiagnostics.RotationStiffness = 0.45f;
CameraDiagnostics.MouseLowPassWindowSec = 0.25f;
CameraDiagnostics.CameraAdjustmentSpeed = 40.0f;
CameraDiagnostics.AlignToSlope = true;
CameraDiagnostics.UseRetailChaseCamera = false;
Assert.Equal(0.45f, CameraDiagnostics.TranslationStiffness);
Assert.Equal(0.45f, CameraDiagnostics.RotationStiffness);
Assert.Equal(0.25f, CameraDiagnostics.MouseLowPassWindowSec);
Assert.Equal(40.0f, CameraDiagnostics.CameraAdjustmentSpeed);
Assert.True(CameraDiagnostics.AlignToSlope);
Assert.False(CameraDiagnostics.UseRetailChaseCamera);
}
[Fact]
public void Setters_PersistRuntimeChanges()
{
CameraDiagnostics.TranslationStiffness = 0.8f;
CameraDiagnostics.UseRetailChaseCamera = true;
Assert.Equal(0.8f, CameraDiagnostics.TranslationStiffness);
Assert.True(CameraDiagnostics.UseRetailChaseCamera);
// Reset so other tests aren't poisoned.
CameraDiagnostics.TranslationStiffness = 0.45f;
CameraDiagnostics.UseRetailChaseCamera = false;
}
}