Merge goalie-tester worktree: save drill, stick plant, and jersey gear.
Bring over img2mesh puck machine, catch/block IK, damped stance blends, and mover-space paddle with hand-on-shaft IK. Keep main jersey skinned gear and underlayer paint, with the stick reparented to the mover.
This commit is contained in:
+104
-1
@@ -27,7 +27,7 @@ section('goalie is a skinned skeleton, not a capsule');
|
||||
ok(goalie.gear.trapper.parent === goalie.skelData.bones.handL, 'trapper is on the left hand');
|
||||
ok(goalie.gear.blocker.parent === goalie.skelData.bones.handR, 'blocker is on the right hand');
|
||||
ok(goalie.gear.mask.parent === goalie.skelData.bones.head, 'mask is on the head');
|
||||
ok(goalie.gear.stick.parent === goalie.skelData.bones.handR, 'paddle is in the blocker hand');
|
||||
ok(goalie.gear.stick.parent === goalie.mover, 'paddle is mover-planted (hand IKs to it)');
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
@@ -100,4 +100,107 @@ section('goalie drops low in the butterfly');
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
section('catch save IKs the trapper onto the puck');
|
||||
{
|
||||
const { goalie } = make();
|
||||
// Settle in ready at the crease.
|
||||
for (let i = 0; i < 30; i++) goalie.update(DT, { x: goalie.pos.x - 8, y: 0.5, z: 0 });
|
||||
const hand = new THREE.Vector3();
|
||||
goalie.skelData.bones.handL.getWorldPosition(hand);
|
||||
// Glove-side high puck (world −Z is goalie left when facing −X at end +1).
|
||||
// Kept inside a reachable arm envelope so the test measures blend, not clamp.
|
||||
const target = {
|
||||
x: goalie.pos.x - 0.18,
|
||||
y: 1.05,
|
||||
z: goalie.pos.z - 0.3,
|
||||
};
|
||||
const before = hand.distanceTo(target);
|
||||
goalie.playSave('catch', target);
|
||||
ok(goalie.animator.save?.kind === 'catch', 'save state is a catch');
|
||||
for (let i = 0; i < 40; i++) goalie.update(DT, target);
|
||||
goalie.skelData.bones.handL.getWorldPosition(hand);
|
||||
const after = hand.distanceTo(target);
|
||||
ok(after < before - 0.08, `trapper moves onto the puck (${before.toFixed(2)} → ${after.toFixed(2)})`);
|
||||
ok(after < 0.45, `trapper lands near the puck (err ${after.toFixed(2)})`);
|
||||
let bad = false;
|
||||
for (const b of goalie.skelData.list) {
|
||||
for (const e of b.matrixWorld.elements) if (!Number.isFinite(e)) bad = true;
|
||||
}
|
||||
ok(!bad, 'catch IK keeps bone matrices finite');
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
section('block save IKs the blocker onto the puck');
|
||||
{
|
||||
const { goalie } = make();
|
||||
for (let i = 0; i < 30; i++) goalie.update(DT, { x: goalie.pos.x - 8, y: 0.5, z: 0 });
|
||||
const hand = new THREE.Vector3();
|
||||
goalie.skelData.bones.handR.getWorldPosition(hand);
|
||||
// Blocker-side high puck (world +Z is goalie right when facing −X).
|
||||
const target = {
|
||||
x: goalie.pos.x - 0.18,
|
||||
y: 1.0,
|
||||
z: goalie.pos.z + 0.3,
|
||||
};
|
||||
const before = hand.distanceTo(target);
|
||||
goalie.playSave('block', target);
|
||||
ok(goalie.animator.save?.kind === 'block', 'save state is a block');
|
||||
for (let i = 0; i < 40; i++) goalie.update(DT, target);
|
||||
goalie.skelData.bones.handR.getWorldPosition(hand);
|
||||
const after = hand.distanceTo(target);
|
||||
ok(after < before - 0.08, `blocker moves onto the puck (${before.toFixed(2)} → ${after.toFixed(2)})`);
|
||||
ok(after < 0.5, `blocker lands near the puck (err ${after.toFixed(2)})`);
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
section('save IK clears after the hold');
|
||||
{
|
||||
const { goalie } = make();
|
||||
for (let i = 0; i < 20; i++) goalie.update(DT, { x: goalie.pos.x - 6, y: 0.5, z: 0 });
|
||||
goalie.playSave('catch', { x: goalie.pos.x - 0.2, y: 1.1, z: goalie.pos.z - 0.4 }, { duration: 0.25 });
|
||||
for (let i = 0; i < 45; i++) {
|
||||
goalie.update(DT, { x: goalie.pos.x - 6, y: 0.5, z: 0 });
|
||||
}
|
||||
ok(goalie.animator.save == null, 'save state expires after duration');
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
section('stick is a heavy plant the hand follows');
|
||||
{
|
||||
const { goalie } = make();
|
||||
for (let i = 0; i < 45; i++) goalie.update(DT, { x: goalie.pos.x - 10, y: 0.5, z: 0 });
|
||||
const stick = goalie.gear.stick;
|
||||
const hand = new THREE.Vector3();
|
||||
const grip = new THREE.Vector3();
|
||||
const measure = () => {
|
||||
grip.set(0, -0.05, 0.016).applyMatrix4(stick.matrixWorld);
|
||||
goalie.skelData.bones.handR.getWorldPosition(hand);
|
||||
return hand.distanceTo(grip);
|
||||
};
|
||||
// Grip is shaft-local; hand should sit on it after settle.
|
||||
let dist = measure();
|
||||
ok(dist < 0.08, `blocker hand holds the shaft (err ${dist.toFixed(2)})`);
|
||||
|
||||
// Stick should not whip when the torso shuffles — move laterally hard and
|
||||
// the paddle lags rather than snapping with the hand bone.
|
||||
const before = stick.position.clone();
|
||||
for (let i = 0; i < 8; i++) {
|
||||
goalie.update(DT, { x: goalie.pos.x - 4, y: 0.4, z: 1.2 + i * 0.05 });
|
||||
}
|
||||
const jump = stick.position.distanceTo(before);
|
||||
ok(jump < 0.12, `stick plant lags under shuffle (Δ ${jump.toFixed(3)})`);
|
||||
|
||||
// Block save: dominant hand stays on the stick (stick steers to the puck).
|
||||
const target = {
|
||||
x: goalie.pos.x - 0.2,
|
||||
y: 1.05,
|
||||
z: goalie.pos.z + 0.35,
|
||||
};
|
||||
goalie.playSave('block', target);
|
||||
for (let i = 0; i < 35; i++) goalie.update(DT, target);
|
||||
dist = measure();
|
||||
ok(dist < 0.1, `hand stays on shaft during block save (err ${dist.toFixed(2)})`);
|
||||
goalie.destroy();
|
||||
}
|
||||
|
||||
done('goalie');
|
||||
|
||||
Reference in New Issue
Block a user