~starkingdoms/starkingdoms

3f9bb7e1b60dbf2bca283c9b3d82c51b0f17425e — ghostly_zsh 17 days ago 89c38c3
feat: avian f64, which means orbit springs work now
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +2 -2
@@ 38,9 38,9 @@ bevy = { version = "0.18", default-features = false, features = [

avian2d = { version = "0.6", default-features = false, features = [
    "2d",
    "f32",
    "f64",
    "default-collider",
    "parry-f32",
    "parry-f64",
    "xpbd_joints",
    "bevy_picking",
    "debug-plugin",

M crates/unified/assets/config/planets.pc.toml => crates/unified/assets/config/planets.pc.toml +1 -1
@@ 1,5 1,5 @@
[orbit]
planet_spring_compliance = 0.0
planet_spring_compliance = 10000000000.0

[[planets]]
name = "Sun"

M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +12 -12
@@ 38,10 38,10 @@ fn handle_incoming_parts(
        } else {
            &new_part.strong_config.part.sprite_disconnected
        }));
        sprite.color = Color::srgb(1.0 + ((temperature.0 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        sprite.custom_size = Some(Vec2::new(
            new_part.strong_config.physics.width,
            new_part.strong_config.physics.height,
            new_part.strong_config.physics.width as f32,
            new_part.strong_config.physics.height as f32,
        ));

        commands


@@ 57,7 57,7 @@ fn handle_updated_temperature(
    mut updated_parts: Query<(&mut Sprite, &Temperature), (With<Part>, Changed<Temperature>)>
) {
    for (mut sprite, temperature) in updated_parts.iter_mut() {
        sprite.color = Color::srgb(1.0 + ((temperature.0 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
    }
}
fn handle_updated_parts(


@@ 72,10 72,10 @@ fn handle_updated_parts(
            &updated_part.strong_config.part.sprite_disconnected
        }));
        sprite.custom_size = Some(Vec2::new(
            updated_part.strong_config.physics.width,
            updated_part.strong_config.physics.height,
            updated_part.strong_config.physics.width as f32,
            updated_part.strong_config.physics.height as f32,
        ));
        sprite.color = Color::srgb(1.0 + ((temperature.0 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);

        commands
            .entity(updated_entity)


@@ 104,10 104,10 @@ fn update_part_sprites(

        let mut sprite = Sprite::from_image(asset_server.load(sprite));
        sprite.custom_size = Some(Vec2::new(
            part.strong_config.physics.width,
            part.strong_config.physics.height,
            part.strong_config.physics.width as f32,
            part.strong_config.physics.height as f32,
        ));
        sprite.color = Color::srgb(1.0 + ((temperature.0 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
        //sprite.color = Color::srgb(1.0, (700.0 - temperature.0) / 700.0, (700.0 - temperature.0) / 700.0);

        commands.entity(e).insert(sprite);


@@ 428,8 428,8 @@ fn update_drag_ghosts(

    let partvel = linvel.single().unwrap();
    let t_dt = dt.delta_secs();
    ghost.translation.x += partvel.x * t_dt;
    ghost.translation.y += partvel.y * t_dt;
    ghost.translation.x += partvel.x as f32 * t_dt;
    ghost.translation.y += partvel.y as f32 * t_dt;

    rsnap.0 = snap;
    rsnap.1 = best_self_snap;

M crates/unified/src/client/planet/incoming_planets.rs => crates/unified/src/client/planet/incoming_planets.rs +3 -3
@@ 13,7 13,7 @@ fn handle_incoming_planets(
) {
    for (new_entity, new_planet) in new_planets.iter() {
        let mut sprite = Sprite::from_image(asset_server.load(&new_planet.sprite));
        sprite.custom_size = Some(Vec2::new(new_planet.radius * 2.0, new_planet.radius * 2.0));
        sprite.custom_size = Some(Vec2::new(new_planet.radius as f32 * 2.0, new_planet.radius as f32 * 2.0));

        if let Some(SpecialSpriteProperties::ForceColor(c)) = new_planet.special_sprite_properties {
            sprite.color = c;


@@ 37,8 37,8 @@ fn handle_updated_planets(
    for (updated_entity, updated_planet) in updated_planets.iter() {
        let mut sprite = Sprite::from_image(asset_server.load(&updated_planet.sprite));
        sprite.custom_size = Some(Vec2::new(
            updated_planet.radius * 2.0,
            updated_planet.radius * 2.0,
            updated_planet.radius as f32 * 2.0,
            updated_planet.radius as f32 * 2.0,
        ));

        if let Some(SpecialSpriteProperties::ForceColor(c)) =

M crates/unified/src/client/starguide/init.rs => crates/unified/src/client/starguide/init.rs +2 -2
@@ 33,8 33,8 @@ pub fn player_init(
) {
    let mut sprite = Sprite::from_image(asset_server.load(&me.2.strong_config.part.sprite_connected));
    sprite.custom_size = Some(Vec2::new(
        me.2.strong_config.physics.width,
        me.2.strong_config.physics.height,
        me.2.strong_config.physics.width as f32,
        me.2.strong_config.physics.height as f32,
    ));
    commands.spawn((sprite, StarguideMe, STARGUIDE_LAYER,
            Transform::from_scale(Vec3::splat(10.0))));

M crates/unified/src/client/starguide/orbit.rs => crates/unified/src/client/starguide/orbit.rs +5 -5
@@ 1,4 1,4 @@
use std::f32::consts::PI;
use std::f64::consts::PI;


use crate::{config::planet::Planet, ecs::{Me, StarguideCamera, StarguideGizmos}, prelude::*, world_config::WorldConfigResource};


@@ 47,12 47,12 @@ fn update_orbits(
        p_transform = Some(*sun_transform);
        p_velocity = Some(*sun_velocity);
    }
    let p_mass = p_mass.unwrap();
    let p_mass = p_mass.unwrap() as f64;
    let p_transform = p_transform.unwrap();
    let p_velocity = p_velocity.unwrap();

    // orbit magic
    let rel_pos = me.0.translation - p_transform.translation;
    let rel_pos = (me.0.translation - p_transform.translation).as_dvec3();
    let rel_vel = me.1.0 - p_velocity.0;
    let u = world_config.world.gravity*p_mass;
    let h = rel_pos.x*rel_vel.y - rel_pos.y*rel_vel.x;


@@ 67,13 67,13 @@ fn update_orbits(
    let mut first_pos = None;
    let mut last_pos = None;
    for i in 0..200 {
        let theta = 2.0*PI*(i as f32)/200.0;
        let theta = 2.0*PI*(i as f64)/200.0;
        let r = (1.0/2.0) * ((f_x*f_x + f_y*f_y - 4.0*a*a) / (-2.0*a - f_x*theta.cos() - f_y*theta.sin()));

        if r < 0.0 { continue }

        // convert r to image coords
        let pos = Vec2::new(r*theta.cos(), r*theta.sin()) + p_transform.translation.truncate();
        let pos = Vec2::new(r as f32*theta.cos() as f32, r as f32*theta.sin() as f32) + p_transform.translation.truncate();

        if let Some(last_pos) = last_pos {
            gizmos.line_2d(last_pos, pos, Color::linear_rgb(1.0, 0.0, 0.0));

M crates/unified/src/config/part.rs => crates/unified/src/config/part.rs +10 -10
@@ 23,15 23,15 @@ pub struct PartPartConfig {
    pub name: String,
    pub sprite_connected: String,
    pub sprite_disconnected: String,
    pub emissivity: f32,
    pub specific_heat: f32,
    pub radiation_area: f32,
    pub emissivity: f64,
    pub specific_heat: f64,
    pub radiation_area: f64,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
pub struct PartPhysicsConfig {
    pub width: f32,
    pub height: f32,
    pub mass: f32,
    pub width: f64,
    pub height: f64,
    pub mass: f64,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
pub struct JointConfig {


@@ 46,8 46,8 @@ pub struct ThrusterConfig {
    pub apply_force_at_local: Vec2,
    pub thrust_vector: Vec2,

    pub exhaust_temperature: f32,
    pub heat_constant: f32,
    pub exhaust_temperature: f64,
    pub heat_constant: f64,
}
#[derive(Deserialize, Serialize, Clone, Debug, TypePath, PartialEq, Copy)]
pub struct JointOffset {


@@ 65,8 65,8 @@ impl From<JointOffset> for Transform {
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
pub struct CoolingConfig {
    pub cool_temperature: f32,
    pub heat_cooling_constant: f32,
    pub cool_temperature: f64,
    pub heat_cooling_constant: f64,
}
#[derive(Deserialize, TypePath, Serialize, Clone, Debug, PartialEq)]
pub struct CraftingConfig {

M crates/unified/src/config/planet.rs => crates/unified/src/config/planet.rs +3 -3
@@ 13,7 13,7 @@ pub struct Planet {
    pub name: String,
    pub sprite: String,
    pub indicator_sprite: Option<String>,
    pub radius: f32,
    pub radius: f64,
    pub mass: f32,
    pub default_transform: [f32; 3],
    pub planet_resource: Option<PlanetResource>,


@@ 48,7 48,7 @@ pub struct PlanetResource {
#[derive(Deserialize, TypePath, Serialize, Clone, Debug)]
pub struct OrbitData {
    pub orbiting: String,
    pub eccentricity: f32,
    pub eccentricity: f64,
}

#[derive(Deserialize, TypePath, Serialize, Clone, Debug)]


@@ 72,5 72,5 @@ pub struct PlanetConfigCollection {

#[derive(Deserialize, Asset, TypePath, Clone, Debug)]
pub struct OrbitConfig {
    pub planet_spring_compliance: f32
    pub planet_spring_compliance: f64
}

M crates/unified/src/config/world.rs => crates/unified/src/config/world.rs +8 -8
@@ 11,7 11,7 @@ pub struct GlobalWorldConfig {

#[derive(Deserialize, Asset, TypePath, Clone)]
pub struct WorldConfig {
    pub gravity: f32,
    pub gravity: f64,
    pub gravity_iterations: usize,
    pub spawn_parts_at: String,
    pub spawn_parts_interval_secs: f32,


@@ 19,13 19,13 @@ pub struct WorldConfig {

#[derive(Deserialize, Asset, TypePath, Clone, Debug)]
pub struct WPartConfig {
    pub default_width: f32,
    pub default_height: f32,
    pub default_mass: f32,
    pub joint_point_compliance: f32,
    pub joint_angle_compliance: f32,
    pub joint_distance_damping: f32,
    pub joint_angular_damping: f32,
    pub default_width: f64,
    pub default_height: f64,
    pub default_mass: f64,
    pub joint_point_compliance: f64,
    pub joint_angle_compliance: f64,
    pub joint_distance_damping: f64,
    pub joint_angular_damping: f64,
}

#[derive(Deserialize, Asset, TypePath, Clone)]

M crates/unified/src/ecs.rs => crates/unified/src/ecs.rs +5 -5
@@ 128,18 128,18 @@ pub struct CraftPartRequest {

#[derive(Component, Serialize, Deserialize, Debug)]
#[require(Replicated)]
pub struct Temperature(pub f32);
pub struct Temperature(pub f64);
#[derive(Component, Serialize, Deserialize, Debug)]
pub struct TemperatureSprite;
#[derive(Component, Serialize, Deserialize, Debug)]
pub struct Cooler {
    pub cool_temperature: f32,
    pub heat_cooling_constant: f32,
    pub cool_temperature: f64,
    pub heat_cooling_constant: f64,
}
#[derive(Component, Serialize, Deserialize, Debug)]
pub struct Radiator {
    pub emissivity: f32,
    pub surface_area: f32,
    pub emissivity: f64,
    pub surface_area: f64,
}

#[derive(Component, Serialize, Deserialize, Debug)]

M crates/unified/src/ecs/thruster.rs => crates/unified/src/ecs/thruster.rs +2 -2
@@ 35,8 35,8 @@ pub struct ThrusterOfPart(#[entities] pub Entity);
pub struct Thruster {
    pub id: ThrusterId,
    pub thrust_vector: Vec2,
    pub exhaust_temperature: f32,
    pub heat_constant: f32,
    pub exhaust_temperature: f64,
    pub heat_constant: f64,
}

#[derive(Bundle)]

M crates/unified/src/server/damping.rs => crates/unified/src/server/damping.rs +8 -7
@@ 1,4 1,5 @@
use avian2d::{dynamics::{joints::EntityConstraint, solver::{joint_damping, schedule::SubstepSolverSystems, solver_body::{SolverBody, SolverBodyInertia}}}, math::RecipOrZero};
use bevy::math::DVec2;

use crate::prelude::*;



@@ 13,8 14,8 @@ pub fn damping_plugin(app: &mut App) {

#[derive(Component, Debug, Clone, Copy)]
pub struct ModuleJointDamping {
    pub distance: f32,
    pub angular: f32,
    pub distance: f64,
    pub angular: f64,
}
fn module_joint_damping<T: Component + EntityConstraint<2>>(
    bodies: Query<(&mut SolverBody, &SolverBodyInertia, &Transform)>,


@@ 45,7 46,7 @@ fn module_joint_damping<T: Component + EntityConstraint<2>>(
        }

        let delta_omega = (body2.angular_velocity - body1.angular_velocity)
            * (damping.angular * delta_secs).min(1.0);
            * (damping.angular * delta_secs as f64).min(1.0);

        if !body1.flags.is_kinematic() {
            body1.angular_velocity += delta_omega;


@@ 54,16 55,16 @@ fn module_joint_damping<T: Component + EntityConstraint<2>>(
            body2.angular_velocity -= delta_omega;
        }

        let position1 = transform1.translation.truncate() + body1.delta_position;
        let position2 = transform2.translation.truncate() + body2.delta_position;
        let position1 = transform1.translation.truncate().as_dvec2() + body1.delta_position;
        let position2 = transform2.translation.truncate().as_dvec2() + body2.delta_position;

        let relative_position = position1 - position2;
        let relative_position: DVec2 = position1 - position2;

        let parallel_velocity1 = body1.linear_velocity.project_onto(relative_position.normalize());
        let parallel_velocity2 = body2.linear_velocity.project_onto(relative_position.normalize());
        
        let delta_v = (parallel_velocity2 - parallel_velocity1)
            * (damping.distance * delta_secs).min(1.0);
            * (damping.distance * delta_secs as f64).min(1.0);

        let w1 = inertia1.effective_inv_mass();
        let w2 = inertia2.effective_inv_mass();

M crates/unified/src/server/earth_parts.rs => crates/unified/src/server/earth_parts.rs +1 -1
@@ 40,7 40,7 @@ fn spawn_parts_on_earth(
        return;
    };
    let angle = rand::random::<f32>() * std::f32::consts::TAU;
    let offset = spawn_planet.radius + 150.0;
    let offset = spawn_planet.radius as f32 + 150.0;
    let mut new_transform = Transform::from_xyz(angle.cos() * offset, angle.sin() * offset, 0.0);
    new_transform.rotate_z(angle);
    new_transform.translation += spawn_planet_pos.translation;

M crates/unified/src/server/gravity.rs => crates/unified/src/server/gravity.rs +8 -8
@@ 22,23 22,23 @@ fn update_gravity(
    for (part_transform, part_velocity, part_mass, mut forces) in &mut part_query {
        *forces = ConstantForce::new(0.0, 0.0);

        let part_mass = part_mass.0;
        let part_mass = part_mass.0 as f64;
        let part_translation = part_transform.translation;

        for (planet_transform, planet_mass) in &planet_query {
            let planet_mass = planet_mass.0;
            let planet_mass = planet_mass.0 as f64;
            let planet_translation = planet_transform.translation;

            let distance = planet_translation.distance(part_translation);
            let distance = planet_translation.distance(part_translation) as f64;

            let mut x = 0.0;
            let mut total_f = 0.0;
            let mut v = part_velocity.0;
            let dt = time.delta_secs() / world_config.world.gravity_iterations as f32;
            let dt = time.delta_secs() as f64 / world_config.world.gravity_iterations as f64;
            for i in 0..world_config.world.gravity_iterations {
                let f =
                    world_config.world.gravity * ((part_mass * planet_mass) / distance.squared()-x);
                let dx = dt*(v.project_onto((planet_translation-part_translation).truncate())).length()
                    world_config.world.gravity * ((part_mass * planet_mass) / (distance*distance)-x);
                let dx = dt*(v.project_onto((planet_translation-part_translation).truncate().as_dvec2())).length()
                    - 1.0/2.0*(f/part_mass)*dt*dt;
                x += dx;
                v += f/part_mass*dt;


@@ 48,8 48,8 @@ fn update_gravity(
                    total_f += 2.0*f;
                }
            }
            let force = (dt/2.0*(total_f))/time.delta_secs();
            let direction = (planet_translation - part_translation).normalize() * force;
            let force = (dt/2.0*(total_f))/time.delta_secs() as f64;
            let direction = (planet_translation - part_translation).normalize().as_dvec3() * force;
            forces.x += direction.x;
            forces.y += direction.y;
        }

M crates/unified/src/server/heat/cooling.rs => crates/unified/src/server/heat/cooling.rs +1 -1
@@ 11,6 11,6 @@ fn cool_part(
) {
    for (mut temperature, cooler) in parts.iter_mut() {
        temperature.0 += cooler.heat_cooling_constant * (cooler.cool_temperature - temperature.0)
            * time.delta_secs();
            * time.delta_secs() as f64;
    }
}

M crates/unified/src/server/heat/radiation.rs => crates/unified/src/server/heat/radiation.rs +3 -3
@@ 1,7 1,7 @@
use crate::{attachment::PartInShip, ecs::{Part, Player, Radiator, Temperature}, prelude::*};

const STEFAN_BOLTZMANN: f32 = 5.670374419E-8;
const T_ENV: f32 = 4.0; // units: Kelvin
const STEFAN_BOLTZMANN: f64 = 5.670374419E-8;
const T_ENV: f64 = 4.0; // units: Kelvin

pub fn heat_radiation_plugin(app: &mut App) {
    app.add_systems(Update, part_radiation);


@@ 28,7 28,7 @@ fn part_radiation(
        let initial_temp = temperature.0;
        let k = (radiator.emissivity * STEFAN_BOLTZMANN * radiator.surface_area)
            / (part.strong_config.physics.mass * part.strong_config.part.specific_heat);
        let dt = time.delta_secs();
        let dt = time.delta_secs() as f64;

        // initial guess
        let mut next_temp = initial_temp;

M crates/unified/src/server/orbit/mod.rs => crates/unified/src/server/orbit/mod.rs +11 -12
@@ 1,6 1,7 @@
use std::collections::HashMap;
use std::f32::consts::PI;
use avian2d::math::TAU;
use std::f64::consts::PI;
use avian2d::dynamics::solver::solver_body::SolverBodyInertia;
use avian2d::math::{Scalar, TAU};
use avian2d::prelude::{LinearVelocity, Mass};
use bevy::log::debug;
use bevy::math::ops::atan2;


@@ 34,19 35,17 @@ fn update_orbits(
        // find parent
        let Some(parent) = planets_2.iter().find(|u| u.0.name == orbit_data.orbiting) else { continue; };

        let a = (planet.default_transform[0] - parent.0.default_transform[0]) / (1.0 - orbit_data.eccentricity);
        let a = (planet.default_transform[0] as f64 - parent.0.default_transform[0] as f64) / (1.0 - orbit_data.eccentricity);
        let e = orbit_data.eccentricity;
        let t = 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent.2))).sqrt();
        let t = 2.0*PI*((a*a*a)/(world_config.world.gravity*(**parent.2 as f64))).sqrt();

        let time = time.elapsed_secs();
        let time = time.elapsed_secs_f64();

        // calculate position of the planet
        let m = (TAU / t) * time;
        let e_k = iterative_kepler(m, e);
        let nu = 2.0 * atan2(
            (1.0 + e).sqrt() * (e_k / 2.0).sin(),
            (1.0 - e).sqrt() * (e_k / 2.0).cos(),
        );
        let nu = 2.0_f64 * ((1.0 + e).sqrt() * (e_k / 2.0).sin())
            .atan2((1.0 - e).sqrt() * (e_k / 2.0).cos());
        let r = a * (1.0 - e * e_k.cos());

        let x = r * nu.cos();


@@ 54,8 53,8 @@ fn update_orbits(

        // find the spring
        let Some(mut planet_spring) = planet_springs.iter_mut().find(|u| u.0.name == planet.name) else { continue; };
        planet_spring.1.translation.x = x + parent.1.translation.x;
        planet_spring.1.translation.y = y + parent.1.translation.y;
        planet_spring.1.translation.x = x as f32 + parent.1.translation.x;
        planet_spring.1.translation.y = y as f32 + parent.1.translation.y;

        let Some(parent_velocity) = parent_velocities.get(&orbit_data.orbiting) else { continue; };
        let de_dt = (TAU / t) / (1.0 - e * e_k.cos());


@@ 67,7 66,7 @@ fn update_orbits(
    }
}

fn iterative_kepler(m: f32, e: f32) -> f32 {
fn iterative_kepler(m: f64, e: f64) -> f64 {
    let mut output = m;
    for _ in 0..100 {
        let d = (m - output + e * output.sin()) / (1.0 - e * output.cos());

M crates/unified/src/server/part.rs => crates/unified/src/server/part.rs +1 -1
@@ 135,7 135,7 @@ fn calculate_bundle(config: &PartConfig, handle: &Handle<PartConfig>) -> impl Bu
    };
    let part_handle = PartHandle(handle.clone());
    let collider = Collider::rectangle(config.physics.width, config.physics.height);
    let mass = Mass(config.physics.mass);
    let mass = Mass(config.physics.mass as f32);
    let temperature = Temperature(298.0); // note that this is 25 degrees C
    let radiator = Radiator {
        emissivity: config.part.emissivity,

M crates/unified/src/server/planets.rs => crates/unified/src/server/planets.rs +4 -2
@@ 72,13 72,15 @@ pub fn update_planets(
                                    planet.default_transform[0],
                                    planet.default_transform[1],
                                    planet.default_transform[2],
                                )
                                ),
                                Mass(planet.mass),
                                RigidBody::Static,
                            )).id();
                            commands.spawn((
                                PlanetSpringJoint {
                                    name: planet.name.clone()
                                },
                                FixedJoint::new(planet_entity, spring).with_point_compliance(planet_config.orbit.planet_spring_compliance),
                                FixedJoint::new(planet_entity, spring)/*.with_point_compliance(planet_config.orbit.planet_spring_compliance)*/,
                            ));
                        }


M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +5 -5
@@ 9,7 9,7 @@ use crate::server::ConnectedNetworkEntity;
use crate::prelude::*;
use crate::world_config::WorldConfigResource;
use bevy_replicon::prelude::{ClientId, FromClient};
use std::f32::consts::PI;
use std::f64::consts::PI;

pub fn player_management_plugin(app: &mut App) {
    app.add_systems(


@@ 361,9 361,9 @@ fn dragging(
                .with_angle_compliance(world_config.part.joint_angle_compliance)
                .with_limit_compliance(world_config.part.joint_limit_compliance);*/
            let joint = FixedJoint::new(target_part.2, source_part.2)
                .with_local_anchor1(target_joint.2.translation.xy())
                .with_local_basis1(target_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 + PI
                    - source_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0)
                .with_local_anchor1(target_joint.2.translation.xy().into())
                .with_local_basis1(target_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64 + PI
                    - source_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64)
                .with_point_compliance(world_config.part.joint_point_compliance)
                .with_angle_compliance(world_config.part.joint_angle_compliance);
            let joint_damping = ModuleJointDamping {


@@ 398,7 398,7 @@ fn dragging(
            teleport_to_translation = target_position.translation.xy();
            teleport_to_rotation = target_position.rotation
                * source_joint.0.transform.rotation.inverse()
                * Quat::from_rotation_z(PI);
                * Quat::from_rotation_z(PI as f32);
            new_linvel = Some(*target_part.3);
            new_angvel = Some(*target_part.5);
            // and we're done!

M crates/unified/src/server/player/join.rs => crates/unified/src/server/player/join.rs +1 -1
@@ 29,7 29,7 @@ fn join_player(joined_player: Entity, mut commands: Commands, wc: &GlobalWorldCo
    let angle = rand::random::<f32>() * std::f32::consts::TAU;
    let offset = spawn_planet.radius + 150.0;
    let mut new_transform =
        Transform::from_xyz(angle.cos() * offset, angle.sin() * offset, 0.0);
        Transform::from_xyz(angle.cos() * offset as f32, angle.sin() * offset as f32, 0.0);
    new_transform.rotate_z(angle);
    new_transform.translation += spawn_planet_pos.translation;


M crates/unified/src/server/player/thrust.rs => crates/unified/src/server/player/thrust.rs +3 -3
@@ 95,11 95,11 @@ fn apply_thrust_solutions(

            // great, it's valid; apply the force and increase temperature
            let (mut part_forces, mut temperature) = parts.get_mut(parent_part.0).unwrap();
            temperature.0 += thruster_info.heat_constant * (thruster_info.exhaust_temperature - temperature.0) * time.delta_secs();
            temperature.0 += thruster_info.heat_constant * (thruster_info.exhaust_temperature - temperature.0) * time.delta_secs() as f64;

            part_forces.apply_force_at_point(
                (thruster_transform.rotation() * thruster_info.thrust_vector.extend(0.0)).xy(),
                thruster_transform.translation().xy(),
                (thruster_transform.rotation() * thruster_info.thrust_vector.extend(0.0)).xy().into(),
                thruster_transform.translation().xy().into(),
            );
        }
    }