From fa714335dc0d72452ffd60138bf85e418a37c85d Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Mon, 25 May 2026 07:50:33 +0200 Subject: [PATCH] don't touch velocity in other systems and don't use snapTarget --- src/lib/core/DynamicBodyComponent.h | 4 -- src/lib/core/EntityAdmin.cpp | 3 +- src/lib/sim/DynamicBodySystem.cpp | 23 ++++------- src/lib/sim/MovementIntentSystem.cpp | 60 +++++++++++----------------- 4 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/lib/core/DynamicBodyComponent.h b/src/lib/core/DynamicBodyComponent.h index 1ccb031..be28df9 100644 --- a/src/lib/core/DynamicBodyComponent.h +++ b/src/lib/core/DynamicBodyComponent.h @@ -1,7 +1,5 @@ #pragma once -#include - #include struct DynamicBodyComponent @@ -20,6 +18,4 @@ struct DynamicBodyComponent // --- written each tick by MovementIntentSystem, consumed by DynamicBodySystem --- QVector2D linearAcceleration; float angularAcceleration; - std::optional snapTarget; // set when snap-to-target will fire this tick }; - diff --git a/src/lib/core/EntityAdmin.cpp b/src/lib/core/EntityAdmin.cpp index 270f26c..45817bf 100644 --- a/src/lib/core/EntityAdmin.cpp +++ b/src/lib/core/EntityAdmin.cpp @@ -44,8 +44,7 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp, QVector2D(0.0f, 0.0f), // velocity 0.0f, // angularVelocity QVector2D(0.0f, 0.0f), // linearAcceleration - 0.0f, // angularAcceleration - std::nullopt // snapTarget + 0.0f // angularAcceleration }); add(entity, SensorRange{sensorRange}); add(entity, ShipIdentity{level, schematicId}); diff --git a/src/lib/sim/DynamicBodySystem.cpp b/src/lib/sim/DynamicBodySystem.cpp index 9885b2e..a95d99f 100644 --- a/src/lib/sim/DynamicBodySystem.cpp +++ b/src/lib/sim/DynamicBodySystem.cpp @@ -1,5 +1,6 @@ #include "DynamicBodySystem.h" +#include #include #include @@ -22,35 +23,27 @@ void DynamicBodySystem::tick(EntityAdmin& admin) admin.forEach( [](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body) { - // Integrate angular velocity and advance facing. + // Integrate angular velocity, clamp to max rotation speed, then advance facing. body.angularVelocity += body.angularAcceleration; + body.angularVelocity = std::max(-body.maxRotationSpeedPerTick, + std::min(body.angularVelocity, + body.maxRotationSpeedPerTick)); facing.radians = wrapAngle(facing.radians + body.angularVelocity); - // Integrate linear velocity. + // Integrate linear velocity and cap to max speed. body.velocity += body.linearAcceleration; - - // Speed cap. const float speed = body.velocity.length(); if (speed > body.maxSpeedPerTick) { body.velocity = body.velocity.normalized() * body.maxSpeedPerTick; } - // Snap to target or advance position. - if (body.snapTarget.has_value()) - { - pos.value = body.snapTarget.value(); - body.velocity = QVector2D(0.0f, 0.0f); - } - else - { - pos.value += body.velocity; - } + // Advance position. + pos.value += body.velocity; // Reset per-tick fields so stale values don't linger if the intent // system is skipped for this entity in a future tick. body.linearAcceleration = QVector2D(0.0f, 0.0f); body.angularAcceleration = 0.0f; - body.snapTarget = std::nullopt; }); } diff --git a/src/lib/sim/MovementIntentSystem.cpp b/src/lib/sim/MovementIntentSystem.cpp index 78041ec..d943c4b 100644 --- a/src/lib/sim/MovementIntentSystem.cpp +++ b/src/lib/sim/MovementIntentSystem.cpp @@ -27,11 +27,16 @@ void MovementIntentSystem::tick(EntityAdmin& admin) { if (intent.priority == 0) { - body.velocity = QVector2D(0.0f, 0.0f); - body.angularVelocity = 0.0f; - body.linearAcceleration = QVector2D(0.0f, 0.0f); - body.angularAcceleration = 0.0f; - body.snapTarget = std::nullopt; + // No movement intent: brake using available thrust. + const float linearBraking = std::min(body.velocity.length(), + body.maneuveringAccelerationPerTick); + body.linearAcceleration = (body.velocity.length() > 0.0001f) + ? -body.velocity.normalized() * linearBraking + : QVector2D(0.0f, 0.0f); + + const float angBraking = std::min(std::abs(body.angularVelocity), + body.angularAccelerationPerTick); + body.angularAcceleration = (body.angularVelocity >= 0.0f) ? -angBraking : angBraking; return; } @@ -40,9 +45,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin) if (dist < 0.001f) { - body.velocity = QVector2D(0.0f, 0.0f); - body.linearAcceleration = QVector2D(0.0f, 0.0f); - body.snapTarget = std::nullopt; + // Already at target: no new thrust. The ship drifts; it will + // re-approach next tick once it has moved away. + body.linearAcceleration = QVector2D(0.0f, 0.0f); + body.angularAcceleration = 0.0f; return; } @@ -55,9 +61,10 @@ void MovementIntentSystem::tick(EntityAdmin& admin) std::min(angleDiff, body.angularAccelerationPerTick)); float newAngVel = body.angularVelocity + rotDelta; - newAngVel = std::max(-body.maxRotationSpeedPerTick, - std::min(newAngVel, body.maxRotationSpeedPerTick)); + // Overshoot prevention: if the accumulated angular velocity already + // exceeds the remaining angle, snap it to exactly that angle so the + // ship doesn't rotate past its heading. const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f); if (sameSign && std::abs(newAngVel) > std::abs(angleDiff)) { @@ -65,12 +72,13 @@ void MovementIntentSystem::tick(EntityAdmin& admin) } body.angularAcceleration = newAngVel - body.angularVelocity; + // DynamicBodySystem applies the clamp to maxRotationSpeedPerTick after + // integrating, so we do not clamp here. // --- Linear acceleration --- // Use the projected facing (after this tick's angular integration) so // that the main thruster aligns with where the ship will actually be - // pointing when DynamicBodySystem applies the forces — matching the - // original single-pass ordering. + // pointing when DynamicBodySystem applies the forces. const float projectedRadians = wrapAngle(facing.radians + newAngVel); const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians)); @@ -82,40 +90,20 @@ void MovementIntentSystem::tick(EntityAdmin& admin) ? std::sqrt(2.0f * manAccel * dist) : body.maxSpeedPerTick; - const QVector2D desiredVel = delta.normalized() * desiredSpeed; - const QVector2D velError = desiredVel - body.velocity; + const QVector2D desiredVel = delta.normalized() * desiredSpeed; + const QVector2D velError = desiredVel - body.velocity; const float mainAligned = std::max(0.0f, QVector2D::dotProduct(velError, facingVec)); const float mainApplied = std::min(mainAligned, body.mainAccelerationPerTick); const QVector2D mainDelta = facingVec * mainApplied; - const QVector2D remaining = velError - mainDelta; - const float remainLen = remaining.length(); + const QVector2D remaining = velError - mainDelta; + const float remainLen = remaining.length(); const QVector2D maneuverDelta = (remainLen > manAccel) ? remaining.normalized() * manAccel : remaining; body.linearAcceleration = mainDelta + maneuverDelta; - - // --- Snap detection --- - // Compute the prospective velocity after integration + speed cap to - // determine whether the ship will reach (or pass) the target this tick. - - QVector2D prospectiveVel = body.velocity + body.linearAcceleration; - const float prospectiveSpeed = prospectiveVel.length(); - if (prospectiveSpeed > body.maxSpeedPerTick) - { - prospectiveVel = prospectiveVel.normalized() * body.maxSpeedPerTick; - } - - if (dist <= prospectiveVel.length()) - { - body.snapTarget = intent.target; - } - else - { - body.snapTarget = std::nullopt; - } }); }