Hold on the wreck before moving the player

Cutting straight from the crash to a base three hundred metres away
read as a bug rather than as a death, and the one moment in a campaign
worth actually looking at was the one moment the player was not allowed
to see it.

The run now ends five seconds after the car does. Control goes; the
camera leaves the bumper and climbs away to sixty metres, turning
slowly, until the street, whatever was hit and whoever is standing
around watching are all in frame. The point of the shot is not the car
- by then the car is a fact - it is *where* this happened, which is the
thing you have to drive back out to.

The world keeps running underneath it. A death cam over a frozen world
is a screenshot, not a moment. What stops is the driver: no throttle,
no board, no overhaul, and no quietly finishing the job by virtue of
having stopped moving on top of the target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dejvino
2026-08-09 07:57:11 +02:00
co-authored by Claude Opus 5
parent d9cdca70d5
commit 563ee66dcd
2 changed files with 132 additions and 20 deletions
+44 -2
View File
@@ -286,10 +286,52 @@ const camTarget = new THREE.Vector3();
const camDesired = new THREE.Vector3();
const CHASE_OFFSET = new THREE.Vector3(0, 3.4, -8.5);
/** Smoothed chase camera. Frame-rate independent damping. */
export function updateCamera(view: SceneView, dt: number, speed: number): void {
/**
* Where the camera starts and ends up while pulling off a wreck.
*
* It leaves from just behind the car and climbs away until the whole scene is
* below it: the street, whatever was hit, and whoever happens to be standing
* around watching. The point is not the car — by then the car is a fact — it is
* *where* this happened, which is the thing you have to drive back out to.
*/
const WAKE_FROM = { radius: 9, height: 3.5 };
const WAKE_TO = { radius: 26, height: 62 };
/** Smoothstep. Eases both ends, so the pull-away neither jerks nor drifts. */
const ease = (t: number) => t * t * (3 - 2 * t);
/**
* Smoothed chase camera. Frame-rate independent damping.
*
* @param wake When set, the camera stops chasing and climbs slowly away from
* the car, looking down at where the run ended. Used after the car is written
* off: a hard cut from a crash to a base three hundred metres away reads as a
* bug rather than as a death. `angle` turns the view a little as it goes so
* the shot is not a straight retreat; `progress` runs 0 to 1 over the hold.
*/
export function updateCamera(
view: SceneView,
dt: number,
speed: number,
wake: { angle: number; progress: number } | null = null,
): void {
const { camera, car } = view;
if (wake) {
const t = ease(Math.min(1, Math.max(0, wake.progress)));
const radius = WAKE_FROM.radius + (WAKE_TO.radius - WAKE_FROM.radius) * t;
const height = WAKE_FROM.height + (WAKE_TO.height - WAKE_FROM.height) * t;
camDesired
.set(Math.sin(wake.angle) * radius, height, Math.cos(wake.angle) * radius)
.add(car.position);
// Barely damped: the easing above already shapes the move, and heavy
// smoothing on top of it turns the climb into a lag.
camera.position.lerp(camDesired, 1 - Math.exp(-6 * dt));
camTarget.copy(car.position);
camera.lookAt(camTarget);
return;
}
camDesired.copy(CHASE_OFFSET);
// Pull back a little at speed for a sense of pace.
camDesired.z -= Math.min(Math.abs(speed) * 0.09, 3);