From 5b28ab7eb0aeb4abadc99ba0d3969a21d1d27c5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:15:16 +0000 Subject: [PATCH 1/4] Initial plan From 42a0571bdf798f0de9fb159b11547b9e8c67ebfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:37:31 +0000 Subject: [PATCH 2/4] Add cell count output for Sensor cells in DetectCreature mode - Modified writeSignal() to accept and write numCellsSignal to SensorNumCells channel - Added convertNumCellsToSignal() for non-linear scaling (log2(numCells)/7.0) - Modified getMatchInfo() for DetectCreature mode to pack numCells in density field - Updated initialScan() and relocateLastMatch() to extract and convert numCells - Added 5 new tests for numCells output functionality Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com> --- source/EngineGpuKernels/SensorProcessor.cuh | 47 +++++- source/EngineTests/SensorTests.cpp | 171 ++++++++++++++++++++ 2 files changed, 212 insertions(+), 6 deletions(-) diff --git a/source/EngineGpuKernels/SensorProcessor.cuh b/source/EngineGpuKernels/SensorProcessor.cuh index 5d8dcb6ce..8e07a15f5 100644 --- a/source/EngineGpuKernels/SensorProcessor.cuh +++ b/source/EngineGpuKernels/SensorProcessor.cuh @@ -28,7 +28,9 @@ private: __inline__ __device__ static uint64_t pack(float distance, float angle, float density, uint16_t misc = 0); __inline__ __device__ static void unpack(float& distance, float& angle, float& density, uint16_t& misc, uint64_t bytes); - __inline__ __device__ static void writeSignal(Signal& signal, float angle, float density, float distance); + __inline__ __device__ static void writeSignal(Signal& signal, float angle, float density, float distance, float numCellsSignal = 0.0f); + + __inline__ __device__ static float convertNumCellsToSignal(uint32_t numCells); __inline__ __device__ static uint16_t convertAngleToUint16(float angle); __inline__ __device__ static float convertUint16ToAngle(uint16_t b); @@ -203,7 +205,16 @@ __inline__ __device__ void SensorProcessor::initialScan(SimulationData& data, Si auto refAngle = Math::angleOfVector(SignalProcessor::calcReferenceDirection(data, cell)); auto relAngle = Math::getNormalizedAngle(absAngle - refAngle - cell->frontAngle, -180.0f); - writeSignal(cell->signal, relAngle, density, distance); + + // For DetectCreature mode, density contains numCells - convert to signal + float numCellsSignal = 0.0f; + if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { + uint32_t numCells = static_cast(density); + numCellsSignal = convertNumCellsToSignal(numCells); + density = 1.0f; // Reset density to standard value for creature detection + } + + writeSignal(cell->signal, relAngle, density, distance, numCellsSignal); statistics.incNumSensorMatches(cell->color); auto matchPos = cell->pos + Math::unitVectorOfAngle(absAngle) * distance; @@ -298,7 +309,17 @@ __inline__ __device__ void SensorProcessor::relocateLastMatch(SimulationData& da auto targetPos = cell->pos + Math::unitVectorOfAngle(absAngle) * distance; auto relAngle = Math::getNormalizedAngle(absAngle - refAngle - cell->frontAngle, -180.0f); - writeSignal(cell->signal, relAngle, density, distance); + + // For DetectCreature mode, density contains numCells - convert to signal + float numCellsSignal = 0.0f; + float densityForSignal = density; + if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { + uint32_t numCells = static_cast(density); + numCellsSignal = convertNumCellsToSignal(numCells); + densityForSignal = 1.0f; // Reset density to standard value for creature detection + } + + writeSignal(cell->signal, relAngle, densityForSignal, distance, numCellsSignal); statistics.incNumSensorMatches(cell->color); @@ -384,7 +405,8 @@ SensorProcessor::getMatchInfo(SimulationData& data, Cell* cell, float2 const& sc if (matches) { uint16_t creatureIdPart = otherCell->creature != nullptr ? static_cast(otherCell->creature->id & 0xFFFF) : 0; - return pack(distance, absAngle, 1.0f, creatureIdPart); + uint32_t numCells = otherCell->creature != nullptr ? otherCell->creature->numCells : 0; + return pack(distance, absAngle, toFloat(numCells), creatureIdPart); } } otherCell = otherCell->nextCell; @@ -397,7 +419,8 @@ SensorProcessor::getMatchInfo(SimulationData& data, Cell* cell, float2 const& sc while (otherCell != nullptr) { if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == sensor.lastMatch.creatureId) { uint16_t creatureIdPart = otherCell->creature != nullptr ? static_cast(otherCell->creature->id & 0xffff) : 0; - return pack(distance, absAngle, 1.0f, creatureIdPart); + uint32_t numCells = otherCell->creature != nullptr ? otherCell->creature->numCells : 0; + return pack(distance, absAngle, toFloat(numCells), creatureIdPart); } otherCell = otherCell->nextCell; } @@ -422,12 +445,24 @@ __inline__ __device__ void SensorProcessor::unpack(float& distance, float& angle misc = static_cast(bytes & 0xFFFF); } -__inline__ __device__ void SensorProcessor::writeSignal(Signal& signal, float angle, float density, float distance) +__inline__ __device__ void SensorProcessor::writeSignal(Signal& signal, float angle, float density, float distance, float numCellsSignal) { signal.channels[Channels::SensorFoundResult] = 1; // Something found signal.channels[Channels::SensorAngle] = angle / 180.0f; // Angle: between -1.0 and 1.0 signal.channels[Channels::SensorDensity] = min(1.0f, density); // Normalized density (1.0 = 64 cells in 8x8 region) signal.channels[Channels::SensorDistance] = 1.0f - min(1.0f, distance / 256); // Distance: 1 = close, 0 = far away + signal.channels[Channels::SensorNumCells] = numCellsSignal; // Number of cells in detected creature (non-linear scale) +} + +// Convert numCells to a non-linear signal value in range [0, 1] +// Formula: log2(numCells) / 7.0, clamped to [0, 1] +// Examples: 2 cells -> ~0.14, 30 cells -> ~0.70, 60 cells -> ~0.85, 120 cells -> ~0.99 +__inline__ __device__ float SensorProcessor::convertNumCellsToSignal(uint32_t numCells) +{ + if (numCells <= 1) { + return 0.0f; + } + return min(1.0f, log2f(toFloat(numCells)) / 7.0f); } __inline__ __device__ uint16_t SensorProcessor::convertAngleToUint16(float angle) diff --git a/source/EngineTests/SensorTests.cpp b/source/EngineTests/SensorTests.cpp index 88d34cd61..ae1aad42f 100644 --- a/source/EngineTests/SensorTests.cpp +++ b/source/EngineTests/SensorTests.cpp @@ -1372,3 +1372,174 @@ TEST_F(SensorTests, telemetry_allOutputs) EXPECT_TRUE(velStrength > 0.5f); EXPECT_TRUE(velStrength < 0.7f); } + +/** + * Tests for SensorNumCells output in DetectCreature mode + */ +TEST_F(SensorTests, detectCreature_numCellsOutput_smallCreature) +{ + auto data = Description().addCreature(CreatureDescription().id(0).cells({ + CellDescription().id(1).pos({100.0f, 100.0f}).frontAngle(0.0f).cellType(SensorDescription().autoTriggerInterval(3).mode(DetectCreatureDescription())), + CellDescription().id(2).pos({101.0f, 100.0f}), + })); + data.addConnection(1, 2); + + // Create a small target creature with 4 cells + data.addCreature(CreatureDescription().id(1).cells({ + CellDescription().id(10).pos({100.0f, 80.0f}), + CellDescription().id(11).pos({101.0f, 80.0f}), + CellDescription().id(12).pos({100.0f, 81.0f}), + CellDescription().id(13).pos({101.0f, 81.0f}), + })); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(1); + + auto actualData = _simulationFacade->getSimulationData(); + auto actualSensor = actualData.getCellRef(1); + + EXPECT_TRUE(actualSensor._signalState == SignalState_Active); + EXPECT_TRUE(approxCompare(1.0f, actualSensor._signal._channels[Channels::SensorFoundResult])); + + // 4 cells: log2(4) / 7.0 = 2 / 7 ≈ 0.286 + auto numCellsSignal = actualSensor._signal._channels[Channels::SensorNumCells]; + EXPECT_TRUE(numCellsSignal > 0.2f); + EXPECT_TRUE(numCellsSignal < 0.4f); +} + +TEST_F(SensorTests, detectCreature_numCellsOutput_largeCreature) +{ + auto data = Description().addCreature(CreatureDescription().id(0).cells({ + CellDescription().id(1).pos({100.0f, 100.0f}).frontAngle(0.0f).cellType(SensorDescription().autoTriggerInterval(3).mode(DetectCreatureDescription())), + CellDescription().id(2).pos({101.0f, 100.0f}), + })); + data.addConnection(1, 2); + + // Create a large creature with 100 cells (10x10 grid) using helper + data.add(createLargeCreature()); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(1); + + auto actualData = _simulationFacade->getSimulationData(); + auto actualSensor = actualData.getCellRef(1); + + EXPECT_TRUE(actualSensor._signalState == SignalState_Active); + EXPECT_TRUE(approxCompare(1.0f, actualSensor._signal._channels[Channels::SensorFoundResult])); + + // 100 cells: log2(100) / 7.0 ≈ 6.64 / 7 ≈ 0.95 + auto numCellsSignal = actualSensor._signal._channels[Channels::SensorNumCells]; + EXPECT_TRUE(numCellsSignal > 0.85f); + EXPECT_TRUE(numCellsSignal < 1.0f); +} + +TEST_F(SensorTests, detectCreature_numCellsOutput_varyingSizes) +{ + // Test that larger creatures produce higher numCells signals + auto runTest = [this](int creatureSize) { + auto data = Description().addCreature(CreatureDescription().id(0).cells({ + CellDescription().id(1).pos({100.0f, 100.0f}).frontAngle(0.0f).cellType(SensorDescription().autoTriggerInterval(3).mode(DetectCreatureDescription())), + CellDescription().id(2).pos({101.0f, 100.0f}), + })); + data.addConnection(1, 2); + + // Create target creature with specified number of cells + std::vector targetCells; + for (int i = 0; i < creatureSize; ++i) { + targetCells.emplace_back(CellDescription().id(10 + i).pos({100.0f + toFloat(i % 10), 80.0f + toFloat(i / 10)})); + } + data.addCreature(CreatureDescription().id(1).cells(targetCells)); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(1); + + auto actualData = _simulationFacade->getSimulationData(); + auto actualSensor = actualData.getCellRef(1); + + EXPECT_TRUE(actualSensor._signalState == SignalState_Active); + return actualSensor._signal._channels[Channels::SensorNumCells]; + }; + + auto signal2Cells = runTest(2); + auto signal10Cells = runTest(10); + auto signal50Cells = runTest(50); + + // Verify non-linear scaling: larger creatures should have higher signals + EXPECT_TRUE(signal10Cells > signal2Cells); + EXPECT_TRUE(signal50Cells > signal10Cells); + + // Verify approximate expected values based on log2(n)/7 formula + // 2 cells: log2(2)/7 = 1/7 ≈ 0.14 + EXPECT_TRUE(signal2Cells > 0.1f); + EXPECT_TRUE(signal2Cells < 0.2f); + // 10 cells: log2(10)/7 ≈ 3.32/7 ≈ 0.47 + EXPECT_TRUE(signal10Cells > 0.4f); + EXPECT_TRUE(signal10Cells < 0.55f); + // 50 cells: log2(50)/7 ≈ 5.64/7 ≈ 0.81 + EXPECT_TRUE(signal50Cells > 0.75f); + EXPECT_TRUE(signal50Cells < 0.9f); +} + +TEST_F(SensorTests, detectCreature_numCellsOutput_relocation) +{ + // Test that numCells is also output correctly during relocation + auto data = Description().addCreature(CreatureDescription().id(0).cells({ + CellDescription().id(1).pos({100.0f, 100.0f}).frontAngle(0.0f).cellType(SensorDescription().autoTriggerInterval(3).mode(DetectCreatureDescription())), + CellDescription().id(2).pos({101.0f, 100.0f}), + })); + data.addConnection(1, 2); + + // Create a target creature with 25 cells (5x5 grid) + data.add(createLargeCreature({95.0f, 80.0f}, 5)); + + _simulationFacade->setSimulationData(data); + + // First scan - initial detection + _simulationFacade->calcTimesteps(1); + auto actualData = _simulationFacade->getSimulationData(); + auto actualSensor = actualData.getCellRef(1); + EXPECT_TRUE(approxCompare(1.0f, actualSensor._signal._channels[Channels::SensorFoundResult])); + auto initialNumCellsSignal = actualSensor._signal._channels[Channels::SensorNumCells]; + + // Second scan - relocation + _simulationFacade->calcTimesteps(3); // Wait for next trigger + actualData = _simulationFacade->getSimulationData(); + actualSensor = actualData.getCellRef(1); + + EXPECT_TRUE(actualSensor._signalState == SignalState_Active); + EXPECT_TRUE(approxCompare(1.0f, actualSensor._signal._channels[Channels::SensorFoundResult])); + + // numCells should be similar between initial scan and relocation + // 25 cells: log2(25)/7 ≈ 4.64/7 ≈ 0.66 + auto relocationNumCellsSignal = actualSensor._signal._channels[Channels::SensorNumCells]; + EXPECT_TRUE(relocationNumCellsSignal > 0.6f); + EXPECT_TRUE(relocationNumCellsSignal < 0.75f); + EXPECT_TRUE(approxCompare(initialNumCellsSignal, relocationNumCellsSignal)); +} + +TEST_P(SensorTests_AllDetectionModesExceptStructure, nonCreatureModes_numCellsZero) +{ + // For non-DetectCreature modes, numCells signal should be 0 + if (GetParam() == SensorMode_DetectCreature) { + GTEST_SKIP() << "This test is for non-creature detection modes only"; + } + + auto data = Description().cells({ + CellDescription().id(1).pos({100.0f, 100.0f}).frontAngle(0.0f).cellType(SensorDescription().autoTriggerInterval(3).mode(createModeWithDensity(GetParam()))), + CellDescription().id(2).pos({101.0f, 100.0f}), + }); + data.addConnection(1, 2); + + // Add detection targets above the sensor + addDetectionTargets(data, GetParam(), {98.0f, 20.0f}, 8); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(1); + + auto actualData = _simulationFacade->getSimulationData(); + auto actualSensor = actualData.getCellRef(1); + + EXPECT_TRUE(approxCompare(1.0f, actualSensor._signal._channels[Channels::SensorFoundResult])); + // For non-creature modes, numCells signal should be 0 + EXPECT_TRUE(approxCompare(0.0f, actualSensor._signal._channels[Channels::SensorNumCells])); +} From 322a54d98419a2556b701ad1eefb9cf042c0ce6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:34:04 +0000 Subject: [PATCH 3/4] Fix: Get numCells directly from detected creature instead of density field - Reverted getMatchInfo() to use 1.0f for density in DetectCreature mode - Modified initialScan() and relocateLastMatch() to look up the detected creature at the match position and get numCells directly from it - This properly derives numCells from the creature object as requested Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com> --- source/EngineGpuKernels/SensorProcessor.cuh | 42 ++++++++++++--------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/source/EngineGpuKernels/SensorProcessor.cuh b/source/EngineGpuKernels/SensorProcessor.cuh index 8e07a15f5..bd4d7f9fb 100644 --- a/source/EngineGpuKernels/SensorProcessor.cuh +++ b/source/EngineGpuKernels/SensorProcessor.cuh @@ -206,20 +206,25 @@ __inline__ __device__ void SensorProcessor::initialScan(SimulationData& data, Si auto refAngle = Math::angleOfVector(SignalProcessor::calcReferenceDirection(data, cell)); auto relAngle = Math::getNormalizedAngle(absAngle - refAngle - cell->frontAngle, -180.0f); - // For DetectCreature mode, density contains numCells - convert to signal + auto matchPos = cell->pos + Math::unitVectorOfAngle(absAngle) * distance; + data.cellMap.correctPosition(matchPos); + + // For DetectCreature mode, look up the creature at match position to get numCells float numCellsSignal = 0.0f; if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { - uint32_t numCells = static_cast(density); - numCellsSignal = convertNumCellsToSignal(numCells); - density = 1.0f; // Reset density to standard value for creature detection + auto otherCell = data.cellMap.getFirst(matchPos); + while (otherCell != nullptr) { + if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == creatureIdPart) { + numCellsSignal = convertNumCellsToSignal(otherCell->creature->numCells); + break; + } + otherCell = otherCell->nextCell; + } } writeSignal(cell->signal, relAngle, density, distance, numCellsSignal); statistics.incNumSensorMatches(cell->color); - auto matchPos = cell->pos + Math::unitVectorOfAngle(absAngle) * distance; - data.cellMap.correctPosition(matchPos); - // No relocation for structures if (cell->cellTypeData.sensor.mode != SensorMode_DetectStructure) { cell->cellTypeData.sensor.lastMatchAvailable = true; @@ -308,18 +313,23 @@ __inline__ __device__ void SensorProcessor::relocateLastMatch(SimulationData& da } auto targetPos = cell->pos + Math::unitVectorOfAngle(absAngle) * distance; + data.cellMap.correctPosition(targetPos); auto relAngle = Math::getNormalizedAngle(absAngle - refAngle - cell->frontAngle, -180.0f); - // For DetectCreature mode, density contains numCells - convert to signal + // For DetectCreature mode, look up the creature at target position to get numCells float numCellsSignal = 0.0f; - float densityForSignal = density; if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { - uint32_t numCells = static_cast(density); - numCellsSignal = convertNumCellsToSignal(numCells); - densityForSignal = 1.0f; // Reset density to standard value for creature detection + auto otherCell = data.cellMap.getFirst(targetPos); + while (otherCell != nullptr) { + if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == creatureIdPart) { + numCellsSignal = convertNumCellsToSignal(otherCell->creature->numCells); + break; + } + otherCell = otherCell->nextCell; + } } - writeSignal(cell->signal, relAngle, densityForSignal, distance, numCellsSignal); + writeSignal(cell->signal, relAngle, density, distance, numCellsSignal); statistics.incNumSensorMatches(cell->color); @@ -405,8 +415,7 @@ SensorProcessor::getMatchInfo(SimulationData& data, Cell* cell, float2 const& sc if (matches) { uint16_t creatureIdPart = otherCell->creature != nullptr ? static_cast(otherCell->creature->id & 0xFFFF) : 0; - uint32_t numCells = otherCell->creature != nullptr ? otherCell->creature->numCells : 0; - return pack(distance, absAngle, toFloat(numCells), creatureIdPart); + return pack(distance, absAngle, 1.0f, creatureIdPart); } } otherCell = otherCell->nextCell; @@ -419,8 +428,7 @@ SensorProcessor::getMatchInfo(SimulationData& data, Cell* cell, float2 const& sc while (otherCell != nullptr) { if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == sensor.lastMatch.creatureId) { uint16_t creatureIdPart = otherCell->creature != nullptr ? static_cast(otherCell->creature->id & 0xffff) : 0; - uint32_t numCells = otherCell->creature != nullptr ? otherCell->creature->numCells : 0; - return pack(distance, absAngle, toFloat(numCells), creatureIdPart); + return pack(distance, absAngle, 1.0f, creatureIdPart); } otherCell = otherCell->nextCell; } From 32834d5d5abb453b41fc108f337cec6ceff3b66b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:37:06 +0000 Subject: [PATCH 4/4] Refactor: Extract lookupCreatureNumCellsSignal helper function Extract the creature lookup logic into a helper function to reduce code duplication between initialScan() and relocateLastMatch() Co-authored-by: chrxh <73127001+chrxh@users.noreply.github.com> --- source/EngineGpuKernels/SensorProcessor.cuh | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/source/EngineGpuKernels/SensorProcessor.cuh b/source/EngineGpuKernels/SensorProcessor.cuh index bd4d7f9fb..7d582c617 100644 --- a/source/EngineGpuKernels/SensorProcessor.cuh +++ b/source/EngineGpuKernels/SensorProcessor.cuh @@ -31,6 +31,7 @@ private: __inline__ __device__ static void writeSignal(Signal& signal, float angle, float density, float distance, float numCellsSignal = 0.0f); __inline__ __device__ static float convertNumCellsToSignal(uint32_t numCells); + __inline__ __device__ static float lookupCreatureNumCellsSignal(SimulationData& data, float2 const& pos, uint16_t creatureIdPart); __inline__ __device__ static uint16_t convertAngleToUint16(float angle); __inline__ __device__ static float convertUint16ToAngle(uint16_t b); @@ -212,14 +213,7 @@ __inline__ __device__ void SensorProcessor::initialScan(SimulationData& data, Si // For DetectCreature mode, look up the creature at match position to get numCells float numCellsSignal = 0.0f; if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { - auto otherCell = data.cellMap.getFirst(matchPos); - while (otherCell != nullptr) { - if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == creatureIdPart) { - numCellsSignal = convertNumCellsToSignal(otherCell->creature->numCells); - break; - } - otherCell = otherCell->nextCell; - } + numCellsSignal = lookupCreatureNumCellsSignal(data, matchPos, creatureIdPart); } writeSignal(cell->signal, relAngle, density, distance, numCellsSignal); @@ -319,14 +313,7 @@ __inline__ __device__ void SensorProcessor::relocateLastMatch(SimulationData& da // For DetectCreature mode, look up the creature at target position to get numCells float numCellsSignal = 0.0f; if (cell->cellTypeData.sensor.mode == SensorMode_DetectCreature) { - auto otherCell = data.cellMap.getFirst(targetPos); - while (otherCell != nullptr) { - if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == creatureIdPart) { - numCellsSignal = convertNumCellsToSignal(otherCell->creature->numCells); - break; - } - otherCell = otherCell->nextCell; - } + numCellsSignal = lookupCreatureNumCellsSignal(data, targetPos, creatureIdPart); } writeSignal(cell->signal, relAngle, density, distance, numCellsSignal); @@ -473,6 +460,19 @@ __inline__ __device__ float SensorProcessor::convertNumCellsToSignal(uint32_t nu return min(1.0f, log2f(toFloat(numCells)) / 7.0f); } +// Look up the creature at the given position and return its numCells as a signal value +__inline__ __device__ float SensorProcessor::lookupCreatureNumCellsSignal(SimulationData& data, float2 const& pos, uint16_t creatureIdPart) +{ + auto otherCell = data.cellMap.getFirst(pos); + while (otherCell != nullptr) { + if (otherCell->creature != nullptr && (otherCell->creature->id & 0xffff) == creatureIdPart) { + return convertNumCellsToSignal(otherCell->creature->numCells); + } + otherCell = otherCell->nextCell; + } + return 0.0f; +} + __inline__ __device__ uint16_t SensorProcessor::convertAngleToUint16(float angle) { angle = Math::getNormalizedAngle(angle, -180.0f);