The hook turns as hard as the geometry needs, or not at all
A slice hooking onto a body two metres to the side flew a closed loop around it forever: 285 frames of the same circuit, never arriving, never falling. A constant turn rate has a constant turn radius (v/w = 2 m), and anything inside that circle cannot be reached however long the ball steers — while the hook pins vy at 0, so nothing else ends it either. A crowd is nothing but close bodies, which made the one place this weapon should shine the one place it hung. The rate is no longer a constant. An arc leaving along the current heading and passing through a body at distance d, bearing a, has radius d/(2 sin a), so the ball needs 2 v sin(a) / d — solved every frame, because the body is walking while the ball travels. sliceTurn stays as the FLOOR, which is what it was always really for: how wide the curl looks when there is room. Turning faster than needed is safe, the aim being re-solved each frame. Two things the maths cannot cover, so they are ruled out instead: a body behind the ball, which no tangent arc reaches at any rate (sliceArc), and a target that walks out of reach mid-hook (sliceHookT, a hard 0.7 s licence to steer). An endless orbit is now impossible by construction rather than by the geometry happening to work out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
99d84b2c9d
commit
e11ad30a63
3 files changed
+42
-11
No files matched your search
+13
-2
@@ -1581,12 +1581,23 @@ export function update(dt) {
|
||||
// 1 px, so the ball stays the bright thing and the line reads as the cut, not as a second ball.
|
||||
if (ball.hook) {
|
||||
const dx = ball.hook.x - ball.x, dz = ball.hook.z - ball.z;
|
||||
if (dx * dx + dz * dz < 0.09) ball.hook = null; // arrived (or it moved) — gravity has it back
|
||||
ball.hook.t -= dt;
|
||||
// arrived, or out of time. The timer is not a polish detail: without it a ball that cannot
|
||||
// close on a walking body steers forever, and with vy pinned at 0 nothing else ever ends it.
|
||||
if (dx * dx + dz * dz < 0.09 || ball.hook.t <= 0) ball.hook = null; // gravity has it back
|
||||
else {
|
||||
const sp = Math.hypot(ball.vx, ball.vz) || 1;
|
||||
const cur = Math.atan2(ball.vx, ball.vz), want = Math.atan2(dx, dz);
|
||||
const da = ((want - cur + Math.PI * 3) % (Math.PI * 2)) - Math.PI; // shortest way round
|
||||
const a = cur + Math.sign(da) * Math.min(Math.abs(da), TUNE.sliceTurn * dt);
|
||||
// THE RATE COMES FROM THE GEOMETRY. An arc that starts along the current heading and passes
|
||||
// through a body at distance d, bearing da, has radius d / (2 sin da) — so the ball must be
|
||||
// able to turn at 2 v sin(da) / d or it can never arrive, however long it steers. sliceTurn
|
||||
// is only the floor (turning FASTER than needed is safe: the aim is re-solved every frame,
|
||||
// so it simply finishes the turn early and flies straight in). Solved live rather than
|
||||
// frozen at the kill, because the body it is hooking to is walking while the ball travels.
|
||||
const d = Math.max(0.6, Math.hypot(dx, dz)); // floored: d -> 0 would demand an infinite rate
|
||||
const w = Math.max(TUNE.sliceTurn, 2 * sp * Math.abs(Math.sin(da)) / d);
|
||||
const a = cur + Math.sign(da) * Math.min(Math.abs(da), w * dt);
|
||||
ball.vx = Math.sin(a) * sp; ball.vz = Math.cos(a) * sp; ball.vy = 0;
|
||||
parts.push({ x: ball.x, y: ball.y, z: ball.z, vx: 0, vy: 0, vz: 0,
|
||||
t: 0.28, fine: true, spark: '#f2f2e6' });
|
||||
|
||||
Reference in new issue
Block a user