diff --git a/src/AcDream.Core/Rendering/CameraDiagnostics.cs b/src/AcDream.Core/Rendering/CameraDiagnostics.cs new file mode 100644 index 0000000..01349f1 --- /dev/null +++ b/src/AcDream.Core/Rendering/CameraDiagnostics.cs @@ -0,0 +1,64 @@ +using System; + +namespace AcDream.Core.Rendering; + +/// +/// Runtime-tunable knobs for the retail-faithful chase camera. Mirrors +/// the pattern: +/// static fields seeded from env vars at process start, runtime-settable +/// via property setters that the DebugPanel writes to. +/// +/// +/// Spec: docs/superpowers/specs/2026-05-18-retail-chase-camera-design.md. +/// +/// +public static class CameraDiagnostics +{ + /// + /// Master toggle. When false (default) the legacy + /// AcDream.App.Rendering.ChaseCamera is the active camera; + /// when true, the retail-faithful RetailChaseCamera is. + /// Initial state from ACDREAM_RETAIL_CHASE=1. + /// + public static bool UseRetailChaseCamera { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_RETAIL_CHASE") == "1"; + + /// + /// 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 ACDREAM_CAMERA_ALIGN_SLOPE; default-on if unset. + /// + public static bool AlignToSlope { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_CAMERA_ALIGN_SLOPE") != "0"; + + /// + /// Per-frame translation damping rate. Retail default 0.45. Higher + /// (→ 1.0) snaps faster; lower (→ 0.0) lags more. Formula per frame: + /// alpha = clamp(TranslationStiffness * dt * 10, 0, 1). + /// + public static float TranslationStiffness { get; set; } = 0.45f; + + /// + /// 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. + /// + public static float RotationStiffness { get; set; } = 0.45f; + + /// + /// 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. + /// + public static float MouseLowPassWindowSec { get; set; } = 0.25f; + + /// + /// Per-second rate that held-key offset adjustments + /// (CameraZoomIn/Out, CameraRaise/Lower) integrate into the + /// camera's Distance / Pitch. Retail default 40.0. + /// + public static float CameraAdjustmentSpeed { get; set; } = 40.0f; +} diff --git a/tests/AcDream.Core.Tests/Rendering/CameraDiagnosticsTests.cs b/tests/AcDream.Core.Tests/Rendering/CameraDiagnosticsTests.cs new file mode 100644 index 0000000..d3d3251 --- /dev/null +++ b/tests/AcDream.Core.Tests/Rendering/CameraDiagnosticsTests.cs @@ -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; + } +}