acdream/src/AcDream.App/Rendering/FlyCamera.cs
Erik d4b5c71e66 fix(render): re-land near plane 0.1m (retail Render::znear) — #110 resolved, closes the §4 corner see-through; close #105/#110
The 137b4f2 payload, re-landed now that #110 is resolved: the missing-indoor-
textures correlation was the pre-existing #105 staged-texture-flush drop
(fixed in c787201), not a near-plane mechanism. znear=0.1 merely raised #105's
trigger probability — a closer near plane makes close-up geometry newly
visible, inflating per-frame prepare/upload pressure indoors and growing the
never-flushed tail. Exactly the handoff's only-credible-link hypothesis,
verified instead of assumed.

Retail: Render::SetFOVRad sets znear=0.1 flat (decomp :342173, initializer
:1101867). 0.1 < the 0.3m camera-collision sphere, so a wall the collided eye
presses against no longer falls inside the near plane — the §4 corner
see-through-wall closes.

Verification on the 0.1 arm (the arm that struck 2-of-3 on 2026-06-10):
nearplane-reland-1.log — [tex-flush] after=0 on all 45 lines, 68,291 [shell]
lines with zero zh>0 batches, all four dat tripwires silent, no [wb-error].
ISSUES.md: #105 + #110 moved to Recently closed with root cause + evidence.
Pending user re-gate: corner press (wall stays solid) + distance scan for
z-shimmer (none expected; retail ships 0.1 with D24).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 12:14:00 +02:00

80 lines
2.9 KiB
C#

// src/AcDream.App/Rendering/FlyCamera.cs
using System.Numerics;
namespace AcDream.App.Rendering;
public sealed class FlyCamera : ICamera
{
public Vector3 Position { get; set; } = new(96, 96, 150);
public float Yaw { get; set; } = MathF.PI / 2f; // facing +Y
public float Pitch { get; set; } = -0.3f; // looking slightly down
public float FovY { get; set; } = MathF.PI / 3f;
public float Aspect { get; set; } = 16f / 9f;
/// <summary>
/// Default move speed in world units per second. AC's character walks
/// at ~4 u/s and runs at ~7 u/s; 12 here is comfortable for navigating
/// scenery without overshooting and roughly tracks a fast jog. Hold
/// shift while moving to enter "boost" mode at <see cref="BoostSpeed"/>.
/// </summary>
public float MoveSpeed { get; set; } = 12f;
public float BoostSpeed { get; set; } = 50f;
public float MouseSensitivity { get; set; } = 0.003f;
private const float PitchLimit = 1.5533f; // ~89 degrees
public Matrix4x4 View
{
get
{
var forward = Forward();
return Matrix4x4.CreateLookAt(Position, Position + forward, Vector3.UnitZ);
}
}
// Near plane 0.1 m = retail Render::znear (see RetailChaseCamera.Projection).
public Matrix4x4 Projection
=> Matrix4x4.CreatePerspectiveFieldOfView(FovY, Aspect, 0.1f, 5000f);
/// <summary>
/// Integrate position for one frame based on WASD + vertical keys.
/// W/S move forward/back in the horizontal plane (ignoring pitch).
/// A/D strafe left/right. Up/down translate along world Z.
/// </summary>
public void Update(double dt, bool w, bool a, bool s, bool d, bool up, bool down, bool boost = false)
{
float speed = boost ? BoostSpeed : MoveSpeed;
float step = (float)(speed * dt);
// Forward in the horizontal plane (ignore pitch so W doesn't dive into ground).
var flatForward = new Vector3(MathF.Cos(Yaw), MathF.Sin(Yaw), 0f);
var right = new Vector3(MathF.Sin(Yaw), -MathF.Cos(Yaw), 0f);
if (w) Position += flatForward * step;
if (s) Position -= flatForward * step;
if (a) Position -= right * step;
if (d) Position += right * step;
if (up) Position += Vector3.UnitZ * step;
if (down) Position -= Vector3.UnitZ * step;
}
/// <summary>
/// Apply accumulated mouse deltas (pixels since last frame). Positive deltaX
/// rotates the view to the right (decreases yaw), positive deltaY rotates
/// down (decreases pitch).
/// </summary>
public void Look(float deltaX, float deltaY)
{
Yaw -= deltaX * MouseSensitivity;
Pitch = Math.Clamp(Pitch - deltaY * MouseSensitivity, -PitchLimit, PitchLimit);
}
private Vector3 Forward()
{
float cp = MathF.Cos(Pitch);
return new Vector3(
cp * MathF.Cos(Yaw),
cp * MathF.Sin(Yaw),
MathF.Sin(Pitch));
}
}