fix(render): Phase A8.F — camera sweep uses retail moverFlags 0x5c (PathClipped hard-stop)
Code review found the probe passed ObjectInfoState.None; retail's SmartBox::update_viewer calls init_object(player, 0x5c) = IsViewer|PathClipped|FreeRotate|PerfectClip (pseudo-C :92864). PathClipped makes the sweep hard-stop at first contact (TransitionTypes.cs:811) instead of edge-sliding around corners (which would re-trigger the A8.F camera-cell instability); IsViewer lets the eye pass through creatures, colliding only with world geometry. Resolves the spec's slide-vs-stop open question. Also reset CollideCamera in the Defaults_AreRetailValues baseline test (review: maintenance trap). Spec §5.1/§11.1 synced. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
376e2c3578
commit
fcea05f808
3 changed files with 27 additions and 11 deletions
|
|
@ -144,9 +144,13 @@ public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint sel
|
||||||
stepDownHeight: 0f, // no step-down / ground snap
|
stepDownHeight: 0f, // no step-down / ground snap
|
||||||
isOnGround: false, // no contact-plane / walkable semantics
|
isOnGround: false, // no contact-plane / walkable semantics
|
||||||
body: null, // no cross-frame contact-plane persistence
|
body: null, // no cross-frame contact-plane persistence
|
||||||
moverFlags: ObjectInfoState.None, // all targets collide; also keeps
|
// Retail init_object(player, 0x5c) = IsViewer|PathClipped|FreeRotate|
|
||||||
// camera sweeps out of the #98
|
// PerfectClip (pseudo-C :92864). PathClipped = hard-stop at first contact
|
||||||
// IsPlayer capture filter
|
// (the spring arm, not edge-slide); IsViewer = eye passes through creatures,
|
||||||
|
// colliding only with world geometry. Not IsPlayer → stays out of the #98
|
||||||
|
// capture filter.
|
||||||
|
moverFlags: ObjectInfoState.IsViewer | ObjectInfoState.PathClipped
|
||||||
|
| ObjectInfoState.FreeRotate | ObjectInfoState.PerfectClip,
|
||||||
movingEntityId: selfEntityId); // skip the player's own ShadowEntry
|
movingEntityId: selfEntityId); // skip the player's own ShadowEntry
|
||||||
return r.Position + zoff; // r.Position = sp.CheckPos (path pt); + zoff = eye
|
return r.Position + zoff; // r.Position = sp.CheckPos (path pt); + zoff = eye
|
||||||
}
|
}
|
||||||
|
|
@ -264,11 +268,13 @@ Compare `ACDREAM_CAMERA_COLLIDE=0` vs default to confirm the flag isolates the f
|
||||||
|
|
||||||
## 11. Open implementation questions (decide during the plan, not now)
|
## 11. Open implementation questions (decide during the plan, not now)
|
||||||
|
|
||||||
1. **Slide vs hard-stop.** Reusing `ResolveWithTransition` gives the player
|
1. **Slide vs hard-stop — RESOLVED (hard-stop).** Retail's `update_viewer` calls
|
||||||
path's edge-slide (the eye glides along a wall rather than jittering). Read
|
`init_object(player, 0x5c)`, and `0x5c` includes `PathClipped`, which makes the
|
||||||
retail's `find_valid_position` during implementation and confirm whether it
|
transition HARD-STOP at first contact (`TransitionTypes.cs:811`) rather than
|
||||||
slides or hard-stops; match it. Both keep the eye out of walls, so this does
|
edge-slide. The probe therefore passes
|
||||||
not change the architecture.
|
`IsViewer | PathClipped | FreeRotate | PerfectClip` (see §5.1). Hard-stop also
|
||||||
|
avoids the eye sliding around a corner into the next room, which would re-trigger
|
||||||
|
the A8.F camera-cell instability this fix targets.
|
||||||
2. **`sphereHeight: 0f` — RESOLVED.** `SpherePath.InitPath` with height 0 yields a
|
2. **`sphereHeight: 0f` — RESOLVED.** `SpherePath.InitPath` with height 0 yields a
|
||||||
single sphere (`NumSphere = 1`, `TransitionTypes.cs:534-537`). It also offsets
|
single sphere (`NumSphere = 1`, `TransitionTypes.cs:534-537`). It also offsets
|
||||||
sphere0's center to `pathPos + (0,0,radius)` (foot-capsule convention) whereas
|
sphere0's center to `pathPos + (0,0,radius)` (foot-capsule convention) whereas
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,17 @@ public sealed class PhysicsCameraCollisionProbe : ICameraCollisionProbe
|
||||||
stepDownHeight: 0f, // no step-down / ground snap
|
stepDownHeight: 0f, // no step-down / ground snap
|
||||||
isOnGround: false, // no contact-plane / walkable semantics
|
isOnGround: false, // no contact-plane / walkable semantics
|
||||||
body: null, // no cross-frame persistence
|
body: null, // no cross-frame persistence
|
||||||
moverFlags: ObjectInfoState.None, // all targets collide; also keeps
|
// Retail SmartBox::update_viewer calls init_object(player, 0x5c) =
|
||||||
// camera sweeps out of the #98
|
// IsViewer | PathClipped | FreeRotate | PerfectClip (acclient
|
||||||
// IsPlayer capture filter
|
// pseudo-C :92864; enum TransitionTypes.cs:24-33). PathClipped makes
|
||||||
|
// the sweep HARD-STOP at first contact (TransitionTypes.cs:811) — the
|
||||||
|
// spring-arm pull-in, not the player's edge-slide. IsViewer lets the
|
||||||
|
// eye pass through creatures, colliding only with world geometry
|
||||||
|
// (CollisionExemption.cs:83-85). FreeRotate/PerfectClip are no-ops in
|
||||||
|
// acdream today but set to match retail's exact value. NOT IsPlayer
|
||||||
|
// (0x100), so camera sweeps stay out of the #98 capture filter.
|
||||||
|
moverFlags: ObjectInfoState.IsViewer | ObjectInfoState.PathClipped
|
||||||
|
| ObjectInfoState.FreeRotate | ObjectInfoState.PerfectClip,
|
||||||
movingEntityId: selfEntityId); // skip the player's own ShadowEntry
|
movingEntityId: selfEntityId); // skip the player's own ShadowEntry
|
||||||
|
|
||||||
return FromSpherePath(r.Position, ViewerSphereRadius);
|
return FromSpherePath(r.Position, ViewerSphereRadius);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class CameraDiagnosticsTests
|
||||||
CameraDiagnostics.CameraAdjustmentSpeed = 40.0f;
|
CameraDiagnostics.CameraAdjustmentSpeed = 40.0f;
|
||||||
CameraDiagnostics.AlignToSlope = true;
|
CameraDiagnostics.AlignToSlope = true;
|
||||||
CameraDiagnostics.UseRetailChaseCamera = true;
|
CameraDiagnostics.UseRetailChaseCamera = true;
|
||||||
|
CameraDiagnostics.CollideCamera = true;
|
||||||
|
|
||||||
Assert.Equal(0.45f, CameraDiagnostics.TranslationStiffness);
|
Assert.Equal(0.45f, CameraDiagnostics.TranslationStiffness);
|
||||||
Assert.Equal(0.45f, CameraDiagnostics.RotationStiffness);
|
Assert.Equal(0.45f, CameraDiagnostics.RotationStiffness);
|
||||||
|
|
@ -31,6 +32,7 @@ public class CameraDiagnosticsTests
|
||||||
// legacy camera remains opt-in via the DebugPanel toggle until
|
// legacy camera remains opt-in via the DebugPanel toggle until
|
||||||
// the follow-up deletion commit.
|
// the follow-up deletion commit.
|
||||||
Assert.True(CameraDiagnostics.UseRetailChaseCamera);
|
Assert.True(CameraDiagnostics.UseRetailChaseCamera);
|
||||||
|
Assert.True(CameraDiagnostics.CollideCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue