From 9534647d6cc5c48f20c0d7a5f76a57d9c1ba5d19 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Wed, 1 Jul 2026 20:41:47 +0200 Subject: [PATCH 1/2] Remove numTimesSent and maxTimesSent from communicator Co-Authored-By: Claude Opus 4.8 --- source/EngineImpl/DescConverterService.cpp | 6 -- source/EngineInterface/CellTypeConstants.h | 2 - source/EngineInterface/Desc.h | 3 - .../EngineInterface/DescValidationService.cpp | 2 - source/EngineInterface/GenomeDesc.h | 1 - source/EngineInterface/GenomeDescHash.h | 1 - .../EngineKernels/CommunicatorProcessor.cuh | 20 +---- source/EngineKernels/DataAccessKernels.cu | 3 - source/EngineKernels/Entities.cuh | 2 - source/EngineKernels/EntityFactory.cuh | 5 -- source/EngineKernels/Genome.cuh | 1 - source/EngineKernels/GenomeTO.cuh | 1 - source/EngineKernels/MutationProcessor.cuh | 3 +- source/EngineKernels/NeuronProcessor.cuh | 14 ---- source/EngineKernels/TOs.cuh | 2 - source/EngineTestData/DescTestDataFactory.cpp | 9 +- .../CellTypeMutationCoverageGuard.cpp | 2 +- source/EngineTests/CommunicatorTests.cpp | 83 ++----------------- source/EngineTests/NeuronTests.cpp | 50 +++-------- source/Gui/InspectionWindow.cpp | 1 - source/Gui/NodeEditorWidget.cpp | 1 - .../PersisterInterface/SerializerService.cpp | 6 -- 22 files changed, 27 insertions(+), 191 deletions(-) diff --git a/source/EngineImpl/DescConverterService.cpp b/source/EngineImpl/DescConverterService.cpp index 234b3d749..23e622b17 100644 --- a/source/EngineImpl/DescConverterService.cpp +++ b/source/EngineImpl/DescConverterService.cpp @@ -570,7 +570,6 @@ ObjectDesc DescConverterService::createObjectDesc(TOs const& to, int objectIndex if (communicatorTO.mode == CommunicatorMode_Sender) { SenderDesc sender; sender._range = communicatorTO.modeData.sender.range; - sender._maxTimesSent = communicatorTO.modeData.sender.maxTimesSent; communicator._mode = sender; } else if (communicatorTO.mode == CommunicatorMode_Receiver) { ReceiverDesc receiver; @@ -612,7 +611,6 @@ ObjectDesc DescConverterService::createObjectDesc(TOs const& to, int objectIndex for (int i = 0; i < NEURONS_PER_CELL; ++i) { cellDesc._signal._channels[i] = objectTO.typeData.cell.signal.channels[i]; } - cellDesc._signal._numTimesSent = objectTO.typeData.cell.signal.numTimesSent; cellDesc._activationTime = objectTO.typeData.cell.activationTime; result._type = cellDesc; @@ -823,7 +821,6 @@ NodeDesc DescConverterService::createNodeDesc(TOs const& to, NodeTO const* nodeT if (communicatorTO.mode == CommunicatorMode_Sender) { SenderGenomeDesc sender; sender._range = communicatorTO.modeData.sender.range; - sender._maxTimesSent = communicatorTO.modeData.sender.maxTimesSent; communicatorDesc._mode = sender; } else if (communicatorTO.mode == CommunicatorMode_Receiver) { ReceiverGenomeDesc receiver; @@ -1201,7 +1198,6 @@ void DescConverterService::convertGenomeToTO( if (communicatorTO.mode == CommunicatorMode_Sender) { auto const& senderDesc = std::get(communicatorDesc._mode); communicatorTO.modeData.sender.range = static_cast(senderDesc._range); - communicatorTO.modeData.sender.maxTimesSent = senderDesc._maxTimesSent; } else if (communicatorTO.mode == CommunicatorMode_Receiver) { auto const& receiverDesc = std::get(communicatorDesc._mode); communicatorTO.modeData.receiver.restrictToColors = static_cast(receiverDesc._restrictToColors); @@ -1528,7 +1524,6 @@ void DescConverterService::convertObjectToTO( if (communicatorTO.mode == CommunicatorMode_Sender) { auto const& senderDesc = std::get(communicatorDesc._mode); communicatorTO.modeData.sender.range = static_cast(senderDesc._range); - communicatorTO.modeData.sender.maxTimesSent = senderDesc._maxTimesSent; } else if (communicatorTO.mode == CommunicatorMode_Receiver) { auto const& receiverDesc = std::get(communicatorDesc._mode); communicatorTO.modeData.receiver.restrictToColors = static_cast(receiverDesc._restrictToColors); @@ -1561,7 +1556,6 @@ void DescConverterService::convertObjectToTO( for (int i = 0; i < NEURONS_PER_CELL && i < numChannels; ++i) { objectTO.typeData.cell.signal.channels[i] = cellDesc._signal._channels[i]; } - objectTO.typeData.cell.signal.numTimesSent = cellDesc._signal._numTimesSent; } else { CHECK(false); } diff --git a/source/EngineInterface/CellTypeConstants.h b/source/EngineInterface/CellTypeConstants.h index b9bc49189..6e2ccba08 100644 --- a/source/EngineInterface/CellTypeConstants.h +++ b/source/EngineInterface/CellTypeConstants.h @@ -471,8 +471,6 @@ namespace Const auto constexpr CommunicatorRange_Min = 0; auto constexpr CommunicatorRange_Max = 20; auto constexpr CommunicatorRange_Default = 15; - auto constexpr CommunicatorMaxTimesSent_Min = 0; - auto constexpr CommunicatorMaxTimesSent_Default = 4; } using CommunicatorMode = int; diff --git a/source/EngineInterface/Desc.h b/source/EngineInterface/Desc.h index 5c6cb070f..1e574b832 100644 --- a/source/EngineInterface/Desc.h +++ b/source/EngineInterface/Desc.h @@ -409,7 +409,6 @@ struct SenderDesc auto operator<=>(SenderDesc const&) const = default; MEMBER(SenderDesc, int, range, Const::CommunicatorRange_Default); - MEMBER(SenderDesc, int, maxTimesSent, Const::CommunicatorMaxTimesSent_Default); }; struct ReceiverDesc @@ -459,8 +458,6 @@ struct SignalDesc SignalDesc& channels(std::vector const& value); std::vector _channels; - - MEMBER(SignalDesc, int, numTimesSent, 0); }; struct SolidDesc diff --git a/source/EngineInterface/DescValidationService.cpp b/source/EngineInterface/DescValidationService.cpp index bfd69d9d0..4820e1071 100644 --- a/source/EngineInterface/DescValidationService.cpp +++ b/source/EngineInterface/DescValidationService.cpp @@ -268,7 +268,6 @@ void DescValidationService::validateAndCorrect(GenomeDesc& genome) if (communicatorMode == CommunicatorMode_Sender) { auto& sender = std::get(communicator._mode); sender._range = std::clamp(sender._range, Const::CommunicatorRange_Min, Const::CommunicatorRange_Max); - sender._maxTimesSent = std::clamp(sender._maxTimesSent, Const::CommunicatorMaxTimesSent_Min, 10); } else if (communicatorMode == CommunicatorMode_Receiver) { auto& receiver = std::get(communicator._mode); receiver._restrictToColors = @@ -482,7 +481,6 @@ void DescValidationService::validateAndCorrect(ExtendedObjectDesc& extendedObjec if (communicatorMode == CommunicatorMode_Sender) { auto& sender = std::get(communicator._mode); sender._range = std::clamp(sender._range, 0, 20); - sender._maxTimesSent = std::max(sender._maxTimesSent, 0); } else if (communicatorMode == CommunicatorMode_Receiver) { auto& receiver = std::get(communicator._mode); receiver._restrictToColors &= (1 << MAX_COLORS) - 1; diff --git a/source/EngineInterface/GenomeDesc.h b/source/EngineInterface/GenomeDesc.h index 632acded8..bc0c18b13 100644 --- a/source/EngineInterface/GenomeDesc.h +++ b/source/EngineInterface/GenomeDesc.h @@ -343,7 +343,6 @@ struct SenderGenomeDesc auto operator<=>(SenderGenomeDesc const&) const = default; MEMBER(SenderGenomeDesc, int, range, Const::CommunicatorRange_Default); - MEMBER(SenderGenomeDesc, int, maxTimesSent, Const::CommunicatorMaxTimesSent_Default); }; struct ReceiverGenomeDesc diff --git a/source/EngineInterface/GenomeDescHash.h b/source/EngineInterface/GenomeDescHash.h index b4240df16..b4e955539 100644 --- a/source/EngineInterface/GenomeDescHash.h +++ b/source/EngineInterface/GenomeDescHash.h @@ -436,7 +436,6 @@ struct std::hash { std::size_t seed = 0; hash_combine(seed, desc._range); - hash_combine(seed, desc._maxTimesSent); return seed; } }; diff --git a/source/EngineKernels/CommunicatorProcessor.cuh b/source/EngineKernels/CommunicatorProcessor.cuh index 7fd39382f..7765c1ac6 100644 --- a/source/EngineKernels/CommunicatorProcessor.cuh +++ b/source/EngineKernels/CommunicatorProcessor.cuh @@ -14,7 +14,7 @@ private: __inline__ __device__ static void processCell(SimulationData& data, SimulationStatistics& statistics, Object* object); __inline__ __device__ static void processSender(SimulationData& data, SimulationStatistics& statistics, Object* object); - __inline__ __device__ static void tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject, int newNumTimesSent); + __inline__ __device__ static void tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject); }; /************************************************************************/ @@ -52,26 +52,15 @@ __device__ __inline__ void CommunicatorProcessor::processCell(SimulationData& da __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& data, SimulationStatistics& statistics, Object* object) { __shared__ int range; - __shared__ int maxTimesSent; - __shared__ int currentNumTimesSent; __shared__ float2 senderPos; if (threadIdx.x == 0) { auto& sender = object->typeData.cell.cellTypeData.communicator.modeData.sender; range = sender.range; - maxTimesSent = sender.maxTimesSent; - currentNumTimesSent = object->typeData.cell.signal.numTimesSent; senderPos = object->pos; } __syncthreads(); - // Check if signal can still be forwarded - if (currentNumTimesSent >= maxTimesSent) { - return; - } - - auto const newNumTimesSent = currentNumTimesSent + 1; - // Matching lambda to check if a cell is a valid receiver auto isMatch = [&object](Object* otherObject) { // Must be a communicator in receiver mode @@ -136,20 +125,19 @@ __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& auto const& otherRecord = records[otherIndex]; auto otherObject = otherRecord.self; if (isMatch(otherObject)) { - tryTransmitSignal(data, object, otherObject, newNumTimesSent); + tryTransmitSignal(data, object, otherObject); } otherIndex = otherRecord.nextObjectIndex; } } } -__inline__ __device__ void CommunicatorProcessor::tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject, int newNumTimesSent) +__inline__ __device__ void CommunicatorProcessor::tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject) { receiverObject->getLock(); - // Copy signal to receiver with incremented numTimesSent + // Copy signal to receiver copyChannels(receiverObject->typeData.cell.signal.channels, senderObject->typeData.cell.signal.channels); - receiverObject->typeData.cell.signal.numTimesSent = newNumTimesSent; // Translate angle in channel[1] from sender's reference direction to receiver's reference direction // The angle is encoded as value/180 degrees, where 1.0 = 180 deg and -1.0 = -180 deg diff --git a/source/EngineKernels/DataAccessKernels.cu b/source/EngineKernels/DataAccessKernels.cu index 5ceeae5a9..7a18fe9be 100644 --- a/source/EngineKernels/DataAccessKernels.cu +++ b/source/EngineKernels/DataAccessKernels.cu @@ -240,7 +240,6 @@ namespace nodeTO.cellTypeData.communicator.mode = node.cellTypeData.communicator.mode; if (node.cellTypeData.communicator.mode == CommunicatorMode_Sender) { nodeTO.cellTypeData.communicator.modeData.sender.range = node.cellTypeData.communicator.modeData.sender.range; - nodeTO.cellTypeData.communicator.modeData.sender.maxTimesSent = node.cellTypeData.communicator.modeData.sender.maxTimesSent; } else if (node.cellTypeData.communicator.mode == CommunicatorMode_Receiver) { nodeTO.cellTypeData.communicator.modeData.receiver.restrictToColors = node.cellTypeData.communicator.modeData.receiver.restrictToColors; @@ -354,7 +353,6 @@ namespace for (int i = 0; i < NEURONS_PER_CELL; ++i) { cellTO.signal.channels[i] = cell.signal.channels[i]; } - cellTO.signal.numTimesSent = cell.signal.numTimesSent; cellTO.activationTime = cell.activationTime; cellTO.lastUpdate = cell.lastUpdate; cellTO.concatenationIndex = cell.concatenationIndex; @@ -540,7 +538,6 @@ namespace cellTO.cellTypeData.communicator.mode = cell.cellTypeData.communicator.mode; if (cell.cellTypeData.communicator.mode == CommunicatorMode_Sender) { cellTO.cellTypeData.communicator.modeData.sender.range = cell.cellTypeData.communicator.modeData.sender.range; - cellTO.cellTypeData.communicator.modeData.sender.maxTimesSent = cell.cellTypeData.communicator.modeData.sender.maxTimesSent; } else if (cell.cellTypeData.communicator.mode == CommunicatorMode_Receiver) { cellTO.cellTypeData.communicator.modeData.receiver.restrictToColors = cell.cellTypeData.communicator.modeData.receiver.restrictToColors; cellTO.cellTypeData.communicator.modeData.receiver.restrictToLineage = cell.cellTypeData.communicator.modeData.receiver.restrictToLineage; diff --git a/source/EngineKernels/Entities.cuh b/source/EngineKernels/Entities.cuh index faf92d1cf..06d0a1f8b 100644 --- a/source/EngineKernels/Entities.cuh +++ b/source/EngineKernels/Entities.cuh @@ -382,7 +382,6 @@ struct Memory struct Sender { uint8_t range; - int maxTimesSent; }; struct Receiver @@ -424,7 +423,6 @@ union CellTypeData struct __align__(16) Signal { float channels[NEURONS_PER_CELL]; - int numTimesSent; }; struct uint32_float diff --git a/source/EngineKernels/EntityFactory.cuh b/source/EngineKernels/EntityFactory.cuh index fc42f0b0d..e5aaaf508 100644 --- a/source/EngineKernels/EntityFactory.cuh +++ b/source/EngineKernels/EntityFactory.cuh @@ -293,7 +293,6 @@ __inline__ __device__ Genome* EntityFactory::createGenomeFromTO(TOs const& to, i node.cellTypeData.communicator.mode = nodeTO.cellTypeData.communicator.mode; if (nodeTO.cellTypeData.communicator.mode == CommunicatorMode_Sender) { node.cellTypeData.communicator.modeData.sender.range = nodeTO.cellTypeData.communicator.modeData.sender.range; - node.cellTypeData.communicator.modeData.sender.maxTimesSent = nodeTO.cellTypeData.communicator.modeData.sender.maxTimesSent; } else if (nodeTO.cellTypeData.communicator.mode == CommunicatorMode_Receiver) { node.cellTypeData.communicator.modeData.receiver.restrictToColors = nodeTO.cellTypeData.communicator.modeData.receiver.restrictToColors; node.cellTypeData.communicator.modeData.receiver.restrictToLineage = nodeTO.cellTypeData.communicator.modeData.receiver.restrictToLineage; @@ -412,7 +411,6 @@ __inline__ __device__ void EntityFactory::changeObjectFromTO(TOs const& to, Obje for (int i = 0; i < NEURONS_PER_CELL; ++i) { cell->signal.channels[i] = cellTO.signal.channels[i]; } - cell->signal.numTimesSent = cellTO.signal.numTimesSent; cell->signalChanges = cellTO.signalChanges; cell->cellType = cellTO.cellType; @@ -577,7 +575,6 @@ __inline__ __device__ void EntityFactory::changeObjectFromTO(TOs const& to, Obje cell->cellTypeData.communicator.mode = cellTO.cellTypeData.communicator.mode; if (cellTO.cellTypeData.communicator.mode == CommunicatorMode_Sender) { cell->cellTypeData.communicator.modeData.sender.range = cellTO.cellTypeData.communicator.modeData.sender.range; - cell->cellTypeData.communicator.modeData.sender.maxTimesSent = cellTO.cellTypeData.communicator.modeData.sender.maxTimesSent; } else if (cellTO.cellTypeData.communicator.mode == CommunicatorMode_Receiver) { cell->cellTypeData.communicator.modeData.receiver.restrictToColors = cellTO.cellTypeData.communicator.modeData.receiver.restrictToColors; cell->cellTypeData.communicator.modeData.receiver.restrictToLineage = cellTO.cellTypeData.communicator.modeData.receiver.restrictToLineage; @@ -767,7 +764,6 @@ __inline__ __device__ Object* EntityFactory::createCellFromNode( for (int i = 0; i < NEURONS_PER_CELL; ++i) { cell.signal.channels[i] = 0.0f; } - cell.signal.numTimesSent = 0; cell.signalChanges = 0; cell.neuralNetwork = _data->entities.heap.getTypedSubArray(1); @@ -963,7 +959,6 @@ __inline__ __device__ Object* EntityFactory::createCellFromNode( communicator.mode = nodeCommunicator.mode; if (nodeCommunicator.mode == CommunicatorMode_Sender) { communicator.modeData.sender.range = nodeCommunicator.modeData.sender.range; - communicator.modeData.sender.maxTimesSent = nodeCommunicator.modeData.sender.maxTimesSent; } else if (nodeCommunicator.mode == CommunicatorMode_Receiver) { communicator.modeData.receiver.restrictToColors = nodeCommunicator.modeData.receiver.restrictToColors; communicator.modeData.receiver.restrictToLineage = nodeCommunicator.modeData.receiver.restrictToLineage; diff --git a/source/EngineKernels/Genome.cuh b/source/EngineKernels/Genome.cuh index 5e441ee97..cbc0a54a3 100644 --- a/source/EngineKernels/Genome.cuh +++ b/source/EngineKernels/Genome.cuh @@ -277,7 +277,6 @@ struct MemoryGenome struct SenderGenome { uint8_t range; - int maxTimesSent; }; struct ReceiverGenome diff --git a/source/EngineKernels/GenomeTO.cuh b/source/EngineKernels/GenomeTO.cuh index 9b521b597..d08f4cd46 100644 --- a/source/EngineKernels/GenomeTO.cuh +++ b/source/EngineKernels/GenomeTO.cuh @@ -282,7 +282,6 @@ struct MemoryGenomeTO struct SenderGenomeTO { uint8_t range; - int maxTimesSent; }; struct ReceiverGenomeTO diff --git a/source/EngineKernels/MutationProcessor.cuh b/source/EngineKernels/MutationProcessor.cuh index 73920defa..b3ed47549 100644 --- a/source/EngineKernels/MutationProcessor.cuh +++ b/source/EngineKernels/MutationProcessor.cuh @@ -462,7 +462,6 @@ __inline__ __device__ void MutationProcessor::applyMutations_cellTypeProperties( switch (node.cellTypeData.communicator.mode) { case CommunicatorMode_Sender: mutateNumber(node.cellTypeData.communicator.modeData.sender.range, Const::CommunicatorRange_Min, Const::CommunicatorRange_Max); - mutateNumber(node.cellTypeData.communicator.modeData.sender.maxTimesSent, Const::CommunicatorMaxTimesSent_Min, 10); break; case CommunicatorMode_Receiver: mutateBitset(node.cellTypeData.communicator.modeData.receiver.restrictToColors, Const::RestrictToColors_Max); @@ -583,7 +582,7 @@ __inline__ __device__ void MutationProcessor::resetCellTypeModeToDefault(Node& n auto& communicator = node.cellTypeData.communicator; switch (communicator.mode) { case CommunicatorMode_Sender: - communicator.modeData.sender = {Const::CommunicatorRange_Default, Const::CommunicatorMaxTimesSent_Default}; + communicator.modeData.sender = {Const::CommunicatorRange_Default}; break; case CommunicatorMode_Receiver: communicator.modeData.receiver = {Const::RestrictToColors_Default, LineageRestriction_No}; diff --git a/source/EngineKernels/NeuronProcessor.cuh b/source/EngineKernels/NeuronProcessor.cuh index 04a16cccb..64b115d1f 100644 --- a/source/EngineKernels/NeuronProcessor.cuh +++ b/source/EngineKernels/NeuronProcessor.cuh @@ -76,7 +76,6 @@ __inline__ __device__ void NeuronProcessor::setSignal(SimulationData& data) cell.signalChanges = static_cast(min(255.0f, channelDeviations * 255 / 2)); copyChannels(cell.signal.channels, cell.futureSignal.channels); - cell.signal.numTimesSent = cell.futureSignal.numTimesSent; } } @@ -85,7 +84,6 @@ __inline__ __device__ void NeuronProcessor::clearSignal(Object* object) for (int i = 0; i < NEURONS_PER_CELL; ++i) { object->typeData.cell.signal.channels[i] = 0; } - object->typeData.cell.signal.numTimesSent = 0; } __inline__ __device__ bool NeuronProcessor::isAutoTriggered(SimulationData& data, Object* object, uint32_t autoTriggerInterval, bool isPreview) @@ -139,15 +137,11 @@ __inline__ __device__ void NeuronProcessor::processCell(Object* object, bool ini int numConnections = object->numConnections; __shared__ __align__(16) float sharedAccumulatedInput[NEURONS_PER_CELL]; - __shared__ int sharedMinNumTimesSent; // Init variables if (laneId < NEURONS_PER_CELL) { sharedAccumulatedInput[laneId] = 0.0f; } - if (laneId == 0) { - sharedMinNumTimesSent = (numConnections > 0) ? INT_MAX : 0; - } block.sync(); // Accumulate weighted inputs from all connected cells @@ -162,10 +156,6 @@ __inline__ __device__ void NeuronProcessor::processCell(Object* object, bool ini continue; } - if (laneId == 0) { - sharedMinNumTimesSent = min(sharedMinNumTimesSent, connectedCell.signal.numTimesSent); - } - if (laneId < NEURONS_PER_CELL) { sharedAccumulatedInput[laneId] += connectedCell.signal.channels[laneId] * cell.neuralNetwork->connectionWeights[connIdx]; } @@ -197,10 +187,6 @@ __inline__ __device__ void NeuronProcessor::processCell(Object* object, bool ini cell.futureSignal.channels[row] = result; } - - if (laneId == 0) { - cell.futureSignal.numTimesSent = sharedMinNumTimesSent; - } } __inline__ __device__ float NeuronProcessor::applyActivationFunction(ActivationFunction activationFunction, float x) diff --git a/source/EngineKernels/TOs.cuh b/source/EngineKernels/TOs.cuh index 870f1a882..63c51cbcb 100644 --- a/source/EngineKernels/TOs.cuh +++ b/source/EngineKernels/TOs.cuh @@ -353,7 +353,6 @@ struct MemoryTO struct SenderTO { uint8_t range; - int maxTimesSent; }; struct ReceiverTO @@ -395,7 +394,6 @@ union CellTypeDataTO struct SignalTO { float channels[NEURONS_PER_CELL]; - int numTimesSent; }; struct SolidTO diff --git a/source/EngineTestData/DescTestDataFactory.cpp b/source/EngineTestData/DescTestDataFactory.cpp index da1bc8a16..e05efb324 100644 --- a/source/EngineTestData/DescTestDataFactory.cpp +++ b/source/EngineTestData/DescTestDataFactory.cpp @@ -78,7 +78,7 @@ ObjectDesc DescTestDataFactory::createNonDefaultObjectDesc(ObjectParameter objec .headUpdateId(13) .headCell(true) .parentNodeIndex(14) - .signal(SignalDesc().channels({1, 0, 0.6f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}).numTimesSent(42)) + .signal(SignalDesc().channels({1, 0, 0.6f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) .constructor(ConstructorDesc() .autoTriggerInterval(55) .geneIndex(1) @@ -621,9 +621,6 @@ bool DescTestDataFactory::compare(ObjectDesc const& object, NodeDesc const& node if (sender._range != nodeSender._range) { return false; } - if (sender._maxTimesSent != nodeSender._maxTimesSent) { - return false; - } } break; case CommunicatorMode_Receiver: { auto const& receiver = std::get(communicator._mode); @@ -826,7 +823,7 @@ CellTypeDesc DescTestDataFactory::createNonDefaultCellTypeDesc(ObjectParameter o CommunicatorModeDesc communicatorModeDesc; switch (communicatorMode) { case CommunicatorMode_Sender: - communicatorModeDesc = SenderDesc().range(150).maxTimesSent(6); + communicatorModeDesc = SenderDesc().range(150); break; case CommunicatorMode_Receiver: communicatorModeDesc = ReceiverDesc().restrictToColors(1 << 2).restrictToLineage(LineageRestriction_UnrelatedLineage); @@ -992,7 +989,7 @@ CellTypeGenomeDesc DescTestDataFactory::createNonDefaultCellTypeGenomeDesc(NodeP CommunicatorModeGenomeDesc communicatorModeDesc; switch (communicatorMode) { case CommunicatorMode_Sender: - communicatorModeDesc = SenderGenomeDesc().range(200).maxTimesSent(8); + communicatorModeDesc = SenderGenomeDesc().range(200); break; case CommunicatorMode_Receiver: communicatorModeDesc = ReceiverGenomeDesc().restrictToColors(1 << 5).restrictToLineage(LineageRestriction_RelatedLineage); diff --git a/source/EngineTests/CellTypeMutationCoverageGuard.cpp b/source/EngineTests/CellTypeMutationCoverageGuard.cpp index 109748040..8db156138 100644 --- a/source/EngineTests/CellTypeMutationCoverageGuard.cpp +++ b/source/EngineTests/CellTypeMutationCoverageGuard.cpp @@ -79,7 +79,7 @@ ALIEN_MUTATION_FIELD_COUNT(SignalStorageGenomeDesc, 1); ALIEN_MUTATION_FIELD_COUNT(SignalIntegratorGenomeDesc, 1); // --- Communicator modes (switch (node.cellTypeData.communicator.mode)) --- -ALIEN_MUTATION_FIELD_COUNT(SenderGenomeDesc, 2); +ALIEN_MUTATION_FIELD_COUNT(SenderGenomeDesc, 1); ALIEN_MUTATION_FIELD_COUNT(ReceiverGenomeDesc, 2); // --- Number of modes per cell type --- diff --git a/source/EngineTests/CommunicatorTests.cpp b/source/EngineTests/CommunicatorTests.cpp index e1f3c369b..3c08684d9 100644 --- a/source/EngineTests/CommunicatorTests.cpp +++ b/source/EngineTests/CommunicatorTests.cpp @@ -27,7 +27,7 @@ class CommunicatorTests : public IntegrationTestFramework protected: // Helper to create a sender creature with 2 cells (sender + helper for signal) - Desc createSenderCreature(uint64_t creatureId, RealVector2D pos, int range = 50, int maxTimesSent = 4, int color = 0) + Desc createSenderCreature(uint64_t creatureId, RealVector2D pos, int range = 50, int color = 0) { auto data = Desc().addCreature( { @@ -37,7 +37,7 @@ class CommunicatorTests : public IntegrationTestFramework .color(color) .type(CellDesc() .neuralNetwork(NeuralNetDesc().biases({1.0f, 0.5f, 2.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) - .cellType(CommunicatorDesc().mode(SenderDesc().range(range).maxTimesSent(maxTimesSent)))), + .cellType(CommunicatorDesc().mode(SenderDesc().range(range)))), ObjectDesc().id(creatureId * 100 + 1).pos({pos.x + 1.0f, pos.y}).color(color), }, CreatureDesc().id(creatureId)); @@ -127,7 +127,7 @@ TEST_F(CommunicatorTests, sender_sameCreatureReceiver_noSignalTransmitted) auto data = Desc().addCreature( { ObjectDesc().id(0).pos({99.0f, 100.0f}).type(CellDesc().signal({1.0f, 2.0f, 3.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), - ObjectDesc().id(1).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(4)))), + ObjectDesc().id(1).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc().id(2).pos({110.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(ReceiverDesc()))), }, CreatureDesc().id(1)); @@ -166,36 +166,10 @@ TEST_F(CommunicatorTests, sender_multipleReceiversInRange_allReceiveSignal) } } -TEST_F(CommunicatorTests, sender_maxTimesSentExceeded_noSignalTransmitted) -{ - // Create sender in creature 1 with signal that has numTimesSent = 2 (equal to maxTimesSent) - auto data = Desc().addCreature( - { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(2)))), - ObjectDesc() - .id(101) - .pos({101.0f, 100.0f}) - .type(CellDesc().signal(SignalDesc().numTimesSent(2).channels({1.0f, 2.0f, 3.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), - }, - CreatureDesc().id(1)); - data.addConnection(100, 101); - - // Create receiver - data.add(createReceiverCreature(2, {110.0f, 100.0f}), false); - - _simulationFacade->setSimulationData(data); - _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); - - auto result = _simulationFacade->getSimulationData(); - auto receiver = result.getObjectRef(200); - - // Receiver should NOT have received the signal (maxTimesSent exceeded) -} - TEST_F(CommunicatorTests, sender_receiverColorRestriction_matchingColor) { // Create sender with color 2 - auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 4, 2); + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 2); // Create receiver that only accepts color 2 data.add(createReceiverCreature(2, {110.0f, 100.0f}, 1 << 2), false); @@ -213,7 +187,7 @@ TEST_F(CommunicatorTests, sender_receiverColorRestriction_matchingColor) TEST_F(CommunicatorTests, sender_receiverColorRestriction_mismatchingColor) { // Create sender with color 3 - auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 4, 3); + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 3); // Create receiver that only accepts color 2 data.add(createReceiverCreature(2, {110.0f, 100.0f}, 1 << 2), false); @@ -233,7 +207,7 @@ TEST_F(CommunicatorTests, sender_noActiveSignal_noTransmission) // Create sender without active signal auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(4)))), + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), // No signalAndState set, so signal is not active ObjectDesc().id(101).pos({101.0f, 100.0f}), }, @@ -286,47 +260,6 @@ TEST_F(CommunicatorTests, sender_signalPriority_signalReceived) EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] != 0.0f); } -TEST_F(CommunicatorTests, sender_signalPriority_lowerNumTimesSentWins) -{ - // Create first sender with numTimesSent = 3 - auto data = Desc().addCreature( - { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(10)))), - ObjectDesc() - .id(101) - .pos({101.0f, 100.0f}) - .type(CellDesc().signal(SignalDesc().numTimesSent(3).channels({1.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), - }, - CreatureDesc().id(1)); - data.addConnection(100, 101); - - // Create second sender with numTimesSent = 1 (higher priority) - data.addCreature( - { - ObjectDesc().id(200).pos({100.0f, 120.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(10)))), - ObjectDesc() - .id(201) - .pos({101.0f, 120.0f}) - .type(CellDesc().signal(SignalDesc().numTimesSent(1).channels({-1.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), - }, - CreatureDesc().id(2)); - data.addConnection(200, 201); - - // Create receiver - data.add(createReceiverCreature(3, {100.0f, 110.0f}), false); - - _simulationFacade->setSimulationData(data); - _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); - - auto result = _simulationFacade->getSimulationData(); - auto receiver = result.getObjectRef(300); - - // Receiver should have received the signal - // The numTimesSent should be the lower one + 1 = 2 - EXPECT_EQ(receiver.getCellRef()._signal._numTimesSent, 2); - EXPECT_EQ(receiver.getCellRef()._signal._channels[0], -1.0f); -} - /** * Parameterized test for angle translation with different reference direction differences. * Parameter: receiverRefAngleDiff - the difference in degrees between sender and receiver reference directions. @@ -351,7 +284,7 @@ TEST_P(CommunicatorTests_AngleTranslation, sender_angleTranslation) auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(4)))), + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc() .id(101) .pos({101.0f, 100.0f}) @@ -419,7 +352,7 @@ TEST_P(CommunicatorTests_LineageRestriction, sender_lineageRestriction) auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(4)))), + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc().id(101).pos({101.0f, 100.0f}).type(CellDesc().signal(SignalDesc().channels({1.0f, 0.5f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), }, CreatureDesc().id(1).lineageId(senderLineageId), diff --git a/source/EngineTests/NeuronTests.cpp b/source/EngineTests/NeuronTests.cpp index d83796c97..9153606cb 100644 --- a/source/EngineTests/NeuronTests.cpp +++ b/source/EngineTests/NeuronTests.cpp @@ -68,11 +68,10 @@ TEST_F(NeuronTests, forwardSignalByDefault) auto signal1 = getExampleSignal1(); auto signal2 = getExampleSignal2(); - // Set different numTimesSent values on input signals auto data = Desc() .addCreature({ - ObjectDesc().id(1).pos({0, 0}).type(CellDesc().signal(SignalDesc().channels(signal2).numTimesSent(3))), - ObjectDesc().id(2).pos({0, 1}).type(CellDesc().signal(SignalDesc().channels(signal1).numTimesSent(7))), + ObjectDesc().id(1).pos({0, 0}).type(CellDesc().signal(SignalDesc().channels(signal2))), + ObjectDesc().id(2).pos({0, 1}).type(CellDesc().signal(SignalDesc().channels(signal1))), }) .addConnection(1, 2); @@ -83,11 +82,6 @@ TEST_F(NeuronTests, forwardSignalByDefault) EXPECT_TRUE(approxCompare(signal1, actualData.getObjectRef(1).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(signal2, actualData.getObjectRef(2).getCellRef()._signal._channels)); - - // Check numTimesSent: cell 1 receives from cell 2 (numTimesSent=7), so its numTimesSent should be 7 - // Cell 2 receives from cell 1 (numTimesSent=3), so its numTimesSent should be 3 - EXPECT_EQ(actualData.getObjectRef(1).getCellRef()._signal._numTimesSent, 7); - EXPECT_EQ(actualData.getObjectRef(2).getCellRef()._signal._numTimesSent, 3); } TEST_F(NeuronTests, forwardSignalByDefault_preview) @@ -95,11 +89,10 @@ TEST_F(NeuronTests, forwardSignalByDefault_preview) auto signal1 = getExampleSignal1(); auto signal2 = getExampleSignal2(); - // Set different numTimesSent values on input signals auto data = Desc() .addCreature({ - ObjectDesc().id(1).pos({0, 0}).type(CellDesc().signal(SignalDesc().channels(signal2).numTimesSent(2))), - ObjectDesc().id(2).pos({0, 1}).type(CellDesc().signal(SignalDesc().channels(signal1).numTimesSent(5))), + ObjectDesc().id(1).pos({0, 0}).type(CellDesc().signal(SignalDesc().channels(signal2))), + ObjectDesc().id(2).pos({0, 1}).type(CellDesc().signal(SignalDesc().channels(signal1))), }) .addConnection(1, 2); @@ -109,11 +102,6 @@ TEST_F(NeuronTests, forwardSignalByDefault_preview) EXPECT_TRUE(approxCompare(signal1, actualData.getObjectRef(1).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(signal2, actualData.getObjectRef(2).getCellRef()._signal._channels)); - - // Check numTimesSent: cell 1 receives from cell 2 (numTimesSent=5), so its numTimesSent should be 5 - // Cell 2 receives from cell 1 (numTimesSent=2), so its numTimesSent should be 2 - EXPECT_EQ(actualData.getObjectRef(1).getCellRef()._signal._numTimesSent, 5); - EXPECT_EQ(actualData.getObjectRef(2).getCellRef()._signal._numTimesSent, 2); } TEST_F(NeuronTests, emptySignalForZeroConnectionWeight) @@ -141,11 +129,10 @@ TEST_F(NeuronTests, forkSignal) { auto signal = getExampleSignal1(); - // Set numTimesSent on the source signal auto data = Desc() .addCreature({ ObjectDesc().id(1).pos({1, 2}), - ObjectDesc().id(2).pos({2, 2}).type(CellDesc().signal(SignalDesc().channels(signal).numTimesSent(4))), + ObjectDesc().id(2).pos({2, 2}).type(CellDesc().signal(SignalDesc().channels(signal))), ObjectDesc().id(3).pos({3, 2}), ObjectDesc().id(4).pos({2, 3}), ObjectDesc().id(5).pos({2, 1}).type(CellDesc().neuralNetwork(NeuralNetDesc().connectionWeights({0, 1, 0, 0, 0, 0}))), @@ -169,11 +156,6 @@ TEST_F(NeuronTests, forkSignal) EXPECT_TRUE(approxCompare(signal, actualData.getObjectRef(4).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(emptySignal, actualData.getObjectRef(5).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(emptySignal, actualData.getObjectRef(6).getCellRef()._signal._channels)); - - // Check numTimesSent: cells 1, 3, 4 receive the signal from cell 2 (numTimesSent=4) - EXPECT_EQ(actualData.getObjectRef(1).getCellRef()._signal._numTimesSent, 4); - EXPECT_EQ(actualData.getObjectRef(3).getCellRef()._signal._numTimesSent, 4); - EXPECT_EQ(actualData.getObjectRef(4).getCellRef()._signal._numTimesSent, 4); } TEST_F(NeuronTests, mergeSignal) @@ -181,20 +163,16 @@ TEST_F(NeuronTests, mergeSignal) auto signal1 = getExampleSignal1(); auto signal2 = getExampleSignal2(); - // Use different numTimesSent values: signal1 has numTimesSent=3, signal2 has numTimesSent=5, signal2 in cell 4 has numTimesSent=2 - // Cell 2 receives from cells 1 (numTimesSent=3), 3 (numTimesSent=5), 4 (numTimesSent=2) - // The resulting numTimesSent should be min(3, 5, 2) = 2 - auto data = Desc() .addCreature({ - ObjectDesc().id(1).pos({1, 2}).type(CellDesc().signal(SignalDesc().channels(signal1).numTimesSent(3))), // Gets input from cell 2 + ObjectDesc().id(1).pos({1, 2}).type(CellDesc().signal(SignalDesc().channels(signal1))), // Gets input from cell 2 ObjectDesc().id(2).pos({2, 2}).type( - CellDesc().neuralNetwork(NeuralNetDesc().connectionWeights({1, 0, 1, 1, 0, 0}))), // Gets input from cell 1, 3, 4 and not cell 5 - ObjectDesc().id(3).pos({3, 2}).type(CellDesc().signal(SignalDesc().channels(signal2).numTimesSent(5))), // Gets input from cell 2 - ObjectDesc().id(4).pos({2, 3}).type(CellDesc().signal(SignalDesc().channels(signal2).numTimesSent(2))), // Gets input from cell 2 + CellDesc().neuralNetwork(NeuralNetDesc().connectionWeights({1, 0, 1, 1, 0, 0}))), // Gets input from cell 1, 3, 4 and not cell 5 + ObjectDesc().id(3).pos({3, 2}).type(CellDesc().signal(SignalDesc().channels(signal2))), // Gets input from cell 2 + ObjectDesc().id(4).pos({2, 3}).type(CellDesc().signal(SignalDesc().channels(signal2))), // Gets input from cell 2 ObjectDesc().id(5).pos({2, 1}).type(CellDesc() - .signal(SignalDesc().channels(signal2).numTimesSent(4)) + .signal(SignalDesc().channels(signal2)) .neuralNetwork(NeuralNetDesc().connectionWeights({0, 1, 0, 0, 0, 0}))), // Gets input from cell 6 ObjectDesc().id(6).pos({2, 0}), // Gets input from cell 5 }) @@ -218,14 +196,6 @@ TEST_F(NeuronTests, mergeSignal) EXPECT_TRUE(approxCompare(emptySignal, actualData.getObjectRef(4).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(emptySignal, actualData.getObjectRef(5).getCellRef()._signal._channels)); EXPECT_TRUE(approxCompare(signal2, actualData.getObjectRef(6).getCellRef()._signal._channels)); - - // Check numTimesSent on the merged signal - // Cell 2 receives from cells 1 (numTimesSent=3), 3 (numTimesSent=5), 4 (numTimesSent=2) - // The resulting numTimesSent should be min(3, 5, 2) = 2 - EXPECT_EQ(actualData.getObjectRef(2).getCellRef()._signal._numTimesSent, 2); - - // Cell 6 receives only from cell 5 (numTimesSent=4), so numTimesSent should be 4 - EXPECT_EQ(actualData.getObjectRef(6).getCellRef()._signal._numTimesSent, 4); } struct ApplyNeuralNetParameter diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index b824dcf33..5c2c1478b 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -859,7 +859,6 @@ void _InspectionWindow::processCellTypeNode(CellDesc& cell) if (mode == CommunicatorMode_Sender) { auto& m = std::get(communicator._mode); AlienGui::SliderInt(AlienGui::SliderIntParameters().name("Range").min(0).max(20).textWidth(TextWidth), &m._range); - AlienGui::InputInt(AlienGui::InputIntParameters().name("Max times sent").textWidth(TextWidth), m._maxTimesSent); } else if (mode == CommunicatorMode_Receiver) { auto& m = std::get(communicator._mode); AlienGui::ColorCheckboxes( diff --git a/source/Gui/NodeEditorWidget.cpp b/source/Gui/NodeEditorWidget.cpp index f03b56f87..622868d11 100644 --- a/source/Gui/NodeEditorWidget.cpp +++ b/source/Gui/NodeEditorWidget.cpp @@ -672,7 +672,6 @@ void _NodeEditorWidget::processNodeAttributes() AlienGui::BeginIndent(); auto& sender = std::get(communicator._mode); AlienGui::SliderInt(AlienGui::SliderIntParameters().name("Range").min(0).max(20).textWidth(rightColumnWidth), &sender._range); - AlienGui::InputInt(AlienGui::InputIntParameters().name("Max times sent").textWidth(rightColumnWidth), sender._maxTimesSent); AlienGui::EndIndent(); } else if (mode == CommunicatorMode_Receiver) { AlienGui::BeginIndent(); diff --git a/source/PersisterInterface/SerializerService.cpp b/source/PersisterInterface/SerializerService.cpp index 7534138db..fe8f96abf 100644 --- a/source/PersisterInterface/SerializerService.cpp +++ b/source/PersisterInterface/SerializerService.cpp @@ -428,7 +428,6 @@ namespace auto constexpr Id_MemoryGenome_ChannelBitMask = 0; auto constexpr Id_SenderGenome_Range = 0; - auto constexpr Id_SenderGenome_MaxTimesSent = 1; auto constexpr Id_ReceiverGenome_RestrictToColor = 1; auto constexpr Id_ReceiverGenome_RestrictToLineage = 2; @@ -841,7 +840,6 @@ namespace cereal SenderGenomeDesc defaultObject; auto scope = getSerializationScope(task, ar); scope.addMember(Id_SenderGenome_Range, data._range, defaultObject._range); - scope.addMember(Id_SenderGenome_MaxTimesSent, data._maxTimesSent, defaultObject._maxTimesSent); } SPLIT_SERIALIZATION(SenderGenomeDesc) @@ -1134,7 +1132,6 @@ namespace auto constexpr Id_Object_Sticky = 17; auto constexpr Id_Signal_Channels = 0; - auto constexpr Id_Signal_NumTimesSent = 1; auto constexpr Id_Connection_ObjectId = 0; auto constexpr Id_Connection_Distance = 1; @@ -1252,7 +1249,6 @@ namespace auto constexpr Id_Memory_ChannelBitMask = 0; auto constexpr Id_Sender_Range = 0; - auto constexpr Id_Sender_MaxTimesSent = 1; auto constexpr Id_Receiver_RestrictToColor = 1; auto constexpr Id_Receiver_RestrictToLineage = 2; @@ -1303,7 +1299,6 @@ namespace cereal SignalDesc defaultObject; auto scope = getSerializationScope(task, ar); scope.addMember(Id_Signal_Channels, data._channels, defaultObject._channels); - scope.addMember(Id_Signal_NumTimesSent, data._numTimesSent, defaultObject._numTimesSent); } SPLIT_SERIALIZATION(SignalDesc) @@ -1705,7 +1700,6 @@ namespace cereal SenderDesc defaultObject; auto scope = getSerializationScope(task, ar); scope.addMember(Id_Sender_Range, data._range, defaultObject._range); - scope.addMember(Id_Sender_MaxTimesSent, data._maxTimesSent, defaultObject._maxTimesSent); } SPLIT_SERIALIZATION(SenderDesc) From 9bba1636819e79866b5daecda2082606a1b92a9e Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Thu, 2 Jul 2026 08:53:36 +0200 Subject: [PATCH 2/2] Add directional gating and front-angle correction to communicator Co-Authored-By: Claude Opus 4.8 --- .../EngineKernels/CommunicatorProcessor.cuh | 37 ++- source/EngineTests/CommunicatorTests.cpp | 225 ++++++++++++------ 2 files changed, 183 insertions(+), 79 deletions(-) diff --git a/source/EngineKernels/CommunicatorProcessor.cuh b/source/EngineKernels/CommunicatorProcessor.cuh index 7765c1ac6..40b93aaae 100644 --- a/source/EngineKernels/CommunicatorProcessor.cuh +++ b/source/EngineKernels/CommunicatorProcessor.cuh @@ -53,14 +53,28 @@ __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& { __shared__ int range; __shared__ float2 senderPos; + __shared__ bool senderFrontValid; + __shared__ float2 senderFacing; // Absolute direction encoded by the signal angle relative to the front direction if (threadIdx.x == 0) { - auto& sender = object->typeData.cell.cellTypeData.communicator.modeData.sender; + auto& cell = object->typeData.cell; + auto& sender = cell.cellTypeData.communicator.modeData.sender; range = sender.range; senderPos = object->pos; + senderFrontValid = cell.frontAngle != VALUE_NOT_SET_FLOAT; + if (senderFrontValid) { + auto refAngle = Math::angleOfVector(ObjectConnectionProcessor::calcReferenceDirection(data, object)); + auto encodedAngle = cell.signal.channels[Channels::CommunicatorAngle]; + senderFacing = Math::unitVectorOfAngle(refAngle + cell.frontAngle + encodedAngle * 180.0f); + } } __syncthreads(); + // The sender must have an initialized front direction + if (!senderFrontValid) { + return; + } + // Matching lambda to check if a cell is a valid receiver auto isMatch = [&object](Object* otherObject) { // Must be a communicator in receiver mode @@ -125,7 +139,14 @@ __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& auto const& otherRecord = records[otherIndex]; auto otherObject = otherRecord.self; if (isMatch(otherObject)) { - tryTransmitSignal(data, object, otherObject); + // The receiver must have an initialized front direction + if (otherObject->typeData.cell.frontAngle != VALUE_NOT_SET_FLOAT) { + // Direction gating: only send if the receiver lies in the half-plane opposite to the encoded facing direction + auto toReceiver = data.objectMap.getCorrectedDirection(otherObject->pos - senderPos); + if (Math::dot(toReceiver, senderFacing) <= 0) { + tryTransmitSignal(data, object, otherObject); + } + } } otherIndex = otherRecord.nextObjectIndex; } @@ -139,14 +160,14 @@ __inline__ __device__ void CommunicatorProcessor::tryTransmitSignal(SimulationDa // Copy signal to receiver copyChannels(receiverObject->typeData.cell.signal.channels, senderObject->typeData.cell.signal.channels); - // Translate angle in channel[1] from sender's reference direction to receiver's reference direction - // The angle is encoded as value/180 degrees, where 1.0 = 180 deg and -1.0 = -180 deg - // We need to maintain the absolute direction: senderRefDir rotated by senderAngle = receiverRefDir rotated by receiverAngle - // Therefore: receiverAngle = senderAngle + (senderRefAngle - receiverRefAngle) + // Translate angle in channel[1] from the sender's absolute front direction to the receiver's absolute front direction + // The angle is encoded as value/180 degrees relative to the front direction, where 1.0 = 180 deg and -1.0 = -180 deg + // We need to maintain the absolute direction: senderFront rotated by senderAngle = receiverFront rotated by receiverAngle + // Therefore: receiverAngle = senderAngle + (senderFrontAngle - receiverFrontAngle) auto senderRefDir = ObjectConnectionProcessor::calcReferenceDirection(data, senderObject); auto receiverRefDir = ObjectConnectionProcessor::calcReferenceDirection(data, receiverObject); - auto senderRefAngle = Math::angleOfVector(senderRefDir); - auto receiverRefAngle = Math::angleOfVector(receiverRefDir); + auto senderRefAngle = Math::angleOfVector(senderRefDir) + senderObject->typeData.cell.frontAngle; + auto receiverRefAngle = Math::angleOfVector(receiverRefDir) + receiverObject->typeData.cell.frontAngle; auto angleDiff = senderRefAngle - receiverRefAngle; // The signal angle is encoded as angle/180, so the diff must also be scaled diff --git a/source/EngineTests/CommunicatorTests.cpp b/source/EngineTests/CommunicatorTests.cpp index 3c08684d9..da4725995 100644 --- a/source/EngineTests/CommunicatorTests.cpp +++ b/source/EngineTests/CommunicatorTests.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include @@ -26,8 +28,17 @@ class CommunicatorTests : public IntegrationTestFramework ~CommunicatorTests() = default; protected: - // Helper to create a sender creature with 2 cells (sender + helper for signal) - Desc createSenderCreature(uint64_t creatureId, RealVector2D pos, int range = 50, int color = 0) + // Helper to create a sender creature with 2 cells (sender + helper for reference direction). + // The sender is connected to a helper cell to the east, so its reference direction is (1, 0). + // With frontAngle and the encoded angle in signal.channels[CommunicatorAngle] (= angleChannel), the + // absolute facing direction defaults to south (0, +1): refAngle(90) + frontAngle(0) + angleChannel(0.5) * 180 = 180. + Desc createSenderCreature( + uint64_t creatureId, + RealVector2D pos, + int range = 50, + int color = 0, + std::optional frontAngle = 0.0f, + float angleChannel = 0.5f) { auto data = Desc().addCreature( { @@ -36,7 +47,8 @@ class CommunicatorTests : public IntegrationTestFramework .pos(pos) .color(color) .type(CellDesc() - .neuralNetwork(NeuralNetDesc().biases({1.0f, 0.5f, 2.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) + .frontAngle(frontAngle) + .neuralNetwork(NeuralNetDesc().biases({1.0f, angleChannel, 2.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})) .cellType(CommunicatorDesc().mode(SenderDesc().range(range)))), ObjectDesc().id(creatureId * 100 + 1).pos({pos.x + 1.0f, pos.y}).color(color), }, @@ -45,13 +57,14 @@ class CommunicatorTests : public IntegrationTestFramework return data; } - // Helper to create a receiver creature with 2 cells + // Helper to create a receiver creature with 2 cells (receiver + helper for reference direction). Desc createReceiverCreature( uint64_t creatureId, RealVector2D pos, int restrictToColors = 0x3ff, LineageRestriction restrictToLineage = LineageRestriction_No, - int color = 0) + int color = 0, + std::optional frontAngle = 0.0f) { auto data = Desc().addCreature( { @@ -59,7 +72,9 @@ class CommunicatorTests : public IntegrationTestFramework .id(creatureId * 100) .pos(pos) .color(color) - .type(CellDesc().cellType(CommunicatorDesc().mode(ReceiverDesc().restrictToColors(restrictToColors).restrictToLineage(restrictToLineage)))), + .type(CellDesc() + .frontAngle(frontAngle) + .cellType(CommunicatorDesc().mode(ReceiverDesc().restrictToColors(restrictToColors).restrictToLineage(restrictToLineage)))), ObjectDesc().id(creatureId * 100 + 1).pos({pos.x + 1.0f, pos.y}).color(color), }, CreatureDesc().id(creatureId)); @@ -85,11 +100,11 @@ TEST_F(CommunicatorTests, sender_noReceiver_noSignalTransmitted) TEST_F(CommunicatorTests, sender_receiverInRange_signalTransmitted) { - // Create sender in creature 1 + // Create sender in creature 1 (facing south) auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f); - // Create receiver in creature 2, within range - data.add(createReceiverCreature(2, {110.0f, 100.0f}), false); + // Create receiver in creature 2, within range and in the opposite half-plane (north of the sender) + data.add(createReceiverCreature(2, {100.0f, 90.0f}), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -97,9 +112,10 @@ TEST_F(CommunicatorTests, sender_receiverInRange_signalTransmitted) auto result = _simulationFacade->getSimulationData(); auto receiver = result.getObjectRef(200); - // Receiver should have received the signal + // Receiver should have received the signal. Sender and receiver share the same absolute front direction, + // so the encoded angle is transmitted unchanged. EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[0], 1.0f); - EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[1], 0.5f); + EXPECT_NEAR(receiver.getCellRef()._signal._channels[Channels::CommunicatorAngle], 0.5f, 1e-4f); EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[2], 2.0f); } @@ -108,8 +124,8 @@ TEST_F(CommunicatorTests, sender_receiverOutOfRange_noSignalTransmitted) // Create sender in creature 1 with small range auto data = createSenderCreature(1, {100.0f, 100.0f}, 10.0f); - // Create receiver in creature 2, out of range - data.add(createReceiverCreature(2, {115.0f, 100.0f}), false); + // Create receiver in creature 2, out of range (north but too far away) + data.add(createReceiverCreature(2, {100.0f, 85.0f}), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -127,8 +143,8 @@ TEST_F(CommunicatorTests, sender_sameCreatureReceiver_noSignalTransmitted) auto data = Desc().addCreature( { ObjectDesc().id(0).pos({99.0f, 100.0f}).type(CellDesc().signal({1.0f, 2.0f, 3.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), - ObjectDesc().id(1).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), - ObjectDesc().id(2).pos({110.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(ReceiverDesc()))), + ObjectDesc().id(1).pos({100.0f, 100.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), + ObjectDesc().id(2).pos({100.0f, 90.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(ReceiverDesc()))), }, CreatureDesc().id(1)); data.addConnection(0, 1); @@ -146,13 +162,13 @@ TEST_F(CommunicatorTests, sender_sameCreatureReceiver_noSignalTransmitted) TEST_F(CommunicatorTests, sender_multipleReceiversInRange_allReceiveSignal) { - // Create sender in creature 1 + // Create sender in creature 1 (facing south) auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f); - // Create multiple receivers in different creatures - data.add(createReceiverCreature(2, {110.0f, 100.0f}), false); - data.add(createReceiverCreature(3, {100.0f, 110.0f}), false); - data.add(createReceiverCreature(4, {90.0f, 100.0f}), false); + // Create multiple receivers in the opposite half-plane (north, north-east, north-west) + data.add(createReceiverCreature(2, {110.0f, 90.0f}), false); + data.add(createReceiverCreature(3, {100.0f, 90.0f}), false); + data.add(createReceiverCreature(4, {90.0f, 90.0f}), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -162,7 +178,7 @@ TEST_F(CommunicatorTests, sender_multipleReceiversInRange_allReceiveSignal) // All receivers should have received the signal for (uint64_t id : {200, 300, 400}) { auto receiver = result.getObjectRef(id); - EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[1], 0.5f); + EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] != 0.0f); } } @@ -172,7 +188,7 @@ TEST_F(CommunicatorTests, sender_receiverColorRestriction_matchingColor) auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 2); // Create receiver that only accepts color 2 - data.add(createReceiverCreature(2, {110.0f, 100.0f}, 1 << 2), false); + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 1 << 2), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -190,7 +206,7 @@ TEST_F(CommunicatorTests, sender_receiverColorRestriction_mismatchingColor) auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 3); // Create receiver that only accepts color 2 - data.add(createReceiverCreature(2, {110.0f, 100.0f}, 1 << 2), false); + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 1 << 2), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -207,15 +223,15 @@ TEST_F(CommunicatorTests, sender_noActiveSignal_noTransmission) // Create sender without active signal auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), - // No signalAndState set, so signal is not active + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), + // No signal set, so signal is not active ObjectDesc().id(101).pos({101.0f, 100.0f}), }, CreatureDesc().id(1)); data.addConnection(100, 101); // Create receiver - data.add(createReceiverCreature(2, {110.0f, 100.0f}), false); + data.add(createReceiverCreature(2, {100.0f, 90.0f}), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -229,25 +245,25 @@ TEST_F(CommunicatorTests, sender_noActiveSignal_noTransmission) TEST_F(CommunicatorTests, sender_signalPriority_signalReceived) { - // Create first sender with signal = 1.0 + // Create two senders (both facing south) and a receiver between them; only the sender for which the receiver + // lies in the opposite half-plane should transmit. auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc().id(101).pos({101.0f, 100.0f}).type(CellDesc().signal(SignalDesc().channels({1.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), }, CreatureDesc().id(1)); data.addConnection(100, 101); - // Create second sender with signal = -1.0 data.addCreature( { - ObjectDesc().id(200).pos({100.0f, 120.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), + ObjectDesc().id(200).pos({100.0f, 120.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc().id(201).pos({101.0f, 120.0f}).type(CellDesc().signal(SignalDesc().channels({-1.0f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), }, CreatureDesc().id(2)); data.addConnection(200, 201); - // Create receiver + // Receiver between both senders; it lies north of sender 2 (opposite half-plane) but south of sender 1. data.add(createReceiverCreature(3, {100.0f, 110.0f}), false); _simulationFacade->setSimulationData(data); @@ -256,50 +272,115 @@ TEST_F(CommunicatorTests, sender_signalPriority_signalReceived) auto result = _simulationFacade->getSimulationData(); auto receiver = result.getObjectRef(300); - // Receiver should have received a signal (from one of the senders) - EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] != 0.0f); + // Receiver should have received the signal from sender 2 + EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[0], -1.0f); } /** - * Parameterized test for angle translation with different reference direction differences. - * Parameter: receiverRefAngleDiff - the difference in degrees between sender and receiver reference directions. + * Directional gating: the sender only transmits to receivers that lie in the half-plane opposite to the encoded + * facing direction, i.e. where dot(receiverDirection, facing) <= 0. + */ +TEST_F(CommunicatorTests, sender_receiverInFacingHalfPlane_noSignalTransmitted) +{ + // Sender faces south; the receiver is placed south of the sender (same half-plane as the facing direction) + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f); + data.add(createReceiverCreature(2, {100.0f, 110.0f}), false); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); + + auto result = _simulationFacade->getSimulationData(); + auto receiver = result.getObjectRef(200); + + // Receiver in the facing half-plane should NOT receive the signal + EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] == 0.0f); +} + +TEST_F(CommunicatorTests, sender_frontAngleFlipsDirection) +{ + // Rotating the sender's front by 180 degrees flips the facing direction to north, so now a southern receiver + // (in the opposite half-plane) receives, while a northern receiver is blocked. + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 0, 180.0f); + data.add(createReceiverCreature(2, {100.0f, 110.0f}), false); // south -> should receive + data.add(createReceiverCreature(3, {100.0f, 90.0f}), false); // north -> should be blocked + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); + + auto result = _simulationFacade->getSimulationData(); + + EXPECT_TRUE(result.getObjectRef(200).getCellRef()._signal._channels[0] != 0.0f); + EXPECT_TRUE(result.getObjectRef(300).getCellRef()._signal._channels[0] == 0.0f); +} + +TEST_F(CommunicatorTests, sender_encodedAngleChangesDirection) +{ + // With an encoded angle of 0, the facing direction equals the absolute front (east). A western receiver is then + // in the opposite half-plane and receives, while an eastern receiver is blocked. + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 0, 0.0f, 0.0f); + data.add(createReceiverCreature(2, {90.0f, 100.0f}), false); // west -> should receive + data.add(createReceiverCreature(3, {110.0f, 100.0f}), false); // east -> should be blocked + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); + + auto result = _simulationFacade->getSimulationData(); + + EXPECT_TRUE(result.getObjectRef(200).getCellRef()._signal._channels[0] != 0.0f); + EXPECT_TRUE(result.getObjectRef(300).getCellRef()._signal._channels[0] == 0.0f); +} + +TEST_F(CommunicatorTests, sender_frontAngleNotInitialized_noSignalTransmitted) +{ + // Sender without an initialized front direction must not transmit + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 0, std::nullopt); + data.add(createReceiverCreature(2, {100.0f, 90.0f}), false); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); + + auto result = _simulationFacade->getSimulationData(); + auto receiver = result.getObjectRef(200); + + EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] == 0.0f); +} + +TEST_F(CommunicatorTests, receiver_frontAngleNotInitialized_noSignalTransmitted) +{ + // Receiver without an initialized front direction must not receive + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f); + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 0x3ff, LineageRestriction_No, 0, std::nullopt), false); + + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); + + auto result = _simulationFacade->getSimulationData(); + auto receiver = result.getObjectRef(200); + + EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] == 0.0f); +} + +/** + * Parameterized test for angle translation. The encoded angle is relative to the absolute front direction + * (reference direction rotated by frontAngle). The receiver's stored angle must be adjusted by the difference + * of the sender's and receiver's absolute front angles. */ class CommunicatorTests_AngleTranslation : public CommunicatorTests , public testing::WithParamInterface {}; -INSTANTIATE_TEST_SUITE_P(CommunicatorTests_AngleTranslation, CommunicatorTests_AngleTranslation, ::testing::Values(0.0f, 90.0f, 180.0f, 270.0f)); +INSTANTIATE_TEST_SUITE_P(CommunicatorTests_AngleTranslation, CommunicatorTests_AngleTranslation, ::testing::Values(0.0f, 90.0f, -90.0f, 180.0f)); TEST_P(CommunicatorTests_AngleTranslation, sender_angleTranslation) { - // The sender is at (100, 100) with connected cell at (101, 100) -> reference direction (1, 0) -> 90 degrees - // The receiver's connected cell position is calculated based on the angle difference parameter - auto receiverRefAngleDiff = GetParam(); + auto receiverFrontAngle = GetParam(); - // Calculate the receiver's connected cell position based on the angle difference - // Sender reference angle is 90 degrees (pointing right), receiver will be at 90 + receiverRefAngleDiff - auto receiverRefAngle = 90.0f + receiverRefAngleDiff; - auto receiverConnectedObjectOffset = Math::unitVectorOfAngle(receiverRefAngle); - - auto data = Desc().addCreature( - { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), - ObjectDesc() - .id(101) - .pos({101.0f, 100.0f}) - .type(CellDesc().signal(SignalDesc().channels({1.0f, 0.5f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), // channel[1] = 0.5 = 90 degrees - }, - CreatureDesc().id(1)); - data.addConnection(100, 101); + // Sender: reference direction east (refAngle 90), frontAngle 0, encoded angle 0.5 -> faces south. + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f); - data.addCreature( - { - ObjectDesc().id(200).pos({120.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(ReceiverDesc()))), - ObjectDesc().id(201).pos({120.0f + receiverConnectedObjectOffset.x, 100.0f + receiverConnectedObjectOffset.y}), - }, - CreatureDesc().id(2)); - data.addConnection(200, 201); + // Receiver: north of the sender (opposite half-plane), reference direction east, variable frontAngle. + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 0x3ff, LineageRestriction_No, 0, receiverFrontAngle), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -309,12 +390,10 @@ TEST_P(CommunicatorTests_AngleTranslation, sender_angleTranslation) EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] != 0.0f); - // The angle translation formula: translatedAngle = senderAngle + (senderRefAngle - receiverRefAngle) / 180 - // senderAngle = 0.5 (90 degrees), senderRefAngle = 90 degrees, receiverRefAngle = 90 + receiverRefAngleDiff - // angleDiff = 90 - (90 + receiverRefAngleDiff) = -receiverRefAngleDiff - // translatedAngle = 0.5 + (-receiverRefAngleDiff) / 180 - auto expectedAngle = Math::getNormalizedAngle(0.5f * 180.0f - receiverRefAngleDiff, -180.0f) / 180.0f; - EXPECT_NEAR(receiver.getCellRef()._signal._channels[1], expectedAngle, 0.001f); + // senderAbsFront = 90 + 0, receiverAbsFront = 90 + receiverFrontAngle, angleDiff = -receiverFrontAngle + // translatedAngle = encodedAngle(0.5) + angleDiff / 180, normalized to [-1, 1] + auto expectedAngle = Math::getNormalizedAngle(0.5f * 180.0f - receiverFrontAngle, -180.0f) / 180.0f; + EXPECT_NEAR(receiver.getCellRef()._signal._channels[Channels::CommunicatorAngle], expectedAngle, 0.001f); } /** @@ -352,17 +431,21 @@ TEST_P(CommunicatorTests_LineageRestriction, sender_lineageRestriction) auto data = Desc().addCreature( { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), + ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(SenderDesc().range(50)))), ObjectDesc().id(101).pos({101.0f, 100.0f}).type(CellDesc().signal(SignalDesc().channels({1.0f, 0.5f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))), }, CreatureDesc().id(1).lineageId(senderLineageId), GenomeDesc()); data.addConnection(100, 101); + // Receiver north of the sender (opposite half-plane) data.addCreature( { - ObjectDesc().id(200).pos({120.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(ReceiverDesc().restrictToLineage(params.restriction)))), - ObjectDesc().id(201).pos({121.0f, 100.0f}), + ObjectDesc() + .id(200) + .pos({100.0f, 80.0f}) + .type(CellDesc().frontAngle(0.0f).cellType(CommunicatorDesc().mode(ReceiverDesc().restrictToLineage(params.restriction)))), + ObjectDesc().id(201).pos({101.0f, 80.0f}), }, CreatureDesc().id(2).lineageId(receiverLineageId), GenomeDesc()); @@ -376,7 +459,7 @@ TEST_P(CommunicatorTests_LineageRestriction, sender_lineageRestriction) if (params.expectedAccept) { EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] != 0.0f); - EXPECT_FLOAT_EQ(receiver.getCellRef()._signal._channels[1], 0.5f); + EXPECT_NEAR(receiver.getCellRef()._signal._channels[Channels::CommunicatorAngle], 0.5f, 1e-4f); } else { EXPECT_TRUE(receiver.getCellRef()._signal._channels[0] == 0.0f); }