[claude] Timmy slap / ragdoll physics — spring wobble + Pip startle (#43) #85
@@ -36,11 +36,12 @@ const COUNTER_RETORTS = [
|
||||
"YOU'LL REGRET THAT, MORTAL!",
|
||||
];
|
||||
|
||||
// Residual mini-spring for slight trembling post-fall (STAND only)
|
||||
const SPRING_STIFFNESS = 7.0;
|
||||
const SPRING_DAMPING = 0.80;
|
||||
const MAX_TILT_RAD = 0.12;
|
||||
const SLAP_IMPULSE = 0.18;
|
||||
// Spring physics for slap wobble (STAND only)
|
||||
const SPRING_STIFFNESS = 18.0;
|
||||
const SPRING_DAMPING = 4.5;
|
||||
const MAX_TILT_RAD = 0.55;
|
||||
const SLAP_IMPULSE = 2.8;
|
||||
const RAGDOLL_TILT_THRESHOLD = 0.42; // if tilt exceeds this, trigger full ragdoll fall
|
||||
|
||||
// ── Face emotion targets per internal state ───────────────────────────────────
|
||||
// lidScale: 0 = fully closed, 1 = wide open
|
||||
@@ -681,31 +682,54 @@ function _updateRagdoll(dt, t, bodyBob) {
|
||||
}
|
||||
|
||||
// ── applySlap — called by interaction.js on hit ───────────────────────────────
|
||||
// Applies an additive spring impulse so repeated slaps stack. If accumulated
|
||||
// tilt exceeds RAGDOLL_TILT_THRESHOLD the full ragdoll fall is triggered.
|
||||
export function applySlap(hitPoint) {
|
||||
if (!timmy) return;
|
||||
|
||||
// Ignore re-slap while already falling/down — wait until standing again
|
||||
const rd = timmy.rd;
|
||||
|
||||
// Ignore re-slap while already in ragdoll cycle — wait until standing
|
||||
if (rd.state !== RD_STAND) return;
|
||||
|
||||
// XZ direction from Timmy to hit point (fall away from impact)
|
||||
// XZ direction from Timmy to hit point (wobble/fall away from impact)
|
||||
const dx = hitPoint.x - TIMMY_POS.x;
|
||||
const dz = hitPoint.z - TIMMY_POS.z;
|
||||
const len = Math.sqrt(dx * dx + dz * dz) || 1;
|
||||
rd.fallDirX = dx / len;
|
||||
rd.fallDirZ = dz / len;
|
||||
const dirX = dx / len;
|
||||
const dirZ = dz / len;
|
||||
|
||||
// Start ragdoll fall
|
||||
rd.state = RD_FALL;
|
||||
rd.timer = 0;
|
||||
rd.fallAngle = 0;
|
||||
// Additive spring impulse — stacks with any existing wobble
|
||||
rd.slapVelocity.x += dirZ * SLAP_IMPULSE; // rotation.x driven by Z direction
|
||||
rd.slapVelocity.z += -dirX * SLAP_IMPULSE; // rotation.z driven by X direction
|
||||
|
||||
// Pip startle — maximum scatter
|
||||
timmy.pipStartleTimer = 5.0;
|
||||
timmy.pipStartleDir.x = (Math.random() - 0.5) * 8.0;
|
||||
timmy.pipStartleDir.z = (Math.random() - 0.5) * 8.0;
|
||||
// Check if accumulated tilt is large enough to trigger full ragdoll
|
||||
const tiltMag = Math.sqrt(rd.slapOffset.x * rd.slapOffset.x + rd.slapOffset.z * rd.slapOffset.z);
|
||||
const velMag = Math.sqrt(rd.slapVelocity.x * rd.slapVelocity.x + rd.slapVelocity.z * rd.slapVelocity.z);
|
||||
|
||||
// Crystal flash on impact
|
||||
if (tiltMag > RAGDOLL_TILT_THRESHOLD || velMag > SLAP_IMPULSE * 2.2) {
|
||||
// Enough stacked force — trigger full ragdoll fall
|
||||
rd.fallDirX = dirX;
|
||||
rd.fallDirZ = dirZ;
|
||||
rd.state = RD_FALL;
|
||||
rd.timer = 0;
|
||||
rd.fallAngle = 0;
|
||||
// Reset spring state so it's clean when returning to STAND
|
||||
rd.slapOffset.x = 0; rd.slapOffset.z = 0;
|
||||
rd.slapVelocity.x = 0; rd.slapVelocity.z = 0;
|
||||
|
||||
// Pip startle — maximum scatter on ragdoll
|
||||
timmy.pipStartleTimer = 5.0;
|
||||
timmy.pipStartleDir.x = (Math.random() - 0.5) * 8.0;
|
||||
timmy.pipStartleDir.z = (Math.random() - 0.5) * 8.0;
|
||||
} else {
|
||||
// Pip mild startle on wobble
|
||||
timmy.pipStartleTimer = Math.max(timmy.pipStartleTimer, 3.0);
|
||||
timmy.pipStartleDir.x = (Math.random() - 0.5) * 4.0;
|
||||
timmy.pipStartleDir.z = (Math.random() - 0.5) * 4.0;
|
||||
}
|
||||
|
||||
// Crystal flash on every hit
|
||||
timmy.hitFlashTimer = 0.5;
|
||||
|
||||
// Cartoonish SMACK sound
|
||||
|
||||
Reference in New Issue
Block a user