diff --git a/source/EngineImpl/DescConverterService.cpp b/source/EngineImpl/DescConverterService.cpp index 57f527dd1..726a17a0c 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; @@ -1210,7 +1207,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); @@ -1537,7 +1533,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); @@ -1570,7 +1565,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 4ec90220d..16c215896 100644 --- a/source/EngineInterface/CellTypeConstants.h +++ b/source/EngineInterface/CellTypeConstants.h @@ -482,8 +482,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 71d474827..f954df43a 100644 --- a/source/EngineInterface/DescValidationService.cpp +++ b/source/EngineInterface/DescValidationService.cpp @@ -276,7 +276,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 = @@ -490,7 +489,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 388ff6e96..c483e00f9 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 acc813a06..d1e957a87 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..b267584d2 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 bool tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject, float2 const& senderFacing); }; /************************************************************************/ @@ -52,26 +52,28 @@ __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; + __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; - maxTimesSent = sender.maxTimesSent; - currentNumTimesSent = object->typeData.cell.signal.numTimesSent; senderPos = object->pos; + senderFrontValid = cell.frontAngle != VALUE_NOT_SET_FLOAT; + if (senderFrontValid) { + auto encodedAngle = cell.signal.channels[Channels::CommunicatorAngle]; + senderFacing = ObjectConnectionProcessor::convertAngleSignalToAbsoluteDirection(data, object, encodedAngle); + } } __syncthreads(); - // Check if signal can still be forwarded - if (currentNumTimesSent >= maxTimesSent) { + // The sender must have an initialized front direction + if (!senderFrontValid) { 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,37 +138,35 @@ __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& auto const& otherRecord = records[otherIndex]; auto otherObject = otherRecord.self; if (isMatch(otherObject)) { - tryTransmitSignal(data, object, otherObject, newNumTimesSent); + // 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, senderFacing); + } } otherIndex = otherRecord.nextObjectIndex; } } } -__inline__ __device__ void CommunicatorProcessor::tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject, int newNumTimesSent) +__inline__ __device__ bool +CommunicatorProcessor::tryTransmitSignal(SimulationData& data, Object* senderObject, Object* receiverObject, float2 const& senderFacing) { + // The receiver must have an initialized front direction + if (receiverObject->typeData.cell.frontAngle == VALUE_NOT_SET_FLOAT) { + return false; + } + 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 - // We need to maintain the absolute direction: senderRefDir rotated by senderAngle = receiverRefDir rotated by receiverAngle - // Therefore: receiverAngle = senderAngle + (senderRefAngle - receiverRefAngle) - auto senderRefDir = ObjectConnectionProcessor::calcReferenceDirection(data, senderObject); - auto receiverRefDir = ObjectConnectionProcessor::calcReferenceDirection(data, receiverObject); - auto senderRefAngle = Math::angleOfVector(senderRefDir); - auto receiverRefAngle = Math::angleOfVector(receiverRefDir); - auto angleDiff = senderRefAngle - receiverRefAngle; - - // The signal angle is encoded as angle/180, so the diff must also be scaled - auto senderAngle = senderObject->typeData.cell.signal.channels[Channels::CommunicatorAngle]; - auto translatedAngle = senderAngle + angleDiff / 180.0f; - // Normalize to [-1, 1] range (representing [-180, 180] degrees) - translatedAngle = Math::getNormalizedAngle(translatedAngle * 180.0f, -180.0f) / 180.0f; - receiverObject->typeData.cell.signal.channels[Channels::CommunicatorAngle] = translatedAngle; + + // The encoded angle is relative to each cell's absolute front direction. senderFacing is the sender's absolute + // encoded direction; convert it back into the receiver's frame so the absolute direction is preserved. + auto receiverAngle = ObjectConnectionProcessor::convertAbsoluteDirectionToAngleSignal(data, receiverObject, senderFacing); + receiverObject->typeData.cell.signal.channels[Channels::CommunicatorAngle] = receiverAngle; receiverObject->releaseLock(); + return true; } diff --git a/source/EngineKernels/DataAccessKernels.cu b/source/EngineKernels/DataAccessKernels.cu index fbc21e8b9..2ce5a354c 100644 --- a/source/EngineKernels/DataAccessKernels.cu +++ b/source/EngineKernels/DataAccessKernels.cu @@ -245,7 +245,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; @@ -359,7 +358,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; @@ -545,7 +543,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 37f46fb0a..6911d0577 100644 --- a/source/EngineKernels/EntityFactory.cuh +++ b/source/EngineKernels/EntityFactory.cuh @@ -298,7 +298,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; @@ -417,7 +416,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; @@ -582,7 +580,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; @@ -772,7 +769,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); @@ -968,7 +964,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 8f1f96b9c..7165060c3 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 18d2a8649..b728329be 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/MuscleProcessor.cuh b/source/EngineKernels/MuscleProcessor.cuh index 447266189..24e3596df 100644 --- a/source/EngineKernels/MuscleProcessor.cuh +++ b/source/EngineKernels/MuscleProcessor.cuh @@ -535,10 +535,8 @@ __inline__ __device__ void MuscleProcessor::directMovement(SimulationData& data, if (object->typeData.cell.frontAngle == VALUE_NOT_SET_FLOAT) { return; } - auto direction = ObjectConnectionProcessor::calcReferenceDirection(data, object); - auto angle = Math::getNormalizedAngle( - object->typeData.cell.frontAngle + max(-1.0f, min(1.0f, object->typeData.cell.signal.channels[Channels::MuscleAngle])) * 180.0f, -180.0f); - direction = Math::rotateClockwise(direction, angle); + auto angleSignal = max(-1.0f, min(1.0f, object->typeData.cell.signal.channels[Channels::MuscleAngle])); + auto direction = ObjectConnectionProcessor::convertAngleSignalToAbsoluteDirection(data, object, angleSignal); auto activation = max(-1.0f, min(1.0f, object->typeData.cell.signal.channels[Channels::CellTypeActivation])); direction = direction * cudaSimulationParameters.muscleMovementAcceleration.value[object->color] * activation * 0.0001f * TIMESTEPS_PER_CELL_FUNCTION; diff --git a/source/EngineKernels/MutationProcessor.cuh b/source/EngineKernels/MutationProcessor.cuh index 9439bdf46..3e8f03609 100644 --- a/source/EngineKernels/MutationProcessor.cuh +++ b/source/EngineKernels/MutationProcessor.cuh @@ -474,7 +474,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); @@ -641,7 +640,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 3ba549dc9..a31853192 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/ObjectConnectionProcessor.cuh b/source/EngineKernels/ObjectConnectionProcessor.cuh index 5c29e99cf..c523ce2dc 100644 --- a/source/EngineKernels/ObjectConnectionProcessor.cuh +++ b/source/EngineKernels/ObjectConnectionProcessor.cuh @@ -46,6 +46,8 @@ public: __inline__ __device__ static bool existsOwnIntersectingObjectInBetween(SimulationData& data, Object* object, Object* otherObject); __inline__ __device__ static float2 calcReferenceDirection(SimulationData& data, Object* object); + __inline__ __device__ static float2 convertAngleSignalToAbsoluteDirection(SimulationData& data, Object* object, float const& angleSignal); + __inline__ __device__ static float convertAbsoluteDirectionToAngleSignal(SimulationData& data, Object* object, float2 const& direction); static int constexpr MaxOperationsPerCell = 50; @@ -648,3 +650,18 @@ __inline__ __device__ float2 ObjectConnectionProcessor::calcReferenceDirection(S } return Math::getNormalized(data.objectMap.getCorrectedDirection(object->connections[0].object->pos - object->pos)); } + +__inline__ __device__ float2 ObjectConnectionProcessor::convertAngleSignalToAbsoluteDirection(SimulationData& data, Object* object, float const& angleSignal) +{ + auto referenceDirection = calcReferenceDirection(data, object); + auto frontAngle = object->typeData.cell.frontAngle; + return Math::rotateClockwise(referenceDirection, frontAngle + angleSignal * 180.0f); +} + +__inline__ __device__ float ObjectConnectionProcessor::convertAbsoluteDirectionToAngleSignal(SimulationData& data, Object* object, float2 const& direction) +{ + auto referenceAngle = Math::angleOfVector(calcReferenceDirection(data, object)); + auto frontAngle = object->typeData.cell.frontAngle; + auto angle = Math::angleOfVector(direction) - referenceAngle - frontAngle; + return Math::getNormalizedAngle(angle, -180.0f) / 180.0f; +} diff --git a/source/EngineKernels/SensorProcessor.cuh b/source/EngineKernels/SensorProcessor.cuh index 6f9ff23ff..d343be577 100644 --- a/source/EngineKernels/SensorProcessor.cuh +++ b/source/EngineKernels/SensorProcessor.cuh @@ -109,12 +109,9 @@ __inline__ __device__ void SensorProcessor::processTelemetry(SimulationData& dat // 1000 -> 0.5 object->typeData.cell.signal.channels[Channels::SensorTelemetryCellEnergy] = 1.0f - 1.0f / powf(object->typeData.cell.usableEnergy + 1.0f, 0.1f); - // Measure cell velocity with respect to front angle - auto refAngle = Math::angleOfVector(ObjectConnectionProcessor::calcReferenceDirection(data, object)); - auto absFrontAngle = refAngle + object->typeData.cell.frontAngle; - auto velAngle = Math::angleOfVector(object->vel); + // Measure cell velocity with respect to front angle. Angle: between -1.0 and 1.0 object->typeData.cell.signal.channels[Channels::SensorTelemetryCellVelAngle] = - Math::getNormalizedAngle(velAngle - absFrontAngle, -180.0f) / 180.0f; // Angle: between -1.0 and 1.0 + ObjectConnectionProcessor::convertAbsoluteDirectionToAngleSignal(data, object, object->vel); // Measure cell velocity with auto vel = Math::length(object->vel); 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 f0ee3c64e..69b70ff72 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) @@ -625,9 +625,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); @@ -830,7 +827,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); @@ -996,7 +993,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..0f14e3f4a 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 maxTimesSent = 4, 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,8 +47,9 @@ 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})) - .cellType(CommunicatorDesc().mode(SenderDesc().range(range).maxTimesSent(maxTimesSent)))), + .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), }, CreatureDesc().id(creatureId)); @@ -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).maxTimesSent(4)))), - 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,43 +178,17 @@ 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); } } -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); + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 1 << 2), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -213,10 +203,10 @@ 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); + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 1 << 2), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -233,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).maxTimesSent(4)))), - // 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); @@ -255,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); @@ -282,91 +272,118 @@ 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); } -TEST_F(CommunicatorTests, sender_signalPriority_lowerNumTimesSentWins) +/** + * 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) { - // 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); + // 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); - // 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); + _simulationFacade->setSimulationData(data); + _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); - // Create receiver - data.add(createReceiverCreature(3, {100.0f, 110.0f}), false); + 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(); - 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); + 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 with different reference direction differences. - * Parameter: receiverRefAngleDiff - the difference in degrees between sender and receiver reference directions. + * 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(); - - // 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 receiverFrontAngle = GetParam(); + auto senderFrontAngle = 30.0f; - auto data = Desc().addCreature( - { - ObjectDesc().id(100).pos({100.0f, 100.0f}).type(CellDesc().cellType(CommunicatorDesc().mode(SenderDesc().range(50).maxTimesSent(4)))), - 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), non-trivial frontAngle, encoded angle 0.5. + // Absolute encoded direction = 90 + senderFrontAngle + 0.5 * 180 = 210 + senderFrontAngle, still pointing into the + // southern half-plane, so the northern receiver passes the directional gate. + auto data = createSenderCreature(1, {100.0f, 100.0f}, 50.0f, 0, senderFrontAngle); - 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 non-trivial frontAngle. + data.add(createReceiverCreature(2, {100.0f, 90.0f}, 0x3ff, LineageRestriction_No, 0, receiverFrontAngle), false); _simulationFacade->setSimulationData(data); _simulationFacade->calcTimesteps(TIMESTEPS_PER_CELL_FUNCTION); @@ -376,12 +393,11 @@ 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); + // Absolute encoded angle = senderRefAngle(90) + senderFrontAngle + encodedAngle(0.5) * 180. + // Receiver angle = absolute angle - receiverRefAngle(90) - receiverFrontAngle, normalized to [-1, 1]. + auto absoluteAngle = 90.0f + senderFrontAngle + 0.5f * 180.0f; + auto expectedAngle = Math::getNormalizedAngle(absoluteAngle - 90.0f - receiverFrontAngle, -180.0f) / 180.0f; + EXPECT_NEAR(receiver.getCellRef()._signal._channels[Channels::CommunicatorAngle], expectedAngle, 0.001f); } /** @@ -419,17 +435,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).maxTimesSent(4)))), + 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()); @@ -443,7 +463,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); } 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 7f95db742..9ba0f1e85 100644 --- a/source/PersisterInterface/SerializerService.cpp +++ b/source/PersisterInterface/SerializerService.cpp @@ -434,7 +434,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; @@ -850,7 +849,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) @@ -1166,7 +1164,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; @@ -1284,7 +1281,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; @@ -1335,7 +1331,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) @@ -1737,7 +1732,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)