From 0cd05294688c9d4e3271258d4dbdef27a2aefd84 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Mon, 25 May 2026 07:25:54 +0200 Subject: [PATCH] split MovementSystem to MovementIntentSystem and DynamicBodySystem --- src/balancing/ArenaSimulation.cpp | 9 +- src/balancing/ArenaSimulation.h | 6 +- src/balancing/ArenaView.cpp | 9 +- src/lib/core/CMakeLists.txt | 1 + src/lib/core/DynamicBodyComponent.h | 25 ++++ src/lib/core/EcsComponents.h | 15 --- src/lib/core/EntityAdmin.cpp | 19 ++- src/lib/sim/CMakeLists.txt | 6 +- src/lib/sim/DynamicBodySystem.cpp | 56 ++++++++ .../{MovementSystem.h => DynamicBodySystem.h} | 2 +- src/lib/sim/MovementIntentSystem.cpp | 121 ++++++++++++++++++ src/lib/sim/MovementIntentSystem.h | 9 ++ src/lib/sim/MovementSystem.cpp | 105 --------------- src/lib/sim/ShipSystem.cpp | 3 +- src/lib/sim/Simulation.cpp | 12 +- src/lib/sim/Simulation.h | 6 +- src/test/BehaviorSystemTest.cpp | 24 ++-- src/test/ShipTest.cpp | 3 +- 18 files changed, 277 insertions(+), 154 deletions(-) create mode 100644 src/lib/core/DynamicBodyComponent.h create mode 100644 src/lib/sim/DynamicBodySystem.cpp rename src/lib/sim/{MovementSystem.h => DynamicBodySystem.h} (77%) create mode 100644 src/lib/sim/MovementIntentSystem.cpp create mode 100644 src/lib/sim/MovementIntentSystem.h delete mode 100644 src/lib/sim/MovementSystem.cpp diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index c8a2ce6..10d9261 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -12,7 +12,8 @@ #include "CombatSystem.h" #include "EcsComponents.h" #include "EntityAdmin.h" -#include "MovementSystem.h" +#include "DynamicBodySystem.h" +#include "MovementIntentSystem.h" #include "ScrapSystem.h" #include "Ship.h" #include "ShipSystem.h" @@ -45,7 +46,8 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig, m_shipSystem = std::make_unique(m_gameConfig, m_admin); m_aiSystem = std::make_unique(); - m_movementSystem = std::make_unique(); + m_movementIntentSystem = std::make_unique(); + m_dynamicBodySystem = std::make_unique(); m_combatSystem = std::make_unique(m_gameConfig); m_scrapSystem = std::make_unique(m_admin); @@ -255,7 +257,8 @@ void ArenaSimulation::tick() tickDeaths(); // Movement (tick step 10). - m_movementSystem->tick(m_admin); + m_movementIntentSystem->tick(m_admin); + m_dynamicBodySystem->tick(m_admin); // Scrap despawn (tick step 11). m_scrapSystem->tickDespawn(m_currentTick); diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index 603f732..f0c7716 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -21,7 +21,8 @@ class AiSystem; class BuildingSystem; class CombatSystem; -class MovementSystem; +class DynamicBodySystem; +class MovementIntentSystem; class ShipSystem; class ScrapSystem; @@ -92,7 +93,8 @@ private: std::unique_ptr m_buildingSystem; std::unique_ptr m_shipSystem; std::unique_ptr m_aiSystem; - std::unique_ptr m_movementSystem; + std::unique_ptr m_movementIntentSystem; + std::unique_ptr m_dynamicBodySystem; std::unique_ptr m_combatSystem; std::unique_ptr m_scrapSystem; diff --git a/src/balancing/ArenaView.cpp b/src/balancing/ArenaView.cpp index 4ce8522..efda8d6 100644 --- a/src/balancing/ArenaView.cpp +++ b/src/balancing/ArenaView.cpp @@ -10,6 +10,7 @@ #include "ArenaSimulation.h" #include "Building.h" #include "BuildingSystem.h" +#include "DynamicBodyComponent.h" #include "EcsComponents.h" #include "ScrapSystem.h" #include "Ship.h" @@ -314,9 +315,9 @@ void ArenaView::drawStations(QPainter& painter) void ArenaView::drawShips(QPainter& painter) { - m_sim->admin().forEach( + m_sim->admin().forEach( [&](entt::entity e, const ShipIdentity& /*si*/, const Position& pos, - const Velocity& vel, const Faction& fac) + const DynamicBodyComponent& body, const Faction& fac) { const bool hasCargo = m_sim->admin().hasAll(e); const bool hasRepair = m_sim->admin().hasAll(e); @@ -326,8 +327,8 @@ void ArenaView::drawShips(QPainter& painter) if (it == m_visuals->ships.end()) { return; } const QPointF center = worldToWidget(pos.value); - const QVector2D dir = (vel.value.length() > 0.0001f) - ? vel.value.normalized() + const QVector2D dir = (body.velocity.length() > 0.0001f) + ? body.velocity.normalized() : QVector2D(1.0f, 0.0f); const QVector2D perp(-dir.y(), dir.x()); diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 3d48f65..faf3587 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -4,6 +4,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/Rotation.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingType.h ${CMAKE_CURRENT_SOURCE_DIR}/EntityAdmin.h + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodyComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/EcsComponents.h ${CMAKE_CURRENT_SOURCE_DIR}/Blueprint.h ${CMAKE_CURRENT_SOURCE_DIR}/ItemType.h diff --git a/src/lib/core/DynamicBodyComponent.h b/src/lib/core/DynamicBodyComponent.h new file mode 100644 index 0000000..1ccb031 --- /dev/null +++ b/src/lib/core/DynamicBodyComponent.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +struct DynamicBodyComponent +{ + // --- dynamics parameters (formerly ShipDynamics) --- + float maxSpeedPerTick; + float mainAccelerationPerTick; + float maneuveringAccelerationPerTick; + float angularAccelerationPerTick; + float maxRotationSpeedPerTick; + + // --- integrated state --- + QVector2D velocity; + float angularVelocity; + + // --- 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/EcsComponents.h b/src/lib/core/EcsComponents.h index 2ffeeff..307fc74 100644 --- a/src/lib/core/EcsComponents.h +++ b/src/lib/core/EcsComponents.h @@ -35,24 +35,9 @@ struct Faction // Ship components (always present on every ship) // --------------------------------------------------------------------------- -struct Velocity -{ - QVector2D value; -}; - struct Facing { float radians; - float rotationSpeed; -}; - -struct ShipDynamics -{ - float maxSpeedPerTick; - float mainAccelerationPerTick; - float maneuveringAccelerationPerTick; - float angularAccelerationPerTick; - float maxRotationSpeedPerTick; }; struct SensorRange diff --git a/src/lib/core/EntityAdmin.cpp b/src/lib/core/EntityAdmin.cpp index d4f6b77..270f26c 100644 --- a/src/lib/core/EntityAdmin.cpp +++ b/src/lib/core/EntityAdmin.cpp @@ -1,5 +1,6 @@ #include "EntityAdmin.h" +#include "DynamicBodyComponent.h" #include "EcsComponents.h" #include "MovementIntent.h" @@ -33,11 +34,19 @@ entt::entity EntityAdmin::spawnShip(QVector2D position, float hp, float maxHp, add(entity, Position{position}); add(entity, Health{hp, maxHp}); add(entity, Faction{isEnemy}); - add(entity, Velocity{QVector2D(0.0f, 0.0f)}); - add(entity, Facing{0.0f, 0.0f}); - add(entity, ShipDynamics{ - maxSpeedPerTick, mainAccelPerTick, maneuveringAccelPerTick, - angularAccelPerTick, maxRotationSpeedPerTick}); + add(entity, Facing{0.0f}); + add(entity, DynamicBodyComponent{ + maxSpeedPerTick, + mainAccelPerTick, + maneuveringAccelPerTick, + angularAccelPerTick, + maxRotationSpeedPerTick, + QVector2D(0.0f, 0.0f), // velocity + 0.0f, // angularVelocity + QVector2D(0.0f, 0.0f), // linearAcceleration + 0.0f, // angularAcceleration + std::nullopt // snapTarget + }); add(entity, SensorRange{sensorRange}); add(entity, ShipIdentity{level, schematicId}); add(entity, MovementIntent{0, QVector2D(0.0f, 0.0f)}); diff --git a/src/lib/sim/CMakeLists.txt b/src/lib/sim/CMakeLists.txt index d70bb0d..65370bc 100644 --- a/src/lib/sim/CMakeLists.txt +++ b/src/lib/sim/CMakeLists.txt @@ -10,7 +10,8 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayoutBlueprint.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.h - ${CMAKE_CURRENT_SOURCE_DIR}/MovementSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.h + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.h ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.h @@ -25,7 +26,8 @@ SET(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ShipSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AiSystem.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/MovementSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MovementIntentSystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DynamicBodySystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ScrapSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/WaveSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CombatSystem.cpp diff --git a/src/lib/sim/DynamicBodySystem.cpp b/src/lib/sim/DynamicBodySystem.cpp new file mode 100644 index 0000000..9885b2e --- /dev/null +++ b/src/lib/sim/DynamicBodySystem.cpp @@ -0,0 +1,56 @@ +#include "DynamicBodySystem.h" + +#include + +#include + +#include "DynamicBodyComponent.h" +#include "EcsComponents.h" +#include "EntityAdmin.h" + +static float wrapAngle(float a) +{ + constexpr float kPi = 3.14159265f; + a = std::fmod(a, 2.0f * kPi); + if (a > kPi) { a -= 2.0f * kPi; } + if (a < -kPi) { a += 2.0f * kPi; } + return a; +} + +void DynamicBodySystem::tick(EntityAdmin& admin) +{ + admin.forEach( + [](entt::entity /*e*/, Position& pos, Facing& facing, DynamicBodyComponent& body) + { + // Integrate angular velocity and advance facing. + body.angularVelocity += body.angularAcceleration; + facing.radians = wrapAngle(facing.radians + body.angularVelocity); + + // Integrate linear velocity. + 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; + } + + // 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/MovementSystem.h b/src/lib/sim/DynamicBodySystem.h similarity index 77% rename from src/lib/sim/MovementSystem.h rename to src/lib/sim/DynamicBodySystem.h index 89fdae6..3fda8d2 100644 --- a/src/lib/sim/MovementSystem.h +++ b/src/lib/sim/DynamicBodySystem.h @@ -2,7 +2,7 @@ class EntityAdmin; -class MovementSystem +class DynamicBodySystem { public: void tick(EntityAdmin& admin); diff --git a/src/lib/sim/MovementIntentSystem.cpp b/src/lib/sim/MovementIntentSystem.cpp new file mode 100644 index 0000000..78041ec --- /dev/null +++ b/src/lib/sim/MovementIntentSystem.cpp @@ -0,0 +1,121 @@ +#include "MovementIntentSystem.h" + +#include +#include + +#include + +#include "DynamicBodyComponent.h" +#include "EcsComponents.h" +#include "EntityAdmin.h" +#include "MovementIntent.h" + +static float wrapAngle(float a) +{ + constexpr float kPi = 3.14159265f; + a = std::fmod(a, 2.0f * kPi); + if (a > kPi) { a -= 2.0f * kPi; } + if (a < -kPi) { a += 2.0f * kPi; } + return a; +} + +void MovementIntentSystem::tick(EntityAdmin& admin) +{ + admin.forEach( + [](entt::entity /*e*/, const Position& pos, const Facing& facing, + DynamicBodyComponent& body, const MovementIntent& intent) + { + 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; + return; + } + + const QVector2D delta = intent.target - pos.value; + const float dist = delta.length(); + + if (dist < 0.001f) + { + body.velocity = QVector2D(0.0f, 0.0f); + body.linearAcceleration = QVector2D(0.0f, 0.0f); + body.snapTarget = std::nullopt; + return; + } + + // --- Angular acceleration --- + + const float desiredAngle = std::atan2(delta.y(), delta.x()); + const float angleDiff = wrapAngle(desiredAngle - facing.radians); + + const float rotDelta = std::max(-body.angularAccelerationPerTick, + std::min(angleDiff, body.angularAccelerationPerTick)); + + float newAngVel = body.angularVelocity + rotDelta; + newAngVel = std::max(-body.maxRotationSpeedPerTick, + std::min(newAngVel, body.maxRotationSpeedPerTick)); + + const bool sameSign = (newAngVel >= 0.0f) == (angleDiff >= 0.0f); + if (sameSign && std::abs(newAngVel) > std::abs(angleDiff)) + { + newAngVel = angleDiff; + } + + body.angularAcceleration = newAngVel - body.angularVelocity; + + // --- 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. + + const float projectedRadians = wrapAngle(facing.radians + newAngVel); + const QVector2D facingVec(std::cos(projectedRadians), std::sin(projectedRadians)); + + const float manAccel = body.maneuveringAccelerationPerTick; + const float stoppingDist = (body.maxSpeedPerTick * body.maxSpeedPerTick) + / (2.0f * manAccel); + const float desiredSpeed = (dist <= stoppingDist) + ? std::sqrt(2.0f * manAccel * dist) + : body.maxSpeedPerTick; + + 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 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; + } + }); +} diff --git a/src/lib/sim/MovementIntentSystem.h b/src/lib/sim/MovementIntentSystem.h new file mode 100644 index 0000000..ba7c466 --- /dev/null +++ b/src/lib/sim/MovementIntentSystem.h @@ -0,0 +1,9 @@ +#pragma once + +class EntityAdmin; + +class MovementIntentSystem +{ +public: + void tick(EntityAdmin& admin); +}; diff --git a/src/lib/sim/MovementSystem.cpp b/src/lib/sim/MovementSystem.cpp deleted file mode 100644 index debbc5e..0000000 --- a/src/lib/sim/MovementSystem.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "MovementSystem.h" - -#include -#include - -#include - -#include "EcsComponents.h" -#include "EntityAdmin.h" -#include "MovementIntent.h" - -static float wrapAngle(float a) -{ - constexpr float kPi = 3.14159265f; - a = std::fmod(a, 2.0f * kPi); - if (a > kPi) { a -= 2.0f * kPi; } - if (a < -kPi) { a += 2.0f * kPi; } - return a; -} - -void MovementSystem::tick(EntityAdmin& admin) -{ - admin.forEach( - [](entt::entity /*e*/, Position& pos, Velocity& vel, Facing& facing, - ShipDynamics& dynamics, MovementIntent& intent) - { - if (intent.priority == 0) - { - vel.value = QVector2D(0.0f, 0.0f); - facing.rotationSpeed = 0.0f; - return; - } - - const QVector2D delta = intent.target - pos.value; - const float dist = delta.length(); - - if (dist < 0.001f) - { - vel.value = QVector2D(0.0f, 0.0f); - return; - } - - // Rotate toward target. - const float desiredAngle = std::atan2(delta.y(), delta.x()); - const float angleDiff = wrapAngle(desiredAngle - facing.radians); - - const float rotDelta = std::max(-dynamics.angularAccelerationPerTick, - std::min(angleDiff, dynamics.angularAccelerationPerTick)); - facing.rotationSpeed += rotDelta; - facing.rotationSpeed = std::max(-dynamics.maxRotationSpeedPerTick, - std::min(facing.rotationSpeed, dynamics.maxRotationSpeedPerTick)); - - const bool sameSign = (facing.rotationSpeed >= 0.0f) == (angleDiff >= 0.0f); - if (sameSign && std::abs(facing.rotationSpeed) > std::abs(angleDiff)) - { - facing.rotationSpeed = angleDiff; - } - - facing.radians = wrapAngle(facing.radians + facing.rotationSpeed); - - // Desired velocity (with braking near target). - const float manAccel = dynamics.maneuveringAccelerationPerTick; - const float stoppingDist = (dynamics.maxSpeedPerTick * dynamics.maxSpeedPerTick) - / (2.0f * manAccel); - const float desiredSpeed = (dist <= stoppingDist) - ? std::sqrt(2.0f * manAccel * dist) - : dynamics.maxSpeedPerTick; - const QVector2D desiredVel = delta.normalized() * desiredSpeed; - const QVector2D velError = desiredVel - vel.value; - - // Main acceleration: forward only, along facing. - const QVector2D facingVec(std::cos(facing.radians), std::sin(facing.radians)); - const float mainAligned = std::max(0.0f, - QVector2D::dotProduct(velError, facingVec)); - const float mainApplied = std::min(mainAligned, dynamics.mainAccelerationPerTick); - const QVector2D mainDelta = facingVec * mainApplied; - - // Maneuvering acceleration: any direction, handles the remainder. - const QVector2D remaining = velError - mainDelta; - const float remainLen = remaining.length(); - const QVector2D maneuverDelta = (remainLen > manAccel) - ? remaining.normalized() * manAccel - : remaining; - - vel.value += mainDelta + maneuverDelta; - - // Speed cap. - const float speed = vel.value.length(); - if (speed > dynamics.maxSpeedPerTick) - { - vel.value = vel.value.normalized() * dynamics.maxSpeedPerTick; - } - - // Snap to target or advance. - if (dist <= vel.value.length()) - { - pos.value = intent.target; - vel.value = QVector2D(0.0f, 0.0f); - } - else - { - pos.value += vel.value; - } - }); -} diff --git a/src/lib/sim/ShipSystem.cpp b/src/lib/sim/ShipSystem.cpp index 632d774..db0bcdb 100644 --- a/src/lib/sim/ShipSystem.cpp +++ b/src/lib/sim/ShipSystem.cpp @@ -4,6 +4,7 @@ #include #include +#include "DynamicBodyComponent.h" #include "EcsComponents.h" #include "EntityAdmin.h" #include "ModulesConfig.h" @@ -146,7 +147,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId, int level, QVecto }; Health& health = m_admin.get(entity); - ShipDynamics& dynamics = m_admin.get(entity); + DynamicBodyComponent& dynamics = m_admin.get(entity); SensorRange& sensor = m_admin.get(entity); applyMod(health.maxHp, "hp"); diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index c6d3566..b3ad5a9 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -6,7 +6,8 @@ #include "BuildingSystem.h" #include "EcsComponents.h" #include "CombatSystem.h" -#include "MovementSystem.h" +#include "DynamicBodySystem.h" +#include "MovementIntentSystem.h" #include "ScrapSystem.h" #include "ShipSystem.h" #include "SurfaceMask.h" @@ -47,7 +48,8 @@ Simulation::Simulation(GameConfig config, unsigned int seed) m_rng); m_shipSystem = std::make_unique(m_config, m_admin); m_aiSystem = std::make_unique(); - m_movementSystem = std::make_unique(); + m_movementIntentSystem = std::make_unique(); + m_dynamicBodySystem = std::make_unique(); m_scrapSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); @@ -114,7 +116,8 @@ void Simulation::reset(unsigned int seed) m_rng); m_shipSystem = std::make_unique(m_config, m_admin); m_aiSystem = std::make_unique(); - m_movementSystem = std::make_unique(); + m_movementIntentSystem = std::make_unique(); + m_dynamicBodySystem = std::make_unique(); m_scrapSystem = std::make_unique(m_admin); m_waveSystem = std::make_unique(m_config, m_rng); m_combatSystem = std::make_unique(m_config); @@ -181,7 +184,8 @@ void Simulation::tick() } // Step 10: advance ship positions - m_movementSystem->tick(m_admin); + m_movementIntentSystem->tick(m_admin); + m_dynamicBodySystem->tick(m_admin); // Step 11: scrap despawn m_scrapSystem->tickDespawn(m_currentTick); diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 624d6ba..53f19c8 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -23,7 +23,8 @@ class AiSystem; class BuildingSystem; class CombatSystem; -class MovementSystem; +class DynamicBodySystem; +class MovementIntentSystem; class ShipSystem; class ScrapSystem; class WaveSystem; @@ -124,7 +125,8 @@ private: std::unique_ptr m_buildingSystem; std::unique_ptr m_shipSystem; std::unique_ptr m_aiSystem; - std::unique_ptr m_movementSystem; + std::unique_ptr m_movementIntentSystem; + std::unique_ptr m_dynamicBodySystem; std::unique_ptr m_scrapSystem; std::unique_ptr m_waveSystem; std::unique_ptr m_combatSystem; diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 7a0f0d6..e06705f 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -10,9 +10,11 @@ #include "BuildingSystem.h" #include "BuildingType.h" #include "ConfigLoader.h" +#include "DynamicBodyComponent.h" +#include "DynamicBodySystem.h" #include "EcsComponents.h" #include "EntityAdmin.h" -#include "MovementSystem.h" +#include "MovementIntentSystem.h" #include "Rotation.h" #include "ScrapSystem.h" #include "Ship.h" @@ -38,9 +40,10 @@ struct Fixture EntityAdmin admin; BuildingSystem buildings; ShipSystem ships; - AiSystem ai; - MovementSystem movement; - ScrapSystem scraps; + AiSystem ai; + MovementIntentSystem movementIntent; + DynamicBodySystem dynamicBody; + ScrapSystem scraps; Tick tick; explicit Fixture() @@ -68,7 +71,8 @@ struct Fixture ai.tickThreatResponseBehavior(admin, buildings); ai.tickRepairBehavior(admin, buildings); ai.tickSalvageBehavior(admin, scraps, buildings); - movement.tick(admin); + movementIntent.tick(admin); + dynamicBody.tick(admin); ++tick; } }; @@ -115,9 +119,10 @@ TEST_CASE("BehaviorSystem: tickMovement advances ship by maxSpeedPerTick toward Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); - const float speed = f.admin.get(e).maxSpeedPerTick; + const float speed = f.admin.get(e).maxSpeedPerTick; f.admin.get(e) = MovementIntent{1, QVector2D(100.0f, 0.0f)}; - f.movement.tick(f.admin); + f.movementIntent.tick(f.admin); + f.dynamicBody.tick(f.admin); REQUIRE(pos(f.admin, e).value.x() == Approx(speed)); REQUIRE(pos(f.admin, e).value.y() == Approx(0.0f)); @@ -129,10 +134,11 @@ TEST_CASE("BehaviorSystem: tickMovement stops exactly at target without overshoo Fixture f; const entt::entity e = f.ships.spawn("interceptor", 1, QVector2D(0.0f, 0.0f)); - const float speed = f.admin.get(e).maxSpeedPerTick; + const float speed = f.admin.get(e).maxSpeedPerTick; const QVector2D target(speed * 0.5f, 0.0f); f.admin.get(e) = MovementIntent{1, target}; - f.movement.tick(f.admin); + f.movementIntent.tick(f.admin); + f.dynamicBody.tick(f.admin); REQUIRE(pos(f.admin, e).value.x() == Approx(target.x())); REQUIRE(pos(f.admin, e).value.y() == Approx(target.y())); diff --git a/src/test/ShipTest.cpp b/src/test/ShipTest.cpp index 022b93c..687b949 100644 --- a/src/test/ShipTest.cpp +++ b/src/test/ShipTest.cpp @@ -6,6 +6,7 @@ #include #include "ConfigLoader.h" +#include "DynamicBodyComponent.h" #include "EcsComponents.h" #include "EntityAdmin.h" #include "BuildingId.h" @@ -83,7 +84,7 @@ TEST_CASE("ShipSystem: interceptor level 0 maxSpeedPerTick matches formula / kTi // speed_formula = "200 + 5*x" at x=0 → 200; maxSpeedPerTick = 200/30 const float expected = 200.0f / static_cast(kTickRateHz); - REQUIRE(admin.get(e).maxSpeedPerTick == Approx(expected)); + REQUIRE(admin.get(e).maxSpeedPerTick == Approx(expected)); } // ---------------------------------------------------------------------------