diff --git a/packages/web/src/Replay.svelte b/packages/web/src/Replay.svelte index e973830..88a4f51 100644 --- a/packages/web/src/Replay.svelte +++ b/packages/web/src/Replay.svelte @@ -253,12 +253,13 @@ } return null; })(); + // A leap the legs cannot explain (first frame, teleport, the return + // from a cutaway) is a CUT, and a cut is not a stride: the direction + // of travel means nothing at the far end. + const willCut = !camReady || (dist > 1.6 && !hurled); let targetFacing = camF; let aimed = false; - // A stride turns the eyes along it — but only a REAL stride: on the - // reel's first run the camera sits at its uninitialized origin, and - // that phantom distance must not pick the opening shot's direction. - if (camReady && dist > 0.05 && !hurled) { targetFacing = Math.atan2(dy, dx); aimed = true; } + if (camReady && !willCut && dist > 0.05 && !hurled) { targetFacing = Math.atan2(dy, dx); aimed = true; } else if (aim) { const ax = aim.x + 0.5, ay = aim.y + 0.5; const toAim = Math.hypot(ax - tx, ay - ty); @@ -316,7 +317,10 @@ for (const t of v.treasures) if (t.position && !t.carriedBy) score += bonus(t.position, 3); return score; }; - const staring = castRay(v, tx, ty, targetFacing).dist < (atEnd ? 1.2 : 0.8); + // A cut lands with whatever stale facing it carried, so a cut with + // nothing aimed always re-picks its view from the new position. + const staring = willCut || + castRay(v, tx, ty, targetFacing).dist < (atEnd ? 1.2 : 0.8); if (staring) { const current = viewScore(targetFacing); let bestA = targetFacing, bestScore = current; @@ -329,7 +333,7 @@ targetFacing = bestA; } } - if (!camReady || (dist > 1.6 && !hurled)) { + if (willCut) { // First frame, or a leap the legs cannot explain: cut. cam.x = tx; cam.y = ty; cam.facing = targetFacing; camReady = true;