/**
 * Derive the per-effect mass scaling factors from the body config. Computed
 * once at link time (mass is not expected to change at runtime); the system
 * caches the result on the runtime.
 *
 * Each scaling is mixed with coupling-strength ∈ [0, 1]: at 0 the ratio is
 * identity (mass becomes purely cosmetic, useful for prototypes); at 1 the
 * full kinematic relationship applies.
 *
 *   massRatio = mass / referenceMass
 *   coupled(r, k) = 1 + (r - 1) · k       // identity at k=0, full r at k=1
 *   jumpV0Scale       = 1 / √coupled(massRatio, k)        ∝ 1/√mass
 *   groundAccelScale  = 1 / coupled(massRatio, k)         ∝ 1/mass
 *   landingDipScale   =   coupled(massRatio, k)           ∝   mass
 *   exertionRiseScale =   coupled(massRatio, k)           ∝   mass
 *
 * @param {number} mass
 * @param {number} referenceMass
 * @param {number} couplingStrength  [0..1]
 * @returns {{
 *   massRatio: number,
 *   jumpV0Scale: number,
 *   groundAccelScale: number,
 *   landingDipScale: number,
 *   exertionRiseScale: number,
 * }}
 */
export function computeMassRatios(mass, referenceMass, couplingStrength) {
    const r = mass / Math.max(referenceMass, 1e-6);
    const k = Math.max(0, Math.min(1, couplingStrength));
    const coupled = 1 + (r - 1) * k;

    // Guard: at very low coupled values 1/coupled blows up. Clamp the
    // *minimum* coupled to avoid division blow-ups for tiny-mass exotic configs.
    const safeCoupled = Math.max(coupled, 0.1);

    return {
        massRatio: r,
        jumpV0Scale: 1 / Math.sqrt(safeCoupled),
        groundAccelScale: 1 / safeCoupled,
        landingDipScale: safeCoupled,
        exertionRiseScale: safeCoupled,
    };
}
