animations

This commit is contained in:
ryanfitzpatrickio
2026-08-03 19:20:00 -05:00
parent fb1ebeed05
commit afd764e5e7
22 changed files with 2049 additions and 212 deletions
+107
View File
@@ -183,6 +183,113 @@ section('turning is harder at speed than at rest');
);
}
section('LT does not spin the body around');
{
// Facing +X, stick pulled toward X under LT — should retreat heels-first
// without yaw flipping to face the stick.
const s = createSkaterState(0, { x: 0, z: 0, yaw: Math.PI / 2 });
const startYaw = s.yaw;
run(s, 2.5, (st) => {
st.ix = -1;
st.iz = 0;
st.backskate = true;
});
const yawDrift = Math.abs(wrap(s.yaw - startYaw));
ok(yawDrift < 0.45, `facing holds under LT (yaw drift ${yawDrift.toFixed(2)} rad)`);
ok(s.bladeSpeed < -1.2, `stick back drives reverse (${s.bladeSpeed.toFixed(2)} m/s)`);
ok(s.x < -1.5, `and the body retreats (x ${s.x.toFixed(2)})`);
}
section('LT with stick forward is a crawl, not a rush');
{
const free = createSkaterState(0, START);
run(free, 5, forward);
const contain = createSkaterState(1, START);
run(contain, 5, (st) => {
forward(st);
st.backskate = true;
});
ok(
speedOf(free) > speedOf(contain) + 1.2,
`contain is much slower than free skate (${speedOf(contain).toFixed(2)} vs ${speedOf(free).toFixed(2)})`,
);
ok(speedOf(contain) <= SKATE.containForwardSpeed + 0.25, 'under the forward-contain ceiling');
ok(contain.bladeSpeed > 0, 'still facing the stick way, not flipped reverse');
}
section('LT reverse and lateral accelerate quickly for gap control');
{
// Reverse from a standstill under LT should get moving fast.
const back = createSkaterState(0, { x: 0, z: 0, yaw: Math.PI / 2 });
run(back, 0.45, (st) => {
st.ix = -1;
st.iz = 0;
st.backskate = true;
});
ok(back.bladeSpeed < -2.5, `quick reverse accel (${back.bladeSpeed.toFixed(2)} m/s in 0.45s)`);
ok(Math.abs(back.x) > 0.6, 'and covers ground retreating');
// Lateral shuffle under LT.
const side = createSkaterState(1, { x: 0, z: 0, yaw: Math.PI / 2 });
run(side, 0.45, (st) => {
st.ix = 0;
st.iz = 1; // body right when facing +X
st.backskate = true;
});
ok(Math.abs(side.z) > 0.7, `quick lateral under LT (z ${side.z.toFixed(2)})`);
ok(Math.abs(side.x) < Math.abs(side.z), 'mostly sideways relative to facing (+X)');
// Flip reverse → forward under LT (offense ↔ defense switch).
const flip = createSkaterState(2, { x: 0, z: 0, yaw: Math.PI / 2 });
run(flip, 0.8, (st) => {
st.ix = -1;
st.iz = 0;
st.backskate = true;
});
const rev = flip.bladeSpeed;
ok(rev < -2, 'was reverse');
run(flip, 0.35, (st) => {
st.ix = 1;
st.iz = 0;
st.backskate = true;
});
ok(flip.bladeSpeed > rev + 2.5, `snaps out of reverse toward forward (${flip.bladeSpeed.toFixed(2)} from ${rev.toFixed(2)})`);
}
section('LT stick neutral bleeds a rush toward neutral');
{
const s = createSkaterState(0, START);
run(s, 5, forward);
const entry = speedOf(s);
ok(entry > 3.5, 'at cruise');
// Hold LT, stick centred — sit in the gap instead of flying.
run(s, 1.2, (st) => {
st.ix = 0;
st.iz = 0;
st.backskate = true;
});
ok(speedOf(s) < entry * 0.55, `bled speed under LT (${speedOf(s).toFixed(2)} of ${entry.toFixed(2)})`);
ok(speedOf(s) > 0.15, 'but not a hard snowplow to zero');
const yawStill = Math.abs(wrap(s.yaw - Math.PI / 2));
ok(yawStill < 0.3, 'and did not spin to face the other way');
}
section('applyIntent accepts backskate');
{
const s = createSkaterState(0, START);
applyIntent(s, { ix: 0, iz: 1, backskate: 1 });
ok(s.backskate === true, 'backskate flag is boolean true');
applyIntent(s, { ix: 0, iz: 1, backskate: 0 });
ok(s.backskate === false, 'and clears');
}
function wrap(a) {
let x = a;
while (x > Math.PI) x -= Math.PI * 2;
while (x <= -Math.PI) x += Math.PI * 2;
return x;
}
section('nothing leaves the rink');
{
// Point skaters at the boards from centre ice and hold it for ten seconds.