Skip to main content
Glama
chrishayuk

Physics MCP Server

by chrishayuk

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
RAPIER_TIMEOUTNoRequest timeout for the Rapier service in seconds.30.0
PHYSICS_PROVIDERNoProvider selection: 'analytic' for built-in formula-based calculations or 'rapier' for rigid-body simulations requiring an external service.analytic
RAPIER_MAX_RETRIESNoMaximum number of retries for Rapier service requests.3
RAPIER_RETRY_DELAYNoDelay between retries for Rapier service requests in seconds.1.0
RAPIER_SERVICE_URLNoThe URL of the Rapier physics service. Defaults to the public service at https://rapier.chukai.io or http://localhost:9000 if running locally.https://rapier.chukai.io

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": true
}
resources
{
  "subscribe": false,
  "listChanged": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
calculate_projectile_motion

Calculate projectile motion trajectory using kinematic equations.

Computes the complete trajectory of a projectile launched at an angle,
including maximum height, range, time of flight, and sample trajectory points.
Perfect for ballistics, sports analysis, or educational demonstrations.

Args:
    initial_velocity: Initial velocity in meters per second (m/s). Must be positive.
    angle_degrees: Launch angle in degrees from horizontal (0-90).
        0° = horizontal, 45° = maximum range, 90° = straight up
    initial_height: Initial height above ground in meters. Default 0.0 (ground level).
    gravity: Gravitational acceleration in m/s². Default 9.81 (Earth surface).
        Use 1.62 for Moon, 3.71 for Mars, etc.

Returns:
    ProjectileMotionResponse containing:
        - max_height: Maximum height reached (meters)
        - range: Horizontal distance traveled (meters)
        - time_of_flight: Total time in air (seconds)
        - trajectory_points: List of [x, y] sample points for plotting

Tips for LLMs:
    - 45° gives maximum range on flat ground (no air resistance)
    - For R3F visualization: convert trajectory_points to 3D by adding z=0
    - trajectory_points are evenly spaced in time (50 samples)
    - Air resistance is NOT modeled - this is ideal ballistic motion
    - Use for: cannon balls, baseballs, basketball shots, water fountains

Example:
    # Calculate trajectory of a cannonball fired at 50 m/s at 30°
    result = await calculate_projectile_motion(
        initial_velocity=50.0,
        angle_degrees=30.0,
        initial_height=2.0
    )
    print(f"Range: {result.range:.1f}m, Max height: {result.max_height:.1f}m")
check_collision

Check if two moving spherical objects will collide.

Predicts whether two moving spheres will collide within a time window,
and if so, calculates when and where the collision occurs. Uses analytic
relative motion to find exact collision time (if any).

Args:
    body1_position: Position of first object [x, y, z] in meters
    body1_velocity: Velocity of first object [x, y, z] in m/s
    body1_radius: Radius of first object in meters (must be positive)
    body2_position: Position of second object [x, y, z] in meters
    body2_velocity: Velocity of second object [x, y, z] in m/s
    body2_radius: Radius of second object in meters (must be positive)
    max_time: Maximum time to check in seconds. Default 10.0.

Returns:
    CollisionCheckResponse containing:
        - will_collide: True if collision will occur
        - collision_time: Time until collision in seconds (if collision occurs)
        - collision_point: Approximate collision location [x, y, z] (if collision occurs)
        - impact_speed: Relative velocity at impact in m/s (if collision occurs)
        - closest_approach_distance: Minimum distance between objects
        - closest_approach_time: Time of closest approach

Tips for LLMs:
    - Objects are modeled as spheres (point masses with radius)
    - Collision detection is exact for constant velocity motion
    - Returns earliest collision time if multiple intersections
    - If no collision, check closest_approach_distance to see how close they get
    - Use for: asteroid tracking, car crash prediction, sports ball interactions
    - For complex shapes or forces, use create_simulation instead

Example:
    # Check if two cars will collide
    result = await check_collision(
        body1_position=[0.0, 0.0, 0.0],
        body1_velocity=[10.0, 0.0, 0.0],
        body1_radius=2.0,
        body2_position=[50.0, 1.0, 0.0],
        body2_velocity=[-8.0, 0.0, 0.0],
        body2_radius=2.0
    )
    if result.will_collide:
        print(f"Collision in {result.collision_time:.2f} seconds at {result.impact_speed:.1f} m/s")
calculate_force

Calculate force from mass and acceleration using Newton's Second Law (F = ma).

Computes the force vector required to produce a given acceleration on a mass.
Fundamental for dynamics, engineering, and understanding motion.

Args:
    mass: Mass in kilograms (must be positive)
    acceleration_x: X component of acceleration in m/s²
    acceleration_y: Y component of acceleration in m/s²
    acceleration_z: Z component of acceleration in m/s²

Returns:
    ForceCalculationResponse containing:
        - force: Force vector [x, y, z] in Newtons
        - magnitude: Force magnitude in Newtons

Tips for LLMs:
    - 1 Newton = force to accelerate 1 kg at 1 m/s²
    - On Earth, weight force = mass × 9.81 N (vertical)
    - Use magnitude to compare total force regardless of direction
    - Common accelerations: car braking ~10 m/s², elevator ~2 m/s²

Example:
    # Force to accelerate a 1500kg car at 3 m/s² forward
    result = await calculate_force(
        mass=1500.0,
        acceleration_x=3.0,
        acceleration_y=0.0,
        acceleration_z=0.0
    )
    print(f"Required force: {result.magnitude:.0f} N")
calculate_kinetic_energy

Calculate kinetic energy from mass and velocity (KE = ½mv²).

Computes the energy of motion for a moving object. Energy is scalar
(direction doesn't matter, only speed). Useful for collision analysis,
vehicle safety, and understanding energy transfer.

Args:
    mass: Mass in kilograms (must be positive)
    velocity_x: X component of velocity in m/s
    velocity_y: Y component of velocity in m/s
    velocity_z: Z component of velocity in m/s

Returns:
    KineticEnergyResponse containing:
        - kinetic_energy: Energy in Joules (J)
        - speed: Velocity magnitude in m/s

Tips for LLMs:
    - 1 Joule = 1 kg⋅m²/s² = energy to lift 102g by 1m on Earth
    - Kinetic energy doubles mass → doubles energy, doubles speed → 4× energy
    - Car at highway speed (~30 m/s, 1500 kg) ≈ 675,000 J
    - Use to compare impact severity or stopping distances

Example:
    # Energy of a 0.145kg baseball at 40 m/s
    result = await calculate_kinetic_energy(
        mass=0.145,
        velocity_x=40.0,
        velocity_y=0.0,
        velocity_z=0.0
    )
    print(f"Kinetic energy: {result.kinetic_energy:.1f} J")
calculate_momentum

Calculate momentum from mass and velocity (p = mv).

Computes the momentum vector, which represents "quantity of motion."
Momentum is conserved in collisions, making it crucial for analyzing
impacts, explosions, and rocket propulsion.

Args:
    mass: Mass in kilograms (must be positive)
    velocity_x: X component of velocity in m/s
    velocity_y: Y component of velocity in m/s
    velocity_z: Z component of velocity in m/s

Returns:
    MomentumResponse containing:
        - momentum: Momentum vector [x, y, z] in kg⋅m/s
        - magnitude: Momentum magnitude in kg⋅m/s

Tips for LLMs:
    - Momentum is a vector (has direction), unlike kinetic energy
    - Total momentum before collision = total momentum after (conservation)
    - Large mass × small velocity can equal small mass × large velocity
    - Use to analyze: collisions, recoil, rocket thrust

Example:
    # Momentum of a 70kg person running at 5 m/s
    result = await calculate_momentum(
        mass=70.0,
        velocity_x=5.0,
        velocity_y=0.0,
        velocity_z=0.0
    )
    print(f"Momentum: {result.magnitude:.1f} kg⋅m/s")
calculate_potential_energy

Calculate gravitational potential energy.

Computes PE = mgh (mass × gravity × height).
Also returns the equivalent velocity if the object falls from that height.

Args:
    mass: Object mass in kilograms
    height: Height above reference point in meters
    gravity: Gravitational acceleration in m/s² (default 9.81 for Earth)

Returns:
    Dict containing:
        - potential_energy: PE in Joules
        - equivalent_kinetic_velocity: Speed if dropped from height (m/s)

Example - Object at 10m height:
    result = await calculate_potential_energy(mass=2.0, height=10.0)
    # PE = 196.2 J
    # Velocity if dropped = 14.0 m/s
calculate_work_power

Calculate work done by a force and optionally power.

Work is the dot product: W = F · d
Power (if time given): P = W / t

Args:
    force: Force vector [x, y, z] in Newtons (or JSON string)
    displacement: Displacement vector [x, y, z] in meters (or JSON string)
    time: Time taken in seconds (optional, for power calculation)

Returns:
    Dict containing:
        - work: Work done in Joules
        - power: Power in Watts (if time provided, else None)

Example - Pushing box 5m with 100N force:
    result = await calculate_work_power(
        force=[100, 0, 0],
        displacement=[5, 0, 0],
        time=10.0
    )
    # Work = 500 J, Power = 50 W
calculate_elastic_collision

Calculate final velocities after a 1D elastic collision.

Uses conservation of momentum and energy to solve for final velocities.
Assumes perfectly elastic collision (no energy loss).

Args:
    mass1: Mass of first object in kg
    velocity1: Initial velocity of first object in m/s (1D)
    mass2: Mass of second object in kg
    velocity2: Initial velocity of second object in m/s (1D)

Returns:
    Dict containing:
        - final_velocity1: Final velocity of object 1 in m/s
        - final_velocity2: Final velocity of object 2 in m/s
        - initial_kinetic_energy: Total KE before (J)
        - final_kinetic_energy: Total KE after (J) - should equal initial
        - initial_momentum: Total momentum before (kg⋅m/s)
        - final_momentum: Total momentum after (kg⋅m/s) - should equal initial

Example - Pool ball collision:
    result = await calculate_elastic_collision(
        mass1=0.17,      # kg (pool ball)
        velocity1=2.0,   # m/s (moving right)
        mass2=0.17,      # kg (pool ball)
        velocity2=0.0    # m/s (stationary)
    )
    # Result: ball 1 stops, ball 2 moves at 2.0 m/s
calculate_centripetal_force

Calculate centripetal force: F_c = m v² / r.

Force required to keep an object moving in a circle.
Always points toward the center of the circular path.

Args:
    mass: Mass in kg
    velocity: Speed (velocity magnitude) in m/s
    radius: Radius of circular path in meters

Returns:
    Dict containing:
        - centripetal_force: F_c in Newtons
        - centripetal_acceleration: a_c in m/s²

Tips for LLMs:
    - Not a new force - it's the net inward force (tension, friction, gravity)
    - Faster speed → much more force needed (v² relationship)
    - Tighter turn → more force needed
    - Use for: car turns, satellite orbits, centrifuges

Example - Car turning:
    result = await calculate_centripetal_force(
        mass=1500,  # kg
        velocity=20,  # m/s (72 km/h)
        radius=50  # meter turn radius
    )
    # F_c = 12000 N (provided by friction between tires and road)
calculate_orbital_period

Calculate orbital period: T = 2π√(r³/GM).

Kepler's Third Law for circular orbits. Period depends on orbital
radius and central body mass.

Args:
    orbital_radius: Orbital radius in meters (from center of central body)
    central_mass: Mass of central body in kg
    gravitational_constant: G in m³/(kg⋅s²) (default 6.674e-11)

Returns:
    Dict containing:
        - period: Orbital period in seconds
        - orbital_velocity: v in m/s
        - period_hours: Period in hours (for convenience)
        - period_days: Period in days (for convenience)

Tips for LLMs:
    - Higher orbit → longer period
    - More massive central body → shorter period
    - Earth: M = 5.972e24 kg, R = 6.371e6 m
    - Moon orbit: r ≈ 384,400 km, T ≈ 27.3 days
    - ISS orbit: r ≈ 6,771 km (altitude 400 km), T ≈ 90 minutes

Example - ISS orbit:
    result = await calculate_orbital_period(
        orbital_radius=6.771e6,  # meters
        central_mass=5.972e24  # Earth mass (kg)
    )
    # T ≈ 5,558 seconds ≈ 92.6 minutes
calculate_banking_angle

Calculate ideal banking angle: θ = arctan(v² / (rg)).

For a banked curve, the ideal angle where no friction is needed
to maintain the turn at a given speed.

Args:
    velocity: Speed in m/s
    radius: Turn radius in meters
    gravity: Gravitational acceleration in m/s² (default 9.81)

Returns:
    Dict containing:
        - angle_radians: Banking angle in radians
        - angle_degrees: Banking angle in degrees

Tips for LLMs:
    - Faster speed → steeper banking angle
    - Tighter turn → steeper banking angle
    - NASCAR tracks banked ~30° for high-speed turns
    - At ideal angle, normal force provides all centripetal force

Example - Highway exit ramp:
    result = await calculate_banking_angle(
        velocity=25,  # m/s (90 km/h)
        radius=100  # meter radius turn
    )
    # θ ≈ 32.5°
calculate_escape_velocity

Calculate escape velocity: v_escape = √(2GM/r).

Minimum speed needed to escape a celestial body's gravitational pull.
Independent of the escaping object's mass.

Args:
    mass: Mass of celestial body in kg
    radius: Radius of celestial body in meters
    gravitational_constant: G in m³/(kg⋅s²) (default 6.674e-11)

Returns:
    Dict containing:
        - escape_velocity: v_escape in m/s
        - escape_velocity_kmh: v_escape in km/h (for convenience)

Tips for LLMs:
    - Earth: v_escape ≈ 11,200 m/s (40,320 km/h)
    - Moon: v_escape ≈ 2,380 m/s
    - Sun: v_escape ≈ 617,500 m/s
    - Independent of escape direction or mass of escaping object

Example - Earth escape velocity:
    result = await calculate_escape_velocity(
        mass=5.972e24,  # Earth mass (kg)
        radius=6.371e6  # Earth radius (meters)
    )
    # v_escape ≈ 11,186 m/s
analyze_circular_orbit

Analyze circular orbit at given altitude above planet surface.

Comprehensive orbital analysis combining period, velocity, and acceleration.

Args:
    altitude: Altitude above surface in meters
    planet_mass: Planet mass in kg
    planet_radius: Planet radius in meters
    gravitational_constant: G in m³/(kg⋅s²) (default 6.674e-11)

Returns:
    Dict containing:
        - orbital_radius: r from planet center in meters
        - orbital_velocity: v in m/s
        - period_seconds: Orbital period in seconds
        - period_minutes: Orbital period in minutes
        - centripetal_acceleration: a_c in m/s²

Example - LEO satellite at 400km altitude:
    result = await analyze_circular_orbit(
        altitude=400000,  # 400 km
        planet_mass=5.972e24,  # Earth
        planet_radius=6.371e6  # Earth
    )
    # v ≈ 7,670 m/s, T ≈ 92.6 min
calculate_inelastic_collision_3d

Calculate 3D collision with coefficient of restitution.

Models realistic collisions where some kinetic energy is lost.
Coefficient of restitution (e) determines how much energy is retained.

Args:
    mass1: Mass of object 1 in kg
    velocity1: Velocity of object 1 [x, y, z] in m/s (or JSON string)
    mass2: Mass of object 2 in kg
    velocity2: Velocity of object 2 [x, y, z] in m/s (or JSON string)
    coefficient_of_restitution: e (0.0 = perfectly inelastic, 1.0 = perfectly elastic)

Returns:
    Dict containing:
        - final_velocity1: Final velocity [x, y, z] in m/s
        - final_velocity2: Final velocity [x, y, z] in m/s
        - initial_momentum: Total initial momentum [x, y, z]
        - final_momentum: Total final momentum [x, y, z]
        - initial_kinetic_energy: Total initial KE in Joules
        - final_kinetic_energy: Total final KE in Joules
        - energy_loss: Energy lost in Joules
        - energy_loss_percent: % of energy lost

Coefficient of restitution values:
    - e = 0.0: Perfectly inelastic (clay, putty) - objects stick
    - e = 0.5: Very inelastic (wet clay)
    - e = 0.7: Moderately elastic (basketball)
    - e = 0.9: Highly elastic (Super Ball)
    - e = 1.0: Perfectly elastic (ideal, no energy loss)

Tips for LLMs:
    - Momentum is always conserved (regardless of e)
    - Energy lost = (1 - e²) × initial KE in center-of-mass frame
    - Use e=1.0 for billiard balls, e=0.0 for car crashes

Example - Car crash:
    result = await calculate_inelastic_collision_3d(
        mass1=1500,  # kg
        velocity1=[20, 0, 0],  # 20 m/s east
        mass2=1200,  # kg
        velocity2=[-15, 0, 0],  # 15 m/s west
        coefficient_of_restitution=0.1  # very inelastic
    )
    # Massive energy loss, objects nearly stick together
calculate_elastic_collision_3d

Calculate 3D elastic collision (perfect energy conservation).

Special case of collision where no kinetic energy is lost (e = 1.0).
Both momentum and energy are conserved.

Args:
    mass1: Mass of object 1 in kg
    velocity1: Velocity of object 1 [x, y, z] in m/s (or JSON string)
    mass2: Mass of object 2 in kg
    velocity2: Velocity of object 2 [x, y, z] in m/s (or JSON string)

Returns:
    Dict containing:
        - final_velocity1: Final velocity [x, y, z] in m/s
        - final_velocity2: Final velocity [x, y, z] in m/s
        - initial_momentum: Total momentum [x, y, z]
        - final_momentum: Total momentum [x, y, z]
        - initial_kinetic_energy: Total KE in Joules
        - final_kinetic_energy: Total KE in Joules

Tips for LLMs:
    - Ideal approximation for billiard balls, Newton's cradle
    - Both momentum and energy conserved
    - Equal masses + head-on → velocities exchange
    - Use for educational examples, idealized systems

Example - Pool balls:
    result = await calculate_elastic_collision_3d(
        mass1=0.17,  # kg (pool ball)
        velocity1=[2, 0, 0],  # 2 m/s
        mass2=0.17,  # kg
        velocity2=[0, 0, 0]  # stationary
    )
    # Result: ball 1 stops, ball 2 moves at 2 m/s
check_energy_conservation

Verify conservation of energy in a physics process.

Checks whether total mechanical energy is conserved (or correctly dissipated).
Useful for validating simulation results and understanding energy transfer.

Args:
    initial_kinetic_energy: Initial KE in Joules
    final_kinetic_energy: Final KE in Joules
    initial_potential_energy: Initial PE in Joules
    final_potential_energy: Final PE in Joules
    expected_energy_loss: Expected energy loss (from friction, etc.) in Joules
    tolerance: Tolerance for conservation check (fraction, default 0.01 = 1%)

Returns:
    Dict containing:
        - initial_total_energy: Initial total energy in Joules
        - final_total_energy: Final total energy in Joules
        - energy_difference: Energy difference in Joules
        - energy_difference_percent: % difference
        - is_conserved: Whether energy is conserved within tolerance
        - expected_loss: Expected energy loss in Joules
        - actual_loss: Actual energy loss in Joules

Tips for LLMs:
    - In isolated systems, total energy is conserved
    - With friction/damping, expect energy loss
    - Small numerical errors are normal in simulations
    - Use to validate simulation accuracy

Example - Bouncing ball with energy loss:
    result = await check_energy_conservation(
        initial_kinetic_energy=0,
        final_kinetic_energy=0,
        initial_potential_energy=10,  # J (at 1m height)
        final_potential_energy=6.4,  # J (bounced to 0.64m)
        expected_energy_loss=3.6,  # 36% loss (e=0.8)
        tolerance=0.01
    )
check_momentum_conservation

Verify conservation of momentum.

Checks whether total momentum is conserved in a collision or interaction.
Momentum should be conserved in isolated systems (no external forces).

Args:
    initial_momentum: Initial total momentum [x, y, z] in kg⋅m/s (or JSON string)
    final_momentum: Final total momentum [x, y, z] in kg⋅m/s (or JSON string)
    tolerance: Tolerance for conservation check (fraction, default 0.01 = 1%)

Returns:
    Dict containing:
        - initial_momentum_magnitude: Initial |p| in kg⋅m/s
        - final_momentum_magnitude: Final |p| in kg⋅m/s
        - momentum_difference: Difference [x, y, z]
        - momentum_difference_magnitude: |Δp|
        - momentum_difference_percent: % difference
        - is_conserved: Whether momentum is conserved within tolerance

Tips for LLMs:
    - Momentum is ALWAYS conserved in isolated systems
    - Vector quantity - direction matters
    - Use to validate collision calculations
    - External forces (friction, etc.) can change total momentum

Example - Collision verification:
    result = await check_momentum_conservation(
        initial_momentum=[3000, 0, 0],  # kg⋅m/s
        final_momentum=[2995, 5, 0],  # slightly off
        tolerance=0.01
    )
check_angular_momentum_conservation

Verify conservation of angular momentum.

Checks whether total angular momentum is conserved. Angular momentum
is conserved when no external torques act on the system.

Args:
    initial_angular_momentum: Initial L [x, y, z] in kg⋅m²/s (or JSON string)
    final_angular_momentum: Final L [x, y, z] in kg⋅m²/s (or JSON string)
    tolerance: Tolerance (fraction, default 0.01 = 1%)

Returns:
    Dict containing:
        - initial_L_magnitude: Initial |L| in kg⋅m²/s
        - final_L_magnitude: Final |L| in kg⋅m²/s
        - L_difference: Difference [x, y, z]
        - L_difference_magnitude: |ΔL|
        - L_difference_percent: % difference
        - is_conserved: Whether L is conserved within tolerance

Tips for LLMs:
    - Conserved when no external torques (isolated rotation)
    - Ice skater spinning: pull arms in → I decreases → ω increases (L constant)
    - Gyroscope: resists changes to L direction
    - Planets orbiting: L conserved → elliptical orbits

Example - Figure skater:
    # Arms extended → Arms pulled in
    result = await check_angular_momentum_conservation(
        initial_angular_momentum=[0, 15, 0],  # kg⋅m²/s
        final_angular_momentum=[0, 15.05, 0],
        tolerance=0.01
    )
track_energy_dissipation

Track energy dissipation over a trajectory.

Analyzes how energy changes over time in a recorded trajectory.
Useful for understanding damping, bounces, and energy loss mechanisms.

Args:
    trajectory_data: Trajectory data dict with 'frames' field
    mass: Object mass in kg
    gravity: Gravitational acceleration in m/s² (default 9.81)
    reference_height: Reference height for PE in meters (default 0.0)

Returns:
    Dict containing:
        - frames: Energy data for each frame (time, KE, PE, total E)
        - initial_total_energy: Initial total energy in Joules
        - final_total_energy: Final total energy in Joules
        - total_energy_loss: Total energy dissipated in Joules
        - total_energy_loss_percent: % of energy lost
        - average_power_dissipated: Average power in Watts (J/s)

Tips for LLMs:
    - Use after record_trajectory or record_trajectory_with_events
    - Visualize energy vs time to see where energy is lost
    - Identifies bounces, friction effects, air resistance
    - Power = rate of energy dissipation

Example - Bouncing ball energy analysis:
    traj = await record_trajectory_with_events(sim_id, "ball", 600)
    result = await track_energy_dissipation(
        trajectory_data=traj.model_dump(),
        mass=0.5,  # 500g ball
        gravity=9.81
    )
    # See how energy decreases with each bounce
calculate_drag_force

Calculate drag force for an object moving through a fluid.

The drag force opposes motion and is given by:
    F_drag = 0.5 * ρ * v² * C_d * A

Common drag coefficients:
    - Sphere: 0.47
    - Streamlined shape: 0.04
    - Flat plate (perpendicular): 1.28
    - Human (standing): 1.0-1.3
    - Car: 0.25-0.35

Args:
    velocity: Velocity vector [x, y, z] in m/s (or JSON string)
    cross_sectional_area: Area perpendicular to flow in m²
    fluid_density: Fluid density in kg/m³ (water=1000, air=1.225)
    drag_coefficient: Drag coefficient (default 0.47 for sphere)
    viscosity: Dynamic viscosity in Pa·s (water=1.002e-3, air=1.825e-5, oil=0.1).
        If not provided, estimated from fluid_density for Reynolds number calculation.

Returns:
    Drag force vector, magnitude, and Reynolds number

Example - Ball falling through water:
    result = await calculate_drag_force(
        velocity=[0, -5.0, 0],
        cross_sectional_area=0.00785,  # π * (0.05m)² for 10cm diameter
        fluid_density=1000,  # water
        drag_coefficient=0.47,
        viscosity=1.002e-3  # water viscosity for accurate Reynolds number
    )
    # Returns upward drag force opposing downward motion

Example - Ball falling through motor oil:
    result = await calculate_drag_force(
        velocity=[0, -2.0, 0],
        cross_sectional_area=0.00785,
        fluid_density=900,  # oil
        drag_coefficient=0.47,
        viscosity=0.1  # motor oil is much more viscous
    )
calculate_buoyancy

Calculate buoyancy force using Archimedes' principle.

The buoyant force equals the weight of displaced fluid:
    F_b = ρ_fluid * V_submerged * g

Args:
    volume: Object volume in m³
    fluid_density: Fluid density in kg/m³ (water=1000, air=1.225)
    gravity: Gravitational acceleration in m/s² (default 9.81)
    submerged_fraction: Fraction submerged 0.0-1.0 (default 1.0 = fully submerged)

Returns:
    Buoyant force (upward) and displaced mass

Example - Checking if a 1kg ball will float:
    # 10cm diameter sphere: V = (4/3)πr³ = 0.000524 m³
    result = await calculate_buoyancy(
        volume=0.000524,
        fluid_density=1000  # water
    )
    # buoyant_force = 5.14 N
    # If weight (mg) < buoyant force, it floats
    # 1kg * 9.81 = 9.81 N > 5.14 N, so it sinks
calculate_terminal_velocity

Calculate terminal velocity when drag equals weight.

At terminal velocity, forces balance:
    F_drag = F_weight
    v_terminal = √(2mg / ρC_dA)

Args:
    mass: Object mass in kg
    cross_sectional_area: Area perpendicular to fall direction in m²
    fluid_density: Fluid density in kg/m³ (air=1.225, water=1000)
    drag_coefficient: Drag coefficient (sphere=0.47, skydiver=1.0)
    gravity: Gravitational acceleration in m/s² (default 9.81)

Returns:
    Terminal velocity, time to 95%, and drag force at terminal

Example - Skydiver terminal velocity:
    result = await calculate_terminal_velocity(
        mass=70,  # kg
        cross_sectional_area=0.7,  # m² (belly-down position)
        fluid_density=1.225,  # air
        drag_coefficient=1.0,  # human
    )
    # v_terminal ≈ 54 m/s (120 mph)
simulate_underwater_motion

Simulate underwater projectile motion with drag and buoyancy.

Uses numerical integration to simulate motion under:
- Gravity (downward)
- Buoyancy (upward, from displaced fluid)
- Drag (opposes motion)

Args:
    initial_velocity: Initial velocity [x, y, z] in m/s
    mass: Object mass in kg
    volume: Object volume in m³
    cross_sectional_area: Cross-sectional area in m²
    fluid_density: Fluid density in kg/m³ (default 1000 for water)
    fluid_viscosity: Fluid viscosity in Pa·s (default 1.002e-3 for water)
    initial_position: Initial position [x, y, z] in m (default [0,0,0])
    drag_coefficient: Drag coefficient (default 0.47 for sphere)
    gravity: Gravitational acceleration in m/s² (default 9.81)
    duration: Simulation duration in seconds (default 10.0)
    dt: Time step in seconds (default 0.01)

Returns:
    Complete trajectory, final state, max depth, and total distance

Example - Torpedo launch:
    result = await simulate_underwater_motion(
        initial_velocity=[20, 0, 0],  # 20 m/s forward
        mass=100,  # kg
        volume=0.05,  # m³
        cross_sectional_area=0.03,  # m²
        fluid_density=1000,  # water
        drag_coefficient=0.04,  # streamlined
        duration=30.0
    )
calculate_lift_force

Calculate lift force using: L = (1/2) ρ v² C_L A.

Based on Bernoulli's principle and wing aerodynamics.

Args:
    velocity: Flow velocity in m/s
    wing_area: Wing area in m²
    lift_coefficient: Lift coefficient C_L (dimensionless)
    fluid_density: Fluid density in kg/m³ (air=1.225)

Returns:
    Dict containing:
        - lift_force: Lift force in Newtons
        - dynamic_pressure: Dynamic pressure (q) in Pascals

Example - Aircraft wing:
    result = await calculate_lift_force(
        velocity=70,  # m/s (~250 km/h)
        wing_area=20.0,  # m²
        lift_coefficient=1.2,
        fluid_density=1.225
    )
calculate_magnus_force

Calculate Magnus force on a spinning ball.

The Magnus force is perpendicular to both velocity and spin axis.
Causes curve balls in sports.

Args:
    velocity: Ball velocity [x, y, z] in m/s (or JSON string)
    angular_velocity: Angular velocity [x, y, z] in rad/s (or JSON string)
    radius: Ball radius in meters
    fluid_density: Fluid density in kg/m³ (air=1.225)

Returns:
    Dict containing:
        - magnus_force: Magnus force vector [x, y, z] in Newtons
        - magnus_force_magnitude: Force magnitude in Newtons
        - spin_rate: Spin rate (angular velocity magnitude) in rad/s

Example - Soccer ball curve:
    result = await calculate_magnus_force(
        velocity=[20, 0, 0],  # 20 m/s forward
        angular_velocity=[0, 0, 50],  # 50 rad/s topspin
        radius=0.11,  # Soccer ball
        fluid_density=1.225
    )
calculate_bernoulli

Calculate Bernoulli's equation: P + (1/2)ρv² + ρgh = constant.

Energy conservation for flowing fluids.

Args:
    pressure1: Pressure at point 1 in Pascals
    velocity1: Flow velocity at point 1 in m/s
    height1: Height at point 1 in meters
    velocity2: Flow velocity at point 2 in m/s (optional)
    height2: Height at point 2 in meters (optional)
    fluid_density: Fluid density in kg/m³ (default 1000 for water)
    gravity: Gravitational acceleration in m/s² (default 9.81)

Returns:
    Dict containing:
        - total_pressure_1: Total pressure at point 1
        - static_pressure_1: Static pressure component
        - dynamic_pressure_1: Dynamic pressure component
        - hydrostatic_pressure_1: Hydrostatic pressure component
        - pressure2: Pressure at point 2 (if velocity2/height2 given)

Example - Water tank with outlet:
    result = await calculate_bernoulli(
        pressure1=101325,  # Atmospheric at top
        velocity1=0,  # Still water
        height1=10,  # 10m height
        velocity2=14,  # Exit velocity
        height2=0,  # Ground level
        fluid_density=1000
    )
calculate_pressure_at_depth

Calculate pressure at depth: P = P_atm + ρgh.

Hydrostatic pressure increases with depth.

Args:
    depth: Depth below surface in meters
    fluid_density: Fluid density in kg/m³ (water=1000, seawater=1025)
    atmospheric_pressure: Pressure at surface in Pascals (default 101325)
    gravity: Gravitational acceleration in m/s² (default 9.81)

Returns:
    Dict containing:
        - total_pressure: Total pressure in Pascals
        - gauge_pressure: Pressure above atmospheric in Pascals
        - pressure_atmospheres: Pressure in atmospheres (1 atm = 101325 Pa)

Example - Scuba diving at 30m:
    result = await calculate_pressure_at_depth(
        depth=30,  # meters
        fluid_density=1025,  # seawater
        atmospheric_pressure=101325
    )
    # Result: ~4 atmospheres
calculate_reynolds_number

Calculate Reynolds number: Re = ρvL/μ.

Determines flow regime (laminar, transitional, turbulent).

Args:
    velocity: Flow velocity in m/s
    characteristic_length: Characteristic length in meters (pipe diameter, etc.)
    fluid_density: Fluid density in kg/m³
    dynamic_viscosity: Dynamic viscosity in Pa·s (water=0.001, air=1.8e-5)

Returns:
    Dict containing:
        - reynolds_number: Re (dimensionless)
        - flow_regime: "laminar" (Re<2300), "transitional" (2300-4000), "turbulent" (Re>4000)

Example - Water in pipe:
    result = await calculate_reynolds_number(
        velocity=2.0,  # m/s
        characteristic_length=0.05,  # 5cm diameter
        fluid_density=1000,  # water
        dynamic_viscosity=0.001
    )
    # Re = 100,000 → turbulent
calculate_venturi_effect

Calculate Venturi effect (flow through constriction).

Uses continuity equation and Bernoulli's principle.

Args:
    inlet_diameter: Inlet diameter in meters
    throat_diameter: Throat (constriction) diameter in meters
    inlet_velocity: Inlet velocity in m/s
    fluid_density: Fluid density in kg/m³

Returns:
    Dict containing:
        - throat_velocity: Velocity at throat in m/s
        - pressure_drop: Pressure drop from inlet to throat in Pascals
        - flow_rate: Volumetric flow rate in m³/s

Example - Venturi meter:
    result = await calculate_venturi_effect(
        inlet_diameter=0.1,  # 10 cm
        throat_diameter=0.05,  # 5 cm
        inlet_velocity=2.0,  # m/s
        fluid_density=1000  # water
    )
    # throat_velocity = 8 m/s (4x area reduction)
calculate_acceleration_from_position

Calculate acceleration by numerical differentiation of position data.

Uses central differences for numerical differentiation:
v[i] ≈ (r[i+1] - r[i-1]) / (2Δt)
a[i] ≈ (v[i+1] - v[i-1]) / (2Δt)

Args:
    times: Time values in seconds (or JSON string)
    positions: Position vectors [[x,y,z], ...] in meters (or JSON string)

Returns:
    Dict containing:
        - velocities: Velocity vectors [[x,y,z], ...] in m/s
        - accelerations: Acceleration vectors [[x,y,z], ...] in m/s²
        - average_velocity: Average velocity [x,y,z] in m/s
        - average_acceleration: Average acceleration [x,y,z] in m/s²

Example - Analyze recorded position data:
    result = await calculate_acceleration_from_position(
        times=[0, 1, 2, 3],
        positions=[[0,0,0], [5,0,0], [10,0,0], [15,0,0]]
    )
calculate_jerk

Calculate jerk (rate of change of acceleration).

Jerk = da/dt is important for comfort in vehicles and mechanical design.

Args:
    times: Time values in seconds (or JSON string)
    accelerations: Acceleration vectors [[x,y,z], ...] in m/s² (or JSON string)

Returns:
    Dict containing:
        - jerks: Jerk vectors [[x,y,z], ...] in m/s³
        - average_jerk: Average jerk [x,y,z] in m/s³
        - max_jerk_magnitude: Maximum jerk magnitude in m/s³

Example:
    result = await calculate_jerk(
        times=[0, 1, 2, 3],
        accelerations=[[0,0,0], [2,0,0], [4,0,0], [6,0,0]]
    )
    # jerk_x ≈ 2 m/s³ (constant)
fit_trajectory

Fit polynomial to trajectory data.

Useful for smoothing noisy data or finding trajectory equations.
Default fit_type="quadratic" fits parabolic trajectory (constant acceleration).

Args:
    times: Time values in seconds (or JSON string)
    positions: Position vectors [[x,y,z], ...] in meters (or JSON string)
    fit_type: Polynomial type - "linear", "quadratic", or "cubic" (default "quadratic")

Returns:
    Dict containing:
        - coefficients_x: Polynomial coefficients for x(t)
        - coefficients_y: Polynomial coefficients for y(t)
        - coefficients_z: Polynomial coefficients for z(t)
        - r_squared: R² goodness of fit (0-1)
        - predicted_positions: Fitted positions [[x,y,z], ...]

Example - Projectile motion:
    result = await fit_trajectory(
        times=[0, 1, 2, 3],
        positions=[[0,0,0], [10,15,0], [20,20,0], [30,15,0]],
        fit_type="quadratic"
    )
    # Fits x(t) = c0 + c1*t + c2*t²
generate_motion_graph

Generate motion graph data (position, velocity, acceleration vs time).

Calculates velocity and acceleration from position data and extracts
the specified component for graphing.

Args:
    times: Time values in seconds (or JSON string)
    positions: Position vectors [[x,y,z], ...] in meters (or JSON string)
    component: Which component to analyze - "x", "y", "z", or "magnitude" (default)

Returns:
    Dict containing:
        - times: Time values
        - positions: Position values (selected component)
        - velocities: Velocity values (selected component)
        - accelerations: Acceleration values (selected component)
        - max_velocity: Maximum velocity magnitude
        - max_acceleration: Maximum acceleration magnitude
        - component: Which component was analyzed

Example:
    result = await generate_motion_graph(
        times=[0, 1, 2, 3],
        positions=[[0,0,0], [5,0,0], [20,0,0], [45,0,0]],
        component="x"
    )
    # Automatically calculates v and a
calculate_average_speed

Calculate average speed along a path.

Average speed = total distance / total time
(Distance is path length, not displacement)

Args:
    positions: Position vectors [[x,y,z], ...] in meters (or JSON string)
    times: Time values in seconds (or JSON string)

Returns:
    Dict containing:
        - average_speed: Average speed in m/s
        - total_distance: Total path length in meters
        - total_time: Total elapsed time in seconds
        - displacement_magnitude: Straight-line displacement in meters
        - displacement: Displacement vector [x,y,z] in meters

Example - Car on winding road:
    result = await calculate_average_speed(
        positions=[[0,0,0], [10,5,0], [20,10,0], [15,20,0]],
        times=[0, 10, 20, 30]
    )
calculate_instantaneous_velocity

Calculate instantaneous velocity at a specific time.

Uses interpolation if target_time is between data points,
otherwise uses numerical differentiation.

Args:
    positions: Position vectors [[x,y,z], ...] in meters (or JSON string)
    times: Time values in seconds (or JSON string)
    target_time: Time at which to calculate velocity in seconds

Returns:
    Dict containing:
        - velocity: Velocity vector [x,y,z] in m/s
        - speed: Speed magnitude in m/s
        - interpolated: Whether interpolation was used
        - time: Target time (echo)

Example:
    result = await calculate_instantaneous_velocity(
        positions=[[0,0,0], [3,4,0], [6,8,0]],
        times=[0, 1, 2],
        target_time=1.0
    )
    # speed = 5 m/s
calculate_projectile_with_drag

Calculate projectile motion including air resistance (drag).

Uses numerical integration (RK4) to solve motion equations with:
- Quadratic drag force: F_drag = 0.5 * ρ * v² * Cd * A
- Magnus force (spin effects): F_magnus = 0.5 * ρ * Cl * A * ω * r * v
- Wind effects (constant wind vector)
- Variable air density (altitude and temperature effects)

This provides REALISTIC trajectories for sports balls, projectiles,
and other objects moving through air or water. Compare with
calculate_projectile_motion (no drag) to see dramatic differences!

Common drag coefficients (Cd):
    - Sphere: 0.47 (default)
    - Baseball: 0.4
    - Golf ball: 0.25 (dimples reduce drag)
    - Football (American): 0.05-0.15 (orientation-dependent)
    - Basketball: 0.55
    - Soccer ball: 0.25
    - Skydiver (belly-down): 1.0-1.3
    - Streamlined car: 0.25-0.35

Args:
    initial_velocity: Launch velocity in m/s
    angle_degrees: Launch angle in degrees (0-90)
    mass: Object mass in kg
    cross_sectional_area: Cross-section perpendicular to motion in m²
    initial_height: Launch height in meters (default 0)
    drag_coefficient: Drag coefficient Cd (default 0.47 for sphere)
    fluid_density: Fluid density in kg/m³ (air=1.225, water=1000)
    gravity: Gravitational acceleration m/s² (default 9.81)
    time_step: Integration time step in seconds (default 0.01)
    max_time: Maximum simulation time in seconds (default 30)
    spin_rate: Spin rate in rad/s for Magnus force (default 0, no spin)
    spin_axis: Spin axis unit vector [x, y, z] (default [0, 0, 1] = vertical)
    wind_velocity: Wind velocity [vx, vy] in m/s (default [0, 0], no wind)
    altitude: Altitude above sea level in meters (default 0, affects air density)
    temperature: Air temperature in Celsius (default 15, affects air density)

Returns:
    Dict containing:
        - max_height: Maximum altitude reached (m)
        - range: Horizontal distance traveled (m)
        - time_of_flight: Total flight time (s)
        - impact_velocity: Speed at landing (m/s)
        - impact_angle: Angle at landing (degrees below horizontal)
        - trajectory_points: [[x, y], ...] for plotting
        - energy_lost_to_drag: Energy dissipated by drag (J)
        - initial_kinetic_energy: Initial KE (J)
        - final_kinetic_energy: Final KE (J)
        - lateral_deflection: Lateral deflection from spin/wind (m)
        - magnus_force_max: Maximum Magnus force magnitude (N)
        - wind_drift: Total wind drift (m)
        - effective_air_density: Effective air density used (kg/m³)

Example - Baseball curveball (2500 rpm backspin):
    result = await calculate_projectile_with_drag(
        initial_velocity=40.23,  # 90 mph
        angle_degrees=10,
        mass=0.145,
        cross_sectional_area=0.0043,
        drag_coefficient=0.4,
        spin_rate=261.8,  # 2500 rpm = 261.8 rad/s
        spin_axis=[0, 0, 1]  # Backspin (vertical axis)
    )
    # Backspin increases range and height!

Example - Golf ball at altitude (Denver, 1600m):
    result = await calculate_projectile_with_drag(
        initial_velocity=70,
        angle_degrees=12,
        mass=0.0459,
        cross_sectional_area=0.00143,
        drag_coefficient=0.25,
        altitude=1600,  # Denver elevation
        temperature=20  # Summer day
    )
    # Less air resistance = longer drive!

Example - Soccer free kick with wind:
    result = await calculate_projectile_with_drag(
        initial_velocity=25,
        angle_degrees=15,
        mass=0.43,
        cross_sectional_area=0.0388,
        drag_coefficient=0.25,
        wind_velocity=[5, 0],  # 5 m/s tailwind
        spin_rate=50,  # Sidespin for curve
        spin_axis=[0, 1, 0]  # Horizontal axis
    )
    # Wind drift + Magnus curve!
calculate_hookes_law

Calculate spring force using Hooke's Law: F = -kx.

The restoring force is proportional to displacement from equilibrium.
Fundamental for springs, elastic materials, and simple harmonic motion.

Args:
    spring_constant: Spring constant k in N/m (stiffness)
    displacement: Displacement from equilibrium in meters

Returns:
    Dict containing:
        - force: Restoring force magnitude in Newtons
        - potential_energy: Elastic potential energy in Joules

Tips for LLMs:
    - Stiffer spring → larger k → more force for same displacement
    - Potential energy stored in spring: PE = (1/2)kx²
    - Negative sign in F = -kx means force opposes displacement

Example - Compressing a car spring:
    result = await calculate_hookes_law(
        spring_constant=10000,  # N/m (stiff car spring)
        displacement=0.05  # 5cm compression
    )
    # Force = 500 N, PE = 12.5 J
calculate_spring_mass_period

Calculate period of spring-mass system: T = 2π√(m/k).

Natural oscillation frequency of a mass attached to a spring.
Independent of amplitude (for ideal springs).

Args:
    mass: Mass in kg
    spring_constant: Spring constant k in N/m

Returns:
    Dict containing:
        - period: T in seconds
        - frequency: f in Hz
        - angular_frequency: ω in rad/s

Tips for LLMs:
    - Heavier mass → longer period (slower oscillation)
    - Stiffer spring → shorter period (faster oscillation)
    - ω = 2πf = √(k/m)

Example - Mass on spring:
    result = await calculate_spring_mass_period(
        mass=0.5,  # 500g mass
        spring_constant=20.0  # N/m
    )
    # T ≈ 0.99s, f ≈ 1.01 Hz
calculate_simple_harmonic_motion

Calculate simple harmonic motion: x(t) = A cos(ωt + φ).

Position, velocity, and acceleration for sinusoidal oscillation.
Models ideal springs, pendulums, and many other oscillating systems.

Args:
    amplitude: Amplitude A in meters (maximum displacement)
    angular_frequency: ω in rad/s (ω = 2πf)
    time: Time t in seconds
    phase: Phase shift φ in radians (default 0)

Returns:
    Dict containing:
        - position: x(t) in meters
        - velocity: v(t) = -Aω sin(ωt + φ) in m/s
        - acceleration: a(t) = -Aω² cos(ωt + φ) in m/s²

Tips for LLMs:
    - Position and acceleration are 180° out of phase
    - Maximum velocity occurs at equilibrium (x = 0)
    - Maximum acceleration occurs at maximum displacement

Example - Oscillating mass:
    result = await calculate_simple_harmonic_motion(
        amplitude=0.1,  # 10cm amplitude
        angular_frequency=5.0,  # rad/s
        time=1.0  # at t = 1s
    )
calculate_damped_oscillation

Calculate damped oscillation with friction/resistance.

Real oscillators lose energy over time due to damping (air resistance,
friction). Three regimes: underdamped, critically damped, overdamped.

Args:
    mass: Mass in kg
    spring_constant: k in N/m
    damping_coefficient: b in kg/s (damping strength)
    time: Time t in seconds
    initial_position: Initial position in meters (default 1.0)
    initial_velocity: Initial velocity in m/s (default 0.0)

Returns:
    Dict containing:
        - position: x(t) in meters
        - velocity: v(t) in m/s
        - damping_ratio: ζ (zeta) = b/(2√(mk))
        - regime: "underdamped", "critically_damped", or "overdamped"

Damping regimes:
    - ζ < 1: Underdamped (oscillates, gradually decays)
    - ζ = 1: Critically damped (returns fastest without oscillating)
    - ζ > 1: Overdamped (slow return, no oscillation)

Example - Car suspension:
    result = await calculate_damped_oscillation(
        mass=300,  # kg (quarter car mass)
        spring_constant=20000,  # N/m
        damping_coefficient=2000,  # kg/s
        time=1.0
    )
    # Should be slightly underdamped for comfort
calculate_pendulum_period

Calculate pendulum period: T = 2π√(L/g).

Period of a simple pendulum depends only on length and gravity
(for small amplitudes). Includes correction for large amplitudes.

Args:
    length: Pendulum length in meters (pivot to center of mass)
    gravity: Gravitational acceleration in m/s² (default 9.81)
    amplitude_degrees: Amplitude in degrees (optional, for large angle correction)

Returns:
    Dict containing:
        - period: T in seconds
        - frequency: f in Hz
        - angular_frequency: ω in rad/s
        - small_angle_approximation: Whether small angle formula was used

Tips for LLMs:
    - Period independent of mass (Galileo's discovery)
    - Period independent of amplitude (for small angles < 15°)
    - Longer pendulum → longer period
    - Use for: clocks, playground swings, seismometers

Example - Grandfather clock:
    result = await calculate_pendulum_period(
        length=0.994,  # meters (for 2-second period)
        gravity=9.81
    )
    # T = 2.0 seconds
calculate_torque

Calculate torque from force and position: τ = r × F (cross product).

Torque is the rotational equivalent of force. It causes angular acceleration
and depends on both the force magnitude and the distance from the pivot point.

Args:
    force_x: X component of force in Newtons
    force_y: Y component of force in Newtons
    force_z: Z component of force in Newtons
    position_x: X component of position vector from pivot to force application (meters)
    position_y: Y component of position vector from pivot to force application (meters)
    position_z: Z component of position vector from pivot to force application (meters)

Returns:
    Dict containing:
        - torque: Torque vector [x, y, z] in N⋅m
        - magnitude: Torque magnitude in N⋅m

Tips for LLMs:
    - Torque direction follows right-hand rule (perpendicular to force and position)
    - Maximum torque when force is perpendicular to position vector
    - Zero torque when force is parallel to position vector
    - Use for: wrenches, door hinges, motors, gears

Example - Opening a door:
    result = await calculate_torque(
        force_x=50.0,  # Push perpendicular to door
        force_y=0.0,
        force_z=0.0,
        position_x=0.0,
        position_y=0.0,
        position_z=0.8  # 0.8m from hinge
    )
    # Torque = 40 N⋅m
calculate_moment_of_inertia

Calculate moment of inertia for various shapes.

Moment of inertia (I) is the rotational equivalent of mass. It determines
how difficult it is to change an object's rotation. Depends on both mass
distribution and rotation axis.

Args:
    shape: Shape type - "sphere", "solid_sphere", "hollow_sphere", "rod", "disk", "cylinder", "box"
    mass: Mass in kilograms
    radius: Radius for sphere/disk/cylinder (meters)
    length: Length for rod (meters)
    width: Width for box (meters)
    height: Height for box/cylinder (meters)
    depth: Depth for box (meters)
    axis: Rotation axis - "center", "end" (for rod), "x", "y", "z" (for box)

Returns:
    Dict containing:
        - moment_of_inertia: I in kg⋅m²
        - shape: Shape type
        - axis: Rotation axis

Common formulas:
    - Solid sphere (center): I = (2/5)mr²
    - Hollow sphere (center): I = (2/3)mr²
    - Rod (center): I = (1/12)mL²
    - Rod (end): I = (1/3)mL²
    - Disk (center): I = (1/2)mr²

Example - Spinning wheel:
    result = await calculate_moment_of_inertia(
        shape="disk",
        mass=5.0,  # 5kg wheel
        radius=0.3  # 30cm radius
    )
    # I = 0.225 kg⋅m²
calculate_angular_momentum

Calculate angular momentum: L = I × ω.

Angular momentum is the rotational equivalent of linear momentum.
It's conserved in the absence of external torques (like ice skater spinning).

Args:
    moment_of_inertia: Moment of inertia in kg⋅m²
    angular_velocity_x: X component of angular velocity in rad/s
    angular_velocity_y: Y component of angular velocity in rad/s
    angular_velocity_z: Z component of angular velocity in rad/s

Returns:
    Dict containing:
        - angular_momentum: L vector [x, y, z] in kg⋅m²/s
        - magnitude: L magnitude in kg⋅m²/s

Tips for LLMs:
    - Angular momentum is conserved when no external torques act
    - Ice skater pulls arms in → I decreases → ω increases (L constant)
    - Gyroscopes resist changes in angular momentum direction

Example - Spinning figure skater:
    # Arms extended: I = 3.0 kg⋅m², ω = 5 rad/s
    result = await calculate_angular_momentum(
        moment_of_inertia=3.0,
        angular_velocity_x=0.0,
        angular_velocity_y=5.0,
        angular_velocity_z=0.0
    )
    # L = 15 kg⋅m²/s (conserved when arms pulled in)
calculate_rotational_kinetic_energy

Calculate rotational kinetic energy: KE_rot = (1/2) I ω².

Energy of rotation. A spinning object has kinetic energy even if
its center of mass is stationary.

Args:
    moment_of_inertia: Moment of inertia in kg⋅m²
    angular_velocity: Angular velocity magnitude in rad/s

Returns:
    Dict containing:
        - rotational_ke: Rotational kinetic energy in Joules

Tips for LLMs:
    - Total KE = translational KE + rotational KE
    - Rolling object has both types of kinetic energy
    - Flywheel energy storage uses this principle

Example - Car wheel at highway speed:
    result = await calculate_rotational_kinetic_energy(
        moment_of_inertia=0.5,  # kg⋅m²
        angular_velocity=100.0  # rad/s (fast spinning)
    )
    # KE_rot = 2500 J
calculate_angular_acceleration

Calculate angular acceleration: α = τ / I.

Angular acceleration is the rotational equivalent of linear acceleration.
Determined by net torque and moment of inertia.

Args:
    torque: Torque magnitude in N⋅m
    moment_of_inertia: Moment of inertia in kg⋅m²

Returns:
    Dict containing:
        - angular_acceleration: α in rad/s²

Tips for LLMs:
    - Rotational version of F = ma → τ = Iα
    - Larger I means slower angular acceleration for same torque
    - Use for: motor acceleration, spinning up flywheels

Example - Motor accelerating a wheel:
    result = await calculate_angular_acceleration(
        torque=10.0,  # N⋅m
        moment_of_inertia=0.5  # kg⋅m²
    )
    # α = 20 rad/s²
check_force_balance

Check if forces are in equilibrium: ΣF = 0.

Verifies whether a system of forces is balanced (net force = 0).
Essential for statics problems and structural analysis.

Args:
    forces: List of force vectors [[x,y,z], ...] in Newtons (or JSON string)
    tolerance: Tolerance for equilibrium check (fraction, default 0.01)

Returns:
    Dict containing:
        - net_force: Net force vector [x, y, z] in Newtons
        - net_force_magnitude: Net force magnitude in Newtons
        - is_balanced: Whether forces are in equilibrium
        - individual_magnitudes: Magnitude of each force

Example - Bridge support forces:
    result = await check_force_balance(
        forces=[[0, 1000, 0], [0, 500, 0], [0, -1500, 0]],
        tolerance=0.01
    )
    # is_balanced = True if net force ≈ 0
check_torque_balance

Check if torques are in equilibrium: Στ = 0.

Verifies whether a system of torques is balanced (net torque = 0).
Essential for rotational equilibrium and lever problems.

Args:
    torques: List of torque vectors [[x,y,z], ...] in N⋅m (or JSON string)
    tolerance: Tolerance for equilibrium check (fraction, default 0.01)

Returns:
    Dict containing:
        - net_torque: Net torque vector [x, y, z] in N⋅m
        - net_torque_magnitude: Net torque magnitude in N⋅m
        - is_balanced: Whether torques are in equilibrium
        - individual_magnitudes: Magnitude of each torque

Example - Seesaw balance:
    result = await check_torque_balance(
        torques=[[0, 0, 100], [0, 0, -100]],
        tolerance=0.01
    )
calculate_center_of_mass

Calculate center of mass for a system of point masses.

Formula: r_cm = Σ(m_i × r_i) / Σm_i

Args:
    masses: List of masses in kg (or JSON string)
    positions: List of positions [[x,y,z], ...] in meters (or JSON string)

Returns:
    Dict containing:
        - center_of_mass: Position [x, y, z] in meters
        - total_mass: Total system mass in kg

Example - Three-mass system:
    result = await calculate_center_of_mass(
        masses=[1.0, 2.0, 3.0],
        positions=[[0,0,0], [1,0,0], [2,0,0]]
    )
    # center_of_mass ≈ [1.5, 0, 0]
calculate_static_friction

Calculate maximum static friction force: f_s,max = μ_s × N.

Determines whether an object will slip under applied force.

Args:
    normal_force: Normal force in Newtons
    coefficient_static_friction: Coefficient of static friction μ_s
    applied_force: Applied horizontal force in Newtons (optional)

Returns:
    Dict containing:
        - max_static_friction: Maximum static friction in Newtons
        - will_slip: Whether object will slip (if applied_force provided)
        - friction_force: Actual friction force (if applied_force provided)

Example - Box on floor:
    result = await calculate_static_friction(
        normal_force=100,
        coefficient_static_friction=0.5,
        applied_force=40
    )
    # will_slip = False (40N < 50N max)
calculate_normal_force

Calculate normal force on an inclined plane.

On an incline at angle θ:
- N = mg cos(θ) + F_additional
- Weight component perpendicular: mg cos(θ)
- Weight component parallel: mg sin(θ)

Args:
    mass: Object mass in kg
    gravity: Gravitational acceleration in m/s² (default 9.81)
    angle_degrees: Incline angle in degrees (0 = horizontal)
    additional_force: Additional perpendicular force in Newtons (optional)

Returns:
    Dict containing:
        - normal_force: Normal force in Newtons
        - weight_component_perpendicular: Weight component ⊥ to surface
        - weight_component_parallel: Weight component ∥ to surface

Example - Box on 30° ramp:
    result = await calculate_normal_force(
        mass=10.0,
        angle_degrees=30.0
    )
    # normal_force ≈ 84.9 N
check_equilibrium

Check complete static equilibrium: ΣF = 0 and Στ = 0.

For static equilibrium, both force and torque must be balanced.

Args:
    forces: List of force vectors [[x,y,z], ...] in N (or JSON string)
    force_positions: Positions where forces applied [[x,y,z], ...] (or JSON string)
    pivot_point: Pivot point for torque calculation [x,y,z] (default [0,0,0])
    tolerance: Tolerance for equilibrium check (default 0.01)

Returns:
    Dict containing:
        - force_balanced: Whether ΣF = 0
        - torque_balanced: Whether Στ = 0
        - in_equilibrium: Whether system is in static equilibrium
        - net_force: Net force [x, y, z] in N
        - net_torque: Net torque [x, y, z] in N⋅m

Example - Beam with two forces:
    result = await check_equilibrium(
        forces=[[0, 100, 0], [0, -100, 0]],
        force_positions=[[1, 0, 0], [2, 0, 0]]
    )
calculate_beam_reactions

Calculate reaction forces for a simply supported beam.

Uses moment equilibrium about supports to find reaction forces.

Args:
    beam_length: Beam length in meters
    loads: Point loads in Newtons (downward positive) (or JSON string)
    load_positions: Positions of loads from left end in meters (or JSON string)

Returns:
    Dict containing:
        - reaction_left: Reaction force at left support in Newtons
        - reaction_right: Reaction force at right support in Newtons
        - total_load: Total downward load in Newtons
        - is_balanced: Whether reactions balance loads

Example - Beam with two loads:
    result = await calculate_beam_reactions(
        beam_length=10.0,
        loads=[1000, 500],
        load_positions=[3.0, 7.0]
    )
convert_unit
Convert a value from one unit to another.

Supports 62 unit types across 16 categories:
- Velocity: m/s, km/h, mph, ft/s, knots
- Distance: m, km, mi, ft, yd, in
- Mass: kg, g, lb, oz
- Force: N, kN, lbf
- Energy: J, kJ, cal, BTU, kWh
- Power: W, kW, hp
- Temperature: K, C, F
- Angle: rad, deg
- Pressure: Pa, kPa, bar, psi, atm
- Area: m², km², ft², acre
- Volume: m³, L, gal, ft³
- Time: s, min, hr, day
- Acceleration: m/s², g, ft/s²
- Torque: N·m, lb·ft, lb·in
- Frequency: Hz, kHz, MHz, GHz
- Data Size: B, KB, MB, GB

Enables natural language queries like:
- "Convert 60 mph to m/s"
- "How fast is 100 km/h in mph?"
- "Convert 10 kg to pounds"

Args:
    value: The numeric value to convert
    from_unit: Source unit (e.g., 'mph', 'kg', 'J')
    to_unit: Target unit (e.g., 'm/s', 'lb', 'kWh')

Returns:
    Dictionary with:
    - original_value: Input value
    - original_unit: Input unit
    - converted_value: Result value
    - converted_unit: Result unit
    - formatted: Human-readable string

Examples:
    >>> convert_unit(100, 'm/s', 'mph')
    {
        "original_value": 100,
        "original_unit": "m/s",
        "converted_value": 223.694,
        "converted_unit": "mph",
        "formatted": "100 m/s = 223.694 mph"
    }

    >>> convert_unit(60, 'mph', 'km/h')
    {
        "original_value": 60,
        "original_unit": "mph",
        "converted_value": 96.56064,
        "converted_unit": "km/h",
        "formatted": "60 mph = 96.56 km/h"
    }
list_unit_conversions
List all supported unit conversions.

Returns a dictionary mapping category names to lists of supported units.

Returns:
    Dictionary with supported unit categories:
    - velocity: Speed units
    - distance: Length units
    - mass: Weight units
    - force: Force units
    - energy: Energy units
    - power: Power units
    - temperature: Temperature scales
    - angle: Angular units
    - pressure: Pressure units
    - area: Area units
    - volume: Volume units

Example:
    >>> list_unit_conversions()
    {
        "velocity": ["m/s", "km/h", "mph", "ft/s", "knots"],
        "distance": ["m", "km", "mi", "ft", "yd", "in"],
        "mass": ["kg", "g", "lb", "oz"],
        ...
    }
create_simulation

Create a new physics simulation using Rapier engine.

Initializes a new rigid-body physics world with configurable gravity and
timestep. Returns a simulation ID used for all subsequent operations.

Args:
    gravity_x: X component of gravity vector (m/s²). Default 0.0
    gravity_y: Y component of gravity vector (m/s²). Default -9.81 (Earth down)
    gravity_z: Z component of gravity vector (m/s²). Default 0.0
    dimensions: 2 or 3 for 2D/3D simulation. Default 3.
    dt: Simulation timestep in seconds. Default 0.016 (60 FPS).
        Smaller = more accurate but slower, larger = faster but less stable
    integrator: Integration method. Options: "euler", "verlet", "rk4". Default "verlet".

Returns:
    SimulationCreateResponse containing:
        - sim_id: Unique simulation identifier (use for all other sim calls)
        - config: Echo of the configuration used

Tips for LLMs:
    - Keep simulation IDs in memory for the conversation session
    - Default gravity is Earth standard (9.81 m/s² down = -Y direction)
    - dt=0.016 ≈ 60 FPS, dt=0.008 ≈ 120 FPS (higher accuracy)
    - "verlet" integrator is good default (stable, energy-conserving)
    - Remember to destroy_simulation when done to free resources

Requires:
    - Rapier provider must be configured (see config.py)
    - Rapier service must be running (see RAPIER_SERVICE.md)

Example:
    # Create simulation with Earth gravity
    sim = await create_simulation(
        gravity_y=-9.81,
        dt=0.016
    )
    # Use sim.sim_id for add_body, step_simulation, etc.
add_rigid_body

Add a rigid body to an existing simulation.

Creates a new physics body (static, dynamic, or kinematic) with specified
shape, mass, and initial conditions. Bodies interact via collisions.

Args:
    sim_id: Simulation ID from create_simulation
    body_id: Unique identifier for this body (user-defined string)
    body_type: "static", "dynamic", or "kinematic"
        - static: Never moves (ground, walls)
        - dynamic: Affected by forces and collisions
        - kinematic: Moves but not affected by forces (scripted motion)
    shape: Collider shape: "box", "sphere", "capsule", "cylinder", "plane"
    size: Shape dimensions:
        - box: [width, height, depth]
        - sphere: [radius]
        - capsule: [half_height, radius]
        - cylinder: [half_height, radius]
        - plane: not needed (use normal/offset instead)
    mass: Mass in kilograms (for dynamic bodies). Default 1.0
    normal: Normal vector [x, y, z] for plane shape. Default [0, 1, 0] (upward)
    offset: Offset along normal for plane. Default 0.0
    position: Initial position [x, y, z]. Default [0, 0, 0]
    orientation: Initial orientation quaternion [x, y, z, w]. Default [0, 0, 0, 1] (identity)
    velocity: Initial linear velocity [x, y, z]. Default [0, 0, 0]
    angular_velocity: Initial angular velocity [x, y, z]. Default [0, 0, 0]
    restitution: Bounciness (0.0 = no bounce, 1.0 = perfect bounce). Default 0.5
    friction: Surface friction (0.0 = ice, 1.0 = rubber). Default 0.5
    is_sensor: If true, detects collisions but doesn't respond physically. Default false
    linear_damping: Linear velocity damping (0.0-1.0) - like air resistance. Default 0.0
    angular_damping: Angular velocity damping (0.0-1.0) - like rotational friction. Default 0.0
    drag_coefficient: Base drag coefficient (Cd) for orientation-dependent drag. Optional
    drag_area: Reference cross-sectional area (m²) for drag calculation. Optional
    drag_axis_ratios: Drag variation along body axes [x, y, z]. E.g., [1.0, 0.2, 1.0] for streamlined along Y. Optional
    fluid_density: Fluid density (kg/m³). Air=1.225, Water=1000. Default 1.225

Returns:
    body_id (echo of the input ID)

Tips for LLMs:
    - Create ground FIRST: body_type="static", shape="plane", normal=[0, 1, 0]
    - Box size is full width/height/depth (not half-extents)
    - Sphere size is [radius] (array with one element)
    - Quaternions: identity = [0, 0, 0, 1] (no rotation)
    - Common restitution: steel=0.8, wood=0.5, clay=0.1
    - Common friction: ice=0.05, wood=0.4, rubber=1.0

Example:
    # Add a ground plane
    await add_rigid_body(
        sim_id=sim_id,
        body_id="ground",
        body_type="static",
        shape="plane",
        normal=[0, 1, 0]
    )

    # Add a bouncing ball
    await add_rigid_body(
        sim_id=sim_id,
        body_id="ball",
        body_type="dynamic",
        shape="sphere",
        size=[0.5],  # radius = 0.5m
        mass=1.0,
        position=[0, 10, 0],
        restitution=0.7
    )

    # Add a falling box
    await add_rigid_body(
        sim_id=sim_id,
        body_id="box",
        body_type="dynamic",
        shape="box",
        size=[1.0, 1.0, 1.0],
        mass=10.0,
        position=[0.0, 5.0, 0.0]
    )
add_joint

Add a joint/constraint to connect two rigid bodies.

Joints allow you to constrain the motion between bodies:
- FIXED: Rigid connection (glue objects together)
- REVOLUTE: Hinge rotation around an axis (doors, pendulums)
- SPHERICAL: Ball-and-socket rotation (ragdolls, gimbals)
- PRISMATIC: Sliding along an axis (pistons, elevators)

Args:
    sim_id: Simulation identifier
    joint: Joint definition with type and parameters

Returns:
    joint_id: Unique identifier for the created joint

Example - Simple Pendulum:
    # Create fixed anchor point
    add_rigid_body(
        sim_id=sim_id,
        body_id="anchor",
        body_type="static",
        shape="sphere",
        size=[0.05],
        position=[0.0, 5.0, 0.0],
    )

    # Create pendulum bob
    add_rigid_body(
        sim_id=sim_id,
        body_id="bob",
        body_type="dynamic",
        shape="sphere",
        size=[0.1],
        mass=1.0,
        position=[0.0, 3.0, 0.0],
    )

    # Connect with revolute joint (hinge)
    add_joint(
        sim_id=sim_id,
        joint=JointDefinition(
            id="pendulum_joint",
            joint_type="revolute",
            body_a="anchor",
            body_b="bob",
            anchor_a=[0.0, 0.0, 0.0],  # Center of anchor
            anchor_b=[0.0, 0.1, 0.0],   # Top of bob
            axis=[0.0, 0.0, 1.0],        # Rotate around Z-axis
        ),
    )
step_simulation

Step the simulation forward in time.

Advances the physics simulation by running the integrator for N steps.
Returns the complete state of all bodies after stepping.

Args:
    sim_id: Simulation ID
    steps: Number of timesteps to simulate. Default 1.
        Example: steps=600 with dt=0.016 = 9.6 seconds of simulation
    dt: Optional timestep override (seconds). If None, uses config default.

Returns:
    SimulationStepResponse containing:
        - sim_id: Simulation identifier
        - time: Current simulation time in seconds
        - bodies: List of all body states with positions, velocities, contacts

Tips for LLMs:
    - Each body state includes position, orientation (quaternion), velocities
    - contacts array shows active collisions with impulse magnitudes
    - For real-time preview: steps=1, call repeatedly
    - For final result: steps=1000+, call once
    - Large step counts may timeout - limit to ~10,000 steps per call

Example:
    # Simulate 10 seconds at 60 FPS
    result = await step_simulation(
        sim_id=sim_id,
        steps=600  # 600 steps × 0.016s = 9.6s
    )
    for body in result.bodies:
        print(f"{body.id}: position={body.position}")
record_trajectory

Record the trajectory of a specific body over time.

Steps the simulation and records position/orientation/velocity at each
timestep for one body. Perfect for generating animation data for R3F.

Args:
    sim_id: Simulation ID
    body_id: ID of the body to track
    steps: Number of timesteps to record
    dt: Optional timestep override. If None, uses config default.

Returns:
    TrajectoryResponse containing:
        - body_id: Tracked body identifier
        - frames: List of trajectory frames with time, position, orientation, velocity
        - total_time: Total simulated time in seconds
        - num_frames: Number of frames recorded

Tips for LLMs:
    - Each frame has: time, position [x,y,z], orientation [x,y,z,w], velocity [x,y,z]
    - Frames are evenly spaced in time (every dt seconds)
    - Output is R3F-compatible: use position/orientation directly in Three.js
    - For 60 FPS video: record at dt=1/60 ≈ 0.0167
    - Typical recording: 100-1000 frames (1.6-16 seconds at 60 FPS)

Example:
    # Record 5 seconds of a falling ball
    traj = await record_trajectory(
        sim_id=sim_id,
        body_id="ball",
        steps=300  # 300 × 0.016 ≈ 5 seconds
    )
    # Use traj.frames in React Three Fiber for animation
record_trajectory_with_events

Record trajectory and automatically detect collision and bounce events.

This is an enhanced version of record_trajectory that analyzes the motion
and detects important events like bounces and collisions. Perfect for
answering questions like "how many times did the ball bounce?"

Args:
    sim_id: Simulation ID
    body_id: Body to track
    steps: Number of simulation steps to record
    dt: Optional custom timestep (overrides simulation default)
    detect_bounces: Whether to detect bounce events (default True)
    bounce_height_threshold: Maximum height to consider as "on ground" in meters (default 0.01)

Returns:
    TrajectoryWithEventsResponse containing:
        - frames: Trajectory frames (positions, velocities)
        - bounces: Detected bounce events with energy loss
        - contact_events: Contact/collision events (future)

Tips for LLMs:
    - Use this instead of record_trajectory when you need event detection
    - Bounces are detected from velocity reversals near the ground
    - Each bounce includes: time, position, speeds before/after, energy loss
    - Use `trajectory.bounces` to count or analyze bounces
    - Adjust bounce_height_threshold for different ground shapes

Example:
    # Record ball bouncing and count bounces
    traj = await record_trajectory_with_events(
        sim_id=sim_id,
        body_id="ball",
        steps=600,
        detect_bounces=True,
        bounce_height_threshold=0.01  # 1cm threshold
    )
    print(f"Detected {len(traj.bounces)} bounces")
    for bounce in traj.bounces:
        print(f"Bounce #{bounce.bounce_number} at t={bounce.time:.2f}s")
destroy_simulation

Destroy a simulation and free resources.

Cleanup when done with a simulation. Important for long-running servers
to avoid memory leaks.

Args:
    sim_id: Simulation ID to destroy

Returns:
    Success message

Tips for LLMs:
    - Always destroy simulations when conversation ends or changes topic
    - Rapier service keeps simulations in memory until explicitly destroyed
    - Good practice: destroy after recording trajectory or final state

Example:
    await destroy_simulation(sim_id)

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-physics'

If you have feedback or need assistance with the MCP directory API, please join our Discord server