Files
tilt/test/rink.mjs
T
2026-08-03 10:28:11 -05:00

78 lines
2.9 KiB
JavaScript

import { RINK, clampToRink, insideRink, randomIcePoint, rinkOutline, rinkPenetration } from '../shared/rink.js';
import { done, near, ok, section } from './harness.mjs';
section('penetration on the straights');
{
ok(insideRink(0, 0), 'centre ice is on the ice');
ok(!insideRink(RINK.halfX + 1, 0), 'past the end boards is not');
ok(!insideRink(0, RINK.halfZ + 1), 'past the side boards is not');
const p = rinkPenetration(0, RINK.halfZ + 0.5);
near(p.dist, 0.5, 1e-9, 'side board penetration');
near(p.nz, -1, 1e-9, 'side board normal points back to centre');
}
section('penetration in the corners');
{
// The corner arc centre, pushed out along the diagonal by exactly the radius,
// has to land on the boards.
const cx = RINK.halfX - RINK.cornerR;
const cz = RINK.halfZ - RINK.cornerR;
const d = RINK.cornerR / Math.SQRT2;
const p = rinkPenetration(cx + d, cz + d);
near(p.dist, 0, 1e-9, 'diagonal from the corner centre lands on the boards');
near(Math.hypot(p.nx, p.nz), 1, 1e-9, 'corner normal is unit length');
ok(p.nx < 0 && p.nz < 0, 'corner normal points inward');
// A point in the corner quadrant but inside the arc is on the ice, even
// though it is outside neither straight wall — this is the case a plain
// rectangle test gets wrong.
ok(insideRink(cx + 1, cz + 1), 'inside the corner arc is on the ice');
ok(!insideRink(RINK.halfX - 0.5, RINK.halfZ - 0.5), 'the clipped corner is off the ice');
}
section('radius is respected');
{
ok(!insideRink(0, RINK.halfZ - 0.2, 0.36), 'a body wider than its gap does not fit');
ok(insideRink(0, RINK.halfZ - 1, 0.36), 'the same body fits with room to spare');
}
section('clamping kills inward velocity');
{
const s = { x: 0, z: RINK.halfZ + 0.2, vx: 1, vz: 3 };
const hit = clampToRink(s, 0.36, 0);
ok(hit, 'a body past the boards reports a hit');
ok(insideRink(s.x, s.z, 0.36), 'and is put back on the ice');
near(s.vz, 0, 1e-9, 'velocity into the boards is removed');
near(s.vx, 1, 1e-9, 'velocity along them is kept');
const bouncy = { x: 0, z: RINK.halfZ + 0.2, vx: 0, vz: 4 };
clampToRink(bouncy, 0.36, 0.5);
near(bouncy.vz, -2, 1e-9, 'restitution reverses half the closing speed');
const clear = { x: 0, z: 0, vx: 5, vz: 0 };
ok(!clampToRink(clear, 0.36), 'centre ice is not clamped');
near(clear.vx, 5, 1e-9, 'and keeps its speed');
}
section('outline follows the boards');
{
const outline = rinkOutline(8);
ok(outline.length === 36, 'four arcs of nine points');
let maxOff = 0;
for (const p of outline) maxOff = Math.max(maxOff, Math.abs(rinkPenetration(p.x, p.z).dist));
near(maxOff, 0, 1e-9, 'every outline point sits exactly on the boards');
}
section('random points land on the ice');
{
let n = 0;
const rand = () => ((n = (n * 1103515245 + 12345) % 2147483648) / 2147483648);
for (let i = 0; i < 500; i++) {
const p = randomIcePoint(rand, 3);
ok(insideRink(p.x, p.z, 3), `waypoint ${i} is 3m clear of the boards`);
}
}
done('rink');