{
  "id": "idle_bounce",
  "type": "procedural",
  "loop": true,
  "durationSeconds": 1.2,
  "intensity": 0.25,
  "priority": "idle",
  "transforms": [
    { "time": 0.0, "scale": [1.0, 1.0, 1.0], "positionOffset": [0.0, 0.0, 0.0], "rotationDegrees": [0, 0, 0] },
    { "time": 0.3, "scale": [0.97, 1.05, 0.97], "positionOffset": [0.0, 0.06, 0.0], "rotationDegrees": [0, 0, 0] },
    { "time": 0.6, "scale": [1.04, 0.94, 1.04], "positionOffset": [0.0, -0.03, 0.0], "rotationDegrees": [0, 0, 0] },
    { "time": 0.9, "scale": [0.98, 1.03, 0.98], "positionOffset": [0.0, 0.04, 0.0], "rotationDegrees": [0, 0, 0] },
    { "time": 1.2, "scale": [1.0, 1.0, 1.0], "positionOffset": [0.0, 0.0, 0.0], "rotationDegrees": [0, 0, 0] }
  ],
  "notes": "Loopable breathing bounce. Volume-preserving: when y>1, x and z dip below 1 and vice versa. Keep intensity low for a calm resting state. Use ease-in-out (cubic) interpolation between keys so the bob feels organic. Start/end keyframes are identical neutral for seamless looping. In Roblox scale via Model:ScaleTo or Motor6D C0; in Unity drive localScale; in Three.js drive mesh.scale and position.y. Multiply offsets by character height if scaling per-rig."
}
