From 8e67a4e1079188286d2289f417925085d4d648b9 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Fri, 19 Jun 2026 08:31:34 +0200 Subject: [PATCH 1/9] Move lineageId, prevLineageId, accumulatedMutations to creature Co-Authored-By: Claude Opus 4.8 --- source/EngineImpl/DescConverterService.cpp | 14 ++++---- source/EngineInterface/Desc.h | 3 ++ source/EngineInterface/DescEditService.cpp | 6 ++-- .../EngineInterface/DescValidationService.cpp | 5 --- source/EngineInterface/GenomeDesc.h | 3 -- .../EngineInterface/GenomeDescEditService.cpp | 2 -- source/EngineInterface/GenomeDescHash.h | 2 -- source/EngineKernels/AttackerProcessor.cuh | 2 +- .../EngineKernels/CommunicatorProcessor.cuh | 4 +-- source/EngineKernels/DataAccessKernels.cu | 8 ++--- source/EngineKernels/EditKernels.cu | 7 ++++ source/EngineKernels/Entities.cuh | 14 ++++++++ source/EngineKernels/EntityFactory.cuh | 6 ++-- source/EngineKernels/Genome.cuh | 13 -------- source/EngineKernels/GenomeTO.cuh | 3 -- source/EngineKernels/GeometryKernels.cu | 4 +-- source/EngineKernels/MutationProcessor.cuh | 24 +++++++------- source/EngineKernels/ReconnectorProcessor.cuh | 4 +-- source/EngineKernels/SensorProcessor.cuh | 4 +-- source/EngineKernels/TOs.cuh | 4 +++ source/EngineKernels/TestKernels.cu | 2 +- source/EngineTestData/DescTestDataFactory.cpp | 15 ++++++--- .../EngineTests/AccumulatedMutationTests.cpp | 33 +++++++++---------- source/EngineTests/BalanceTests.cpp | 24 ++++---------- .../EngineTests/CellTypeModeMutationTests.cpp | 3 -- source/EngineTests/CellTypeMutationTests.cpp | 3 -- .../CellTypePropertiesMutationTests.cpp | 3 -- source/EngineTests/CommunicatorTests.cpp | 8 ++--- .../EngineTests/ConnectionMutationTests.cpp | 3 -- .../EngineTests/ConstructorMutationTests.cpp | 3 -- source/EngineTests/MutationTestsBase.h | 7 ++++ source/EngineTests/NeuronMutationTests.cpp | 9 ----- source/EngineTests/ReconnectorTests.cpp | 24 +++++++------- source/EngineTests/SensorTests.cpp | 30 ++++++++--------- source/Gui/GenomeEditorWidget.cpp | 12 ------- source/Gui/InspectionWindow.cpp | 9 ++--- .../PersisterInterface/SerializerService.cpp | 12 +++---- 37 files changed, 149 insertions(+), 183 deletions(-) diff --git a/source/EngineImpl/DescConverterService.cpp b/source/EngineImpl/DescConverterService.cpp index 0f3c257c22..00c9b3cde1 100644 --- a/source/EngineImpl/DescConverterService.cpp +++ b/source/EngineImpl/DescConverterService.cpp @@ -865,11 +865,7 @@ GenomeDesc DescConverterService::createGenomeDesc(TOs const& to, int genomeIndex result._id = genomeTO.id; NumberGenerator::get().adaptMaxEntityId(genomeTO.id); result._name = char64ToString(genomeTO.name); - result._lineageId = genomeTO.lineageId; - NumberGenerator::get().adaptMaxLineageId(genomeTO.lineageId); - result._prevLineageId = genomeTO.prevLineageId != 0 ? std::make_optional(genomeTO.prevLineageId) : std::nullopt; result._frontAngle = genomeTO.frontAngle; - result._accumulatedMutations = genomeTO.accumulatedMutations; result._resistanceToInjection = genomeTO.resistanceToInjection; result._applyMetaMutations = genomeTO.applyMetaMutations; for (int i = 0; i < 2; ++i) { @@ -931,6 +927,10 @@ CreatureDesc DescConverterService::createCreatureDesc(TOs const& to, int creatur result._generation = creatureTO.generation; result._numCells = creatureTO.numCells; result._mutationState = creatureTO.mutationState; + result._lineageId = creatureTO.lineageId; + NumberGenerator::get().adaptMaxLineageId(creatureTO.lineageId); + result._prevLineageId = creatureTO.prevLineageId != 0 ? std::make_optional(creatureTO.prevLineageId) : std::nullopt; + result._accumulatedMutations = creatureTO.accumulatedMutations; result._headUpdateId = creatureTO.headUpdateId; return result; @@ -967,10 +967,7 @@ void DescConverterService::convertGenomeToTO( stringToChar64(genomeTO.name, genome._name); genomeTO.id = genome._id; - genomeTO.lineageId = genome._lineageId; - genomeTO.prevLineageId = genome._prevLineageId.value_or(0); genomeTO.frontAngle = genome._frontAngle; - genomeTO.accumulatedMutations = genome._accumulatedMutations; genomeTO.resistanceToInjection = genome._resistanceToInjection; genomeTO.applyMetaMutations = genome._applyMetaMutations; for (int i = 0; i < 2; ++i) { @@ -1246,6 +1243,9 @@ void DescConverterService::convertCreatureToTO( creatureTO.headUpdateId = creatureDesc._headUpdateId; creatureTO.numCells = creatureDesc._numCells; creatureTO.mutationState = creatureDesc._mutationState; + creatureTO.lineageId = creatureDesc._lineageId; + creatureTO.prevLineageId = creatureDesc._prevLineageId.value_or(0); + creatureTO.accumulatedMutations = creatureDesc._accumulatedMutations; creatureTO.genomeArrayIndex = genomeTOIndexById.at(creatureDesc._genomeId); } diff --git a/source/EngineInterface/Desc.h b/source/EngineInterface/Desc.h index 5329816b7f..6f3995edea 100644 --- a/source/EngineInterface/Desc.h +++ b/source/EngineInterface/Desc.h @@ -588,6 +588,9 @@ struct CreatureDesc MEMBER(CreatureDesc, int, numCells, 0); MEMBER(CreatureDesc, uint64_t, genomeId, 0); MEMBER(CreatureDesc, MutationState, mutationState, MutationState_Mutated); + MEMBER(CreatureDesc, int, lineageId, 0); + MEMBER(CreatureDesc, std::optional, prevLineageId, std::nullopt); + MEMBER(CreatureDesc, float, accumulatedMutations, 0.0f); // Process data MEMBER(CreatureDesc, int, headUpdateId, 0); diff --git a/source/EngineInterface/DescEditService.cpp b/source/EngineInterface/DescEditService.cpp index b47ff1395c..87ee87af05 100644 --- a/source/EngineInterface/DescEditService.cpp +++ b/source/EngineInterface/DescEditService.cpp @@ -515,9 +515,9 @@ void DescEditService::randomizeCountdowns(Desc& description, int minValue, int m void DescEditService::randomizeLineageIds(Desc& description) const { - for (auto& genome : description._genomes) { - genome._lineageId = NumberGenerator::get().getRandomInt(); - genome._prevLineageId = 0; + for (auto& creature : description._creatures) { + creature._lineageId = NumberGenerator::get().getRandomInt(); + creature._prevLineageId = 0; } } diff --git a/source/EngineInterface/DescValidationService.cpp b/source/EngineInterface/DescValidationService.cpp index 0247a3569c..5449901e3e 100644 --- a/source/EngineInterface/DescValidationService.cpp +++ b/source/EngineInterface/DescValidationService.cpp @@ -14,11 +14,6 @@ void DescValidationService::validateAndCorrect(GenomeDesc& genome) genome._frontAngle = Math::getNormalizedAngle(genome._frontAngle, -180.0f); // Validate mutation rate fields - genome._lineageId = std::max(genome._lineageId, 0); - if (genome._prevLineageId.has_value()) { - genome._prevLineageId = std::max(genome._prevLineageId.value(), 0); - } - genome._accumulatedMutations = std::max(genome._accumulatedMutations, 0.0f); auto validateCellTypePropertiesMutation = [](CellTypePropertiesMutationDesc& mutation) { mutation._nodeProbability = std::clamp(mutation._nodeProbability, 0.0f, 1.0f); mutation._valueChangeSigma = std::clamp(mutation._valueChangeSigma, 0.0f, 1.0f); diff --git a/source/EngineInterface/GenomeDesc.h b/source/EngineInterface/GenomeDesc.h index 41b9363525..81350a3642 100644 --- a/source/EngineInterface/GenomeDesc.h +++ b/source/EngineInterface/GenomeDesc.h @@ -552,10 +552,7 @@ struct GenomeDesc GenomeDesc id(uint64_t id); MEMBER(GenomeDesc, std::string, name, ""); MEMBER(GenomeDesc, std::vector, genes, {}) - MEMBER(GenomeDesc, int, lineageId, 0); - MEMBER(GenomeDesc, std::optional, prevLineageId, std::nullopt); MEMBER(GenomeDesc, float, frontAngle, 0.0f); - MEMBER(GenomeDesc, float, accumulatedMutations, 0.0f); MEMBER(GenomeDesc, bool, resistanceToInjection, false); MEMBER(GenomeDesc, bool, applyMetaMutations, true); diff --git a/source/EngineInterface/GenomeDescEditService.cpp b/source/EngineInterface/GenomeDescEditService.cpp index bfeea9e2fe..c7e83c91b3 100644 --- a/source/EngineInterface/GenomeDescEditService.cpp +++ b/source/EngineInterface/GenomeDescEditService.cpp @@ -492,8 +492,6 @@ namespace void adaptGenomeAttributesForPreview(GenomeDesc& genome, bool detailSimulation) { - genome._lineageId = 0; - genome._accumulatedMutations = 0; genome._mutationRates._neuronMutations[0] = NeuronMutationDesc(); genome._mutationRates._neuronMutations[1] = NeuronMutationDesc(); genome._mutationRates._connectionMutations[0] = ConnectionMutationDesc(); diff --git a/source/EngineInterface/GenomeDescHash.h b/source/EngineInterface/GenomeDescHash.h index 4fdd97b451..208ad50d9c 100644 --- a/source/EngineInterface/GenomeDescHash.h +++ b/source/EngineInterface/GenomeDescHash.h @@ -540,10 +540,8 @@ struct std::hash hash_combine(seed, std::hash{}(gene)); } hash_combine(seed, desc._frontAngle); - hash_combine(seed, desc._accumulatedMutations); hash_combine(seed, desc._resistanceToInjection); hash_combine(seed, desc._applyMetaMutations); - hash_combine(seed, desc._prevLineageId); for (int i = 0; i < 2; ++i) { hash_combine(seed, desc._mutationRates._neuronMutations[i]._nodeProbability); hash_combine(seed, desc._mutationRates._neuronMutations[i]._weightChangeSigma); diff --git a/source/EngineKernels/AttackerProcessor.cuh b/source/EngineKernels/AttackerProcessor.cuh index 0c1ba71607..79ea1f76bf 100644 --- a/source/EngineKernels/AttackerProcessor.cuh +++ b/source/EngineKernels/AttackerProcessor.cuh @@ -207,7 +207,7 @@ __device__ __inline__ void AttackerProcessor::processCell(SimulationData& data, ParameterCalculator::calcParameter(cudaSimulationParameters.attackerFoodChainColorMatrix, data, object->pos, color, otherColor); // Evaluate lineage - if (cell->creature->genome->isRelatedLineage(otherCell->creature->genome)) { + if (cell->creature->isRelatedLineage(otherCell->creature)) { energyToTransfer *= (1.0f - cudaSimulationParameters.attackerRelatedLineageProtection.value[object->color]); } diff --git a/source/EngineKernels/CommunicatorProcessor.cuh b/source/EngineKernels/CommunicatorProcessor.cuh index 691bc36e3f..3f1018c81a 100644 --- a/source/EngineKernels/CommunicatorProcessor.cuh +++ b/source/EngineKernels/CommunicatorProcessor.cuh @@ -98,11 +98,11 @@ __device__ __inline__ void CommunicatorProcessor::processSender(SimulationData& // Check lineage restriction if (receiver.restrictToLineage != LineageRestriction_No) { if (receiver.restrictToLineage == LineageRestriction_RelatedLineage) { - if (!object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (!object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { return false; } } else if (receiver.restrictToLineage == LineageRestriction_UnrelatedLineage) { - if (object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { return false; } } diff --git a/source/EngineKernels/DataAccessKernels.cu b/source/EngineKernels/DataAccessKernels.cu index e4477aedd4..bed7a61c8c 100644 --- a/source/EngineKernels/DataAccessKernels.cu +++ b/source/EngineKernels/DataAccessKernels.cu @@ -31,10 +31,7 @@ namespace } auto& genomeTO = to.genomes[genomeTOIndex]; genomeTO.id = genome->id; - genomeTO.lineageId = genome->lineageId; - genomeTO.prevLineageId = genome->prevLineageId; genomeTO.frontAngle = genome->frontAngle; - genomeTO.accumulatedMutations = genome->accumulatedMutations; genomeTO.resistanceToInjection = genome->resistanceToInjection; genomeTO.applyMetaMutations = genome->applyMetaMutations; for (int i = 0; i < 2; ++i) { @@ -293,6 +290,9 @@ namespace creatureTO.generation = creature->generation; creatureTO.numCells = creature->numCells; creatureTO.mutationState = creature->mutationState; + creatureTO.lineageId = creature->lineageId; + creatureTO.prevLineageId = creature->prevLineageId; + creatureTO.accumulatedMutations = creature->accumulatedMutations; creatureTO.headUpdateId = creature->headUpdateId; creatureTO.genomeArrayIndex = creature->genome->genomeIndex; @@ -1042,6 +1042,7 @@ __global__ void cudaAdaptNumberGenerator(CudaNumberGenerator numberGen, TOs to) for (int index = partition.startIndex; index <= partition.endIndex; index += partition.step) { auto const& creature = to.creatures[index]; maxIds.entityId = max(maxIds.entityId, creature.id); + maxIds.lineageId = max(maxIds.lineageId, creature.lineageId); } } { @@ -1050,7 +1051,6 @@ __global__ void cudaAdaptNumberGenerator(CudaNumberGenerator numberGen, TOs to) for (int index = partition.startIndex; index <= partition.endIndex; index += partition.step) { auto const& genome = to.genomes[index]; maxIds.entityId = max(maxIds.entityId, genome.id); - maxIds.lineageId = max(maxIds.lineageId, genome.lineageId); } } numberGen.adaptMaxIds(maxIds); diff --git a/source/EngineKernels/EditKernels.cu b/source/EngineKernels/EditKernels.cu index 97d6a5de1c..921b74b8b8 100644 --- a/source/EngineKernels/EditKernels.cu +++ b/source/EngineKernels/EditKernels.cu @@ -30,6 +30,13 @@ __global__ void cudaChangeObject(SimulationData data, TOs changeTO) EntityFactory entityFactory; entityFactory.init(&data); entityFactory.changeObjectFromTO(changeTO, objectTO, object); + if (objectTO.type == ObjectType_Cell) { + auto const& creatureTO = changeTO.creatures[objectTO.typeData.cell.creatureIndex]; + auto const& creature = object->typeData.cell.creature; + creature->lineageId = creatureTO.lineageId; + creature->prevLineageId = creatureTO.prevLineageId; + creature->accumulatedMutations = creatureTO.accumulatedMutations; + } } } } diff --git a/source/EngineKernels/Entities.cuh b/source/EngineKernels/Entities.cuh index 4a7305fcc3..0d0c4ac563 100644 --- a/source/EngineKernels/Entities.cuh +++ b/source/EngineKernels/Entities.cuh @@ -450,11 +450,25 @@ struct Creature Genome* genome; MutationState mutationState; + uint32_t lineageId; + uint32_t prevLineageId; + float accumulatedMutations; + // Process data uint32_t headUpdateId; // Will be updated regularly to trigger head updates // Temporary data uint64_t creatureIndex; // May be invalid + + __device__ __inline__ bool isRelatedLineage(Creature* other) + { + if (prevLineageId != 0 && other->prevLineageId != 0) { + return lineageId == other->lineageId || lineageId == other->prevLineageId || prevLineageId == other->lineageId + || prevLineageId == other->prevLineageId; + } else { + return lineageId == other->lineageId; + } + } }; struct Solid diff --git a/source/EngineKernels/EntityFactory.cuh b/source/EngineKernels/EntityFactory.cuh index d6185f15ed..703a3ee0d2 100644 --- a/source/EngineKernels/EntityFactory.cuh +++ b/source/EngineKernels/EntityFactory.cuh @@ -86,10 +86,7 @@ __inline__ __device__ Genome* EntityFactory::createGenomeFromTO(TOs const& to, i auto genome = _data->entities.heap.getTypedSubArray(1); genomeTO.genomeIndexOnGpu = static_cast(reinterpret_cast(genome) - _data->entities.heap.getArray()); genome->id = genomeTO.id; - genome->lineageId = genomeTO.lineageId; - genome->prevLineageId = genomeTO.prevLineageId; genome->frontAngle = genomeTO.frontAngle; - genome->accumulatedMutations = genomeTO.accumulatedMutations; genome->resistanceToInjection = genomeTO.resistanceToInjection; genome->applyMetaMutations = genomeTO.applyMetaMutations; for (int i = 0; i < 2; ++i) { @@ -331,6 +328,9 @@ __inline__ __device__ Creature* EntityFactory::createCreatureFromTO(TOs const& t creature->generation = creatureTO.generation; creature->numCells = creatureTO.numCells; creature->mutationState = creatureTO.mutationState; + creature->lineageId = creatureTO.lineageId; + creature->prevLineageId = creatureTO.prevLineageId; + creature->accumulatedMutations = creatureTO.accumulatedMutations; creature->headUpdateId = creatureTO.headUpdateId; auto const& genomeTO = to.genomes[creatureTO.genomeArrayIndex]; diff --git a/source/EngineKernels/Genome.cuh b/source/EngineKernels/Genome.cuh index 6d98945fa9..cb9fd1f56d 100644 --- a/source/EngineKernels/Genome.cuh +++ b/source/EngineKernels/Genome.cuh @@ -426,24 +426,11 @@ struct Genome int numGenes; Gene* genes; - uint32_t lineageId; - uint32_t prevLineageId; float frontAngle; - float accumulatedMutations; bool resistanceToInjection; bool applyMetaMutations; MutationRates mutationRates; // Temporary data uint64_t genomeIndex; // May be invalid - - __device__ __inline__ bool isRelatedLineage(Genome* other) - { - if (prevLineageId != 0 && other->prevLineageId != 0) { - return lineageId == other->lineageId || lineageId == other->prevLineageId || prevLineageId == other->lineageId - || prevLineageId == other->prevLineageId; - } else { - return lineageId == other->lineageId; - } - } }; diff --git a/source/EngineKernels/GenomeTO.cuh b/source/EngineKernels/GenomeTO.cuh index af73f128bf..e89f0872bb 100644 --- a/source/EngineKernels/GenomeTO.cuh +++ b/source/EngineKernels/GenomeTO.cuh @@ -431,10 +431,7 @@ struct GenomeTO int numGenes; uint64_t geneArrayIndex; - uint32_t lineageId; - uint32_t prevLineageId; float frontAngle; - float accumulatedMutations; bool resistanceToInjection; bool applyMetaMutations; MutationRatesTO mutationRates; diff --git a/source/EngineKernels/GeometryKernels.cu b/source/EngineKernels/GeometryKernels.cu index d30d01acb1..4e91b5fa6f 100644 --- a/source/EngineKernels/GeometryKernels.cu +++ b/source/EngineKernels/GeometryKernels.cu @@ -138,7 +138,7 @@ namespace float b = fminf(1.0f, fmaxf(0.0f, color.b)); float h, s, v; rgbToHsv(r, g, b, h, s, v); - auto lineageId = object->typeData.cell.creature->genome->lineageId; + auto lineageId = object->typeData.cell.creature->lineageId; uint32_t hash = lineageId * 2654435761u; float hueOffset = (static_cast(hash & 0xFFFFu) / 65535.0f) * 0.2f - 0.1f; h += hueOffset; @@ -149,7 +149,7 @@ namespace if (object->type != ObjectType_Cell) { return getCustomizationColor(object->color); } - auto lineageId = object->typeData.cell.creature->genome->lineageId; + auto lineageId = object->typeData.cell.creature->lineageId; uint32_t hash1 = lineageId * 2654435761u; uint32_t hash2 = lineageId * 2246822519u; uint32_t hash3 = lineageId * 3266489917u; diff --git a/source/EngineKernels/MutationProcessor.cuh b/source/EngineKernels/MutationProcessor.cuh index d2b47ab235..c7bf407f24 100644 --- a/source/EngineKernels/MutationProcessor.cuh +++ b/source/EngineKernels/MutationProcessor.cuh @@ -20,7 +20,7 @@ class MutationProcessor { public: __inline__ __device__ static void process(SimulationData& data, SimulationStatistics& statistics); - __inline__ __device__ static void applyMutations(SimulationData& data, Genome* genome); + __inline__ __device__ static void applyMutations(SimulationData& data, Creature* creature, Genome* genome); private: // Upper bound to avoid heap exhaustion. @@ -48,7 +48,8 @@ private: __inline__ __device__ static void removeNode(SimulationData& data, Gene& gene, int position); __inline__ __device__ static void correctGenome(SimulationData& data, Genome* genome); - __inline__ __device__ static void updateAccumulatedMutationsAndLineageId(SimulationData& data, Genome* genome, float& accumulatedMutations); + __inline__ __device__ static void + updateAccumulatedMutationsAndLineageId(SimulationData& data, Creature* creature, Genome* genome, float& accumulatedMutations); __inline__ __device__ static float generateGaussian(SimulationData& data); __inline__ __device__ static bool isRandomEvent(SimulationData& data, float probability); @@ -99,7 +100,7 @@ __inline__ __device__ void MutationProcessor::process(SimulationData& data, Simu if (clonedGenome != nullptr) { // Apply mutations to cloned genome - applyMutations(data, clonedGenome); + applyMutations(data, object->typeData.cell.creature, clonedGenome); if (laneId == 0) { object->typeData.cell.creature->genome = clonedGenome; @@ -109,7 +110,7 @@ __inline__ __device__ void MutationProcessor::process(SimulationData& data, Simu } } -__inline__ __device__ void MutationProcessor::applyMutations(SimulationData& data, Genome* genome) +__inline__ __device__ void MutationProcessor::applyMutations(SimulationData& data, Creature* creature, Genome* genome) { __shared__ float accumulatedMutations; auto block = cg_mutation::this_thread_block(); @@ -147,7 +148,7 @@ __inline__ __device__ void MutationProcessor::applyMutations(SimulationData& dat } block.sync(); - updateAccumulatedMutationsAndLineageId(data, genome, accumulatedMutations); + updateAccumulatedMutationsAndLineageId(data, creature, genome, accumulatedMutations); } __inline__ __device__ void MutationProcessor::applyMutations_neurons(SimulationData& data, Genome* genome, float& accumulatedMutations) @@ -1206,7 +1207,8 @@ __inline__ __device__ void MutationProcessor::applyMutations_meta(SimulationData } } -__inline__ __device__ void MutationProcessor::updateAccumulatedMutationsAndLineageId(SimulationData& data, Genome* genome, float& accumulatedMutations) +__inline__ __device__ void +MutationProcessor::updateAccumulatedMutationsAndLineageId(SimulationData& data, Creature* creature, Genome* genome, float& accumulatedMutations) { auto laneId = cg_mutation::this_thread_block().thread_rank(); if (laneId == 0) { @@ -1214,11 +1216,11 @@ __inline__ __device__ void MutationProcessor::updateAccumulatedMutationsAndLinea auto denominator = numberOfNodes > 0 ? toFloat(numberOfNodes) : 1.0f; - genome->accumulatedMutations += accumulatedMutations / denominator; - if (genome->accumulatedMutations > cudaSimulationParameters.newLineageThreshold.value) { - genome->prevLineageId = genome->lineageId; - genome->lineageId = data.primaryNumberGen.createLineageId(); - genome->accumulatedMutations = 0; + creature->accumulatedMutations += accumulatedMutations / denominator; + if (creature->accumulatedMutations > cudaSimulationParameters.newLineageThreshold.value) { + creature->prevLineageId = creature->lineageId; + creature->lineageId = data.primaryNumberGen.createLineageId(); + creature->accumulatedMutations = 0; } } } diff --git a/source/EngineKernels/ReconnectorProcessor.cuh b/source/EngineKernels/ReconnectorProcessor.cuh index 29f746d552..4540d7662d 100644 --- a/source/EngineKernels/ReconnectorProcessor.cuh +++ b/source/EngineKernels/ReconnectorProcessor.cuh @@ -101,11 +101,11 @@ __inline__ __device__ void ReconnectorProcessor::tryCreateConnection(SimulationD // Filter by lineage restriction if (reconnector.modeData.reconnectCreature.restrictToLineage != LineageRestriction_No) { if (reconnector.modeData.reconnectCreature.restrictToLineage == LineageRestriction_RelatedLineage) { - if (!object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (!object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { return; } } else if (reconnector.modeData.reconnectCreature.restrictToLineage == LineageRestriction_UnrelatedLineage) { - if (object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { return; } } diff --git a/source/EngineKernels/SensorProcessor.cuh b/source/EngineKernels/SensorProcessor.cuh index 475461fdf8..9dfa14502d 100644 --- a/source/EngineKernels/SensorProcessor.cuh +++ b/source/EngineKernels/SensorProcessor.cuh @@ -398,11 +398,11 @@ SensorProcessor::getMatchInfo(SimulationData& data, Object* object, float2 const } if (matches && restrictToLineage != LineageRestriction_No) { if (restrictToLineage == LineageRestriction_RelatedLineage) { - if (!object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (!object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { matches = false; } } else if (restrictToLineage == LineageRestriction_UnrelatedLineage) { - if (object->typeData.cell.creature->genome->isRelatedLineage(otherObject->typeData.cell.creature->genome)) { + if (object->typeData.cell.creature->isRelatedLineage(otherObject->typeData.cell.creature)) { matches = false; } } diff --git a/source/EngineKernels/TOs.cuh b/source/EngineKernels/TOs.cuh index ee3bdc6629..d2b7a823f5 100644 --- a/source/EngineKernels/TOs.cuh +++ b/source/EngineKernels/TOs.cuh @@ -496,6 +496,10 @@ struct CreatureTO uint64_t genomeArrayIndex; MutationState mutationState; + uint32_t lineageId; + uint32_t prevLineageId; + float accumulatedMutations; + // Process data uint32_t headUpdateId; diff --git a/source/EngineKernels/TestKernels.cu b/source/EngineKernels/TestKernels.cu index 4e66d3991f..d98d9fdd91 100644 --- a/source/EngineKernels/TestKernels.cu +++ b/source/EngineKernels/TestKernels.cu @@ -25,7 +25,7 @@ __global__ void cudaTestMutate(SimulationData data, uint64_t objectId) block.sync(); if (shouldMutate) { - MutationProcessor::applyMutations(data, object->typeData.cell.creature->genome); + MutationProcessor::applyMutations(data, object->typeData.cell.creature, object->typeData.cell.creature->genome); } block.sync(); } diff --git a/source/EngineTestData/DescTestDataFactory.cpp b/source/EngineTestData/DescTestDataFactory.cpp index 4b34673831..8480765904 100644 --- a/source/EngineTestData/DescTestDataFactory.cpp +++ b/source/EngineTestData/DescTestDataFactory.cpp @@ -195,10 +195,7 @@ std::pair DescTestDataFactory::createNonDefaultCreatur auto genome = GenomeDesc() .name("Test Genome") - .lineageId(502) - .prevLineageId(501) .frontAngle(270.0f) - .accumulatedMutations(0.05f) .resistanceToInjection(true) .applyMetaMutations(false) .mutationRates(mutation) @@ -214,8 +211,16 @@ std::pair DescTestDataFactory::createNonDefaultCreatur }), }); - auto creature = - CreatureDesc().ancestorId(1001).generation(7).numCells(25).headUpdateId(42).mutationState(MutationState_MutationInProgress).genomeId(genome._id); + auto creature = CreatureDesc() + .ancestorId(1001) + .generation(7) + .numCells(25) + .headUpdateId(42) + .mutationState(MutationState_MutationInProgress) + .lineageId(502) + .prevLineageId(501) + .accumulatedMutations(0.05f) + .genomeId(genome._id); return {creature, genome}; } diff --git a/source/EngineTests/AccumulatedMutationTests.cpp b/source/EngineTests/AccumulatedMutationTests.cpp index 34fd75b59b..c8a00f1d71 100644 --- a/source/EngineTests/AccumulatedMutationTests.cpp +++ b/source/EngineTests/AccumulatedMutationTests.cpp @@ -48,7 +48,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(AccumulatedMutationTests_AllTypes, accumulatedMutations_increases) { - auto genome = createTestGenome().lineageId(42).prevLineageId(41); + auto genome = createTestGenome(); switch (GetParam()) { case MutationType::Neuron: genome._mutationRates._neuronMutations[0] = @@ -87,7 +87,7 @@ TEST_P(AccumulatedMutationTests_AllTypes, accumulatedMutations_increases) break; } - auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc(), genome); + auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc().lineageId(42).prevLineageId(41), genome); auto parameters = _parameters; parameters.newLineageThreshold.value = 1.0e30f; @@ -98,15 +98,15 @@ TEST_P(AccumulatedMutationTests_AllTypes, accumulatedMutations_increases) _simulationFacade->testOnly_mutate(1); } - auto actualGenome = getMutatedGenome(); - EXPECT_GT(actualGenome._accumulatedMutations, genome._accumulatedMutations); + auto actualCreature = getMutatedCreature(); + EXPECT_GT(actualCreature._accumulatedMutations, 0.0f); } TEST_F(AccumulatedMutationTests, accumulatedMutations_metaMutationDoesNotAccount) { - auto genome = GenomeDesc().lineageId(42).prevLineageId(41); + auto genome = GenomeDesc(); - auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc(), genome); + auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc().lineageId(42).prevLineageId(41), genome); _parameters.neuronsMetaMutationsSigma.value = 1.0f; _parameters.connectionsMetaMutationsSigma.value = 1.0f; @@ -124,18 +124,17 @@ TEST_F(AccumulatedMutationTests, accumulatedMutations_metaMutationDoesNotAccount _simulationFacade->setSimulationData(data); _simulationFacade->testOnly_mutate(1); - auto actualGenome = getMutatedGenome(); - EXPECT_EQ(actualGenome._accumulatedMutations, 0.0f); - EXPECT_EQ(actualGenome._lineageId, 42); - EXPECT_EQ(actualGenome._prevLineageId, 41); + auto actualCreature = getMutatedCreature(); + EXPECT_EQ(actualCreature._accumulatedMutations, 0.0f); + EXPECT_EQ(actualCreature._lineageId, 42); + EXPECT_EQ(actualCreature._prevLineageId, 41); } TEST_F(AccumulatedMutationTests, accumulatedMutations_createsNewLineageId) { - auto genome = createTestGenome().lineageId(42).prevLineageId(41); - genome._accumulatedMutations = 11.0f; + auto genome = createTestGenome(); - auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc(), genome); + auto data = Desc().addCreature({ObjectDesc().id(1)}, CreatureDesc().lineageId(42).prevLineageId(41).accumulatedMutations(11.0f), genome); _parameters.newLineageThreshold.value = 0.1f; _simulationFacade->setSimulationParameters(_parameters); @@ -143,8 +142,8 @@ TEST_F(AccumulatedMutationTests, accumulatedMutations_createsNewLineageId) _simulationFacade->setSimulationData(data); _simulationFacade->testOnly_mutate(1); - auto actualGenome = getMutatedGenome(); - ASSERT_TRUE(actualGenome._prevLineageId.has_value()); - EXPECT_EQ(*actualGenome._prevLineageId, 42); - EXPECT_GT(actualGenome._lineageId, 42); + auto actualCreature = getMutatedCreature(); + ASSERT_TRUE(actualCreature._prevLineageId.has_value()); + EXPECT_EQ(*actualCreature._prevLineageId, 42); + EXPECT_GT(actualCreature._lineageId, 42); } diff --git a/source/EngineTests/BalanceTests.cpp b/source/EngineTests/BalanceTests.cpp index 96cd52b945..4ebd79677c 100644 --- a/source/EngineTests/BalanceTests.cpp +++ b/source/EngineTests/BalanceTests.cpp @@ -31,8 +31,8 @@ class BalanceTests : public IntegrationTestFramework .pos({numberGen.getRandomFloat(0.0f, worldSize.x), numberGen.getRandomFloat(0.0f, worldSize.y)}) .type(CellDesc().headCell(true).constructor(ConstructorDesc().provideEnergy(ProvideEnergy_Free).separation(true))), }, - CreatureDesc(), - GenomeDesc().lineageId(0).prevLineageId(0).frontAngle(225.0f).genes( + CreatureDesc().lineageId(0).prevLineageId(0), + GenomeDesc().frontAngle(225.0f).genes( { GeneDesc() @@ -73,8 +73,8 @@ class BalanceTests : public IntegrationTestFramework .pos({numberGen.getRandomFloat(0.0f, worldSize.x), numberGen.getRandomFloat(0.0f, worldSize.y)}) .type(CellDesc().headCell(true).constructor(ConstructorDesc().provideEnergy(ProvideEnergy_Free).separation(true))), }, - CreatureDesc(), - GenomeDesc().lineageId(1).prevLineageId(1).frontAngle(225.0f).genes( + CreatureDesc().lineageId(1).prevLineageId(1), + GenomeDesc().frontAngle(225.0f).genes( { GeneDesc() @@ -129,16 +129,10 @@ TEST_F(BalanceTests, longRunning_smallCreatures_vs_largeCreatures_fewDigestionCa _simulationFacade->calcTimesteps(20000); auto actualData = _simulationFacade->getSimulationData(); - // Create a map of genomeId to lineageId - std::unordered_map genomeIdToLineageId; - for (const auto& genome : actualData._genomes) { - genomeIdToLineageId[genome._id] = genome._lineageId; - } - int numSmallCreatures = 0; int numLargeCreatures = 0; for (const auto& creature : actualData._creatures) { - auto lineageId = genomeIdToLineageId.at(creature._genomeId); + auto lineageId = creature._lineageId; if (lineageId == 0) { ++numSmallCreatures; } else if (lineageId == 1) { @@ -173,16 +167,10 @@ TEST_F(BalanceTests, longRunning_smallCreatures_vs_largeCreatures_highDigestionC _simulationFacade->calcTimesteps(20000); auto actualData = _simulationFacade->getSimulationData(); - // Create a map of genomeId to lineageId - std::unordered_map genomeIdToLineageId; - for (const auto& genome : actualData._genomes) { - genomeIdToLineageId[genome._id] = genome._lineageId; - } - int numSmallCreatures = 0; int numLargeCreatures = 0; for (const auto& creature : actualData._creatures) { - auto lineageId = genomeIdToLineageId.at(creature._genomeId); + auto lineageId = creature._lineageId; if (lineageId == 0) { ++numSmallCreatures; } else if (lineageId == 1) { diff --git a/source/EngineTests/CellTypeModeMutationTests.cpp b/source/EngineTests/CellTypeModeMutationTests.cpp index 6b21732899..8d11d2c198 100644 --- a/source/EngineTests/CellTypeModeMutationTests.cpp +++ b/source/EngineTests/CellTypeModeMutationTests.cpp @@ -28,9 +28,6 @@ class CellTypeModeMutationTests : public MutationTestsBase bool compareAllExceptCellTypeMode(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { resetCellTypeMode(node._cellType); diff --git a/source/EngineTests/CellTypeMutationTests.cpp b/source/EngineTests/CellTypeMutationTests.cpp index 63193c07f9..c9f9eb3901 100644 --- a/source/EngineTests/CellTypeMutationTests.cpp +++ b/source/EngineTests/CellTypeMutationTests.cpp @@ -15,9 +15,6 @@ class CellTypeMutationTests : public MutationTestsBase bool compareAllExceptCellTypeAndHomogeneousFlag(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { gene._homogeneousCellType = false; // canonicalize: the cell type mutation may also flip this flag for (auto& node : gene._nodes) { diff --git a/source/EngineTests/CellTypePropertiesMutationTests.cpp b/source/EngineTests/CellTypePropertiesMutationTests.cpp index 3ea06c1d0a..18710156d9 100644 --- a/source/EngineTests/CellTypePropertiesMutationTests.cpp +++ b/source/EngineTests/CellTypePropertiesMutationTests.cpp @@ -44,9 +44,6 @@ class CellTypePropertiesMutationTests : public MutationTestsBase bool compareAllExceptCellTypeProperties(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { resetCellTypeProperties(node._cellType); diff --git a/source/EngineTests/CommunicatorTests.cpp b/source/EngineTests/CommunicatorTests.cpp index 02d61b25d9..e1f3c369bc 100644 --- a/source/EngineTests/CommunicatorTests.cpp +++ b/source/EngineTests/CommunicatorTests.cpp @@ -422,8 +422,8 @@ TEST_P(CommunicatorTests_LineageRestriction, sender_lineageRestriction) 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}))), }, - CreatureDesc().id(1), - GenomeDesc().lineageId(senderLineageId)); + CreatureDesc().id(1).lineageId(senderLineageId), + GenomeDesc()); data.addConnection(100, 101); data.addCreature( @@ -431,8 +431,8 @@ TEST_P(CommunicatorTests_LineageRestriction, sender_lineageRestriction) 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}), }, - CreatureDesc().id(2), - GenomeDesc().lineageId(receiverLineageId)); + CreatureDesc().id(2).lineageId(receiverLineageId), + GenomeDesc()); data.addConnection(200, 201); _simulationFacade->setSimulationData(data); diff --git a/source/EngineTests/ConnectionMutationTests.cpp b/source/EngineTests/ConnectionMutationTests.cpp index 99c8c16b2d..7341aa456e 100644 --- a/source/EngineTests/ConnectionMutationTests.cpp +++ b/source/EngineTests/ConnectionMutationTests.cpp @@ -14,9 +14,6 @@ class ConnectionMutationTests : public MutationTestsBase bool compareAllExceptConnectionWeights(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { std::fill(node._neuralNetwork._connectionWeights.begin(), node._neuralNetwork._connectionWeights.end(), 0.0f); diff --git a/source/EngineTests/ConstructorMutationTests.cpp b/source/EngineTests/ConstructorMutationTests.cpp index bcec59d843..e5c3558c78 100644 --- a/source/EngineTests/ConstructorMutationTests.cpp +++ b/source/EngineTests/ConstructorMutationTests.cpp @@ -15,9 +15,6 @@ class ConstructorMutationTests : public MutationTestsBase bool compareAllExceptConstructor(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { node._constructor.reset(); diff --git a/source/EngineTests/MutationTestsBase.h b/source/EngineTests/MutationTestsBase.h index 5141472f44..204e29f227 100644 --- a/source/EngineTests/MutationTestsBase.h +++ b/source/EngineTests/MutationTestsBase.h @@ -47,4 +47,11 @@ class MutationTestsBase : public IntegrationTestFramework auto actualCreature = actualData.getCreatureRef(actualCell._creatureId); return actualData.getGenomeRef(actualCreature._genomeId); } + + CreatureDesc getMutatedCreature(uint64_t objectId = 1) const + { + auto actualData = _simulationFacade->getSimulationData(); + auto actualCell = actualData.getObjectRef(objectId).getCellRef(); + return actualData.getCreatureRef(actualCell._creatureId); + } }; diff --git a/source/EngineTests/NeuronMutationTests.cpp b/source/EngineTests/NeuronMutationTests.cpp index 63ce1e7502..7d4d19b1e8 100644 --- a/source/EngineTests/NeuronMutationTests.cpp +++ b/source/EngineTests/NeuronMutationTests.cpp @@ -15,9 +15,6 @@ class NeuronMutationTests : public MutationTestsBase bool compareAllExceptNeuronWeights(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { std::fill(node._neuralNetwork._weights.begin(), node._neuralNetwork._weights.end(), 0.0f); @@ -32,9 +29,6 @@ class NeuronMutationTests : public MutationTestsBase bool compareAllExceptNeuronBiases(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { std::fill(node._neuralNetwork._biases.begin(), node._neuralNetwork._biases.end(), 0.0f); @@ -49,9 +43,6 @@ class NeuronMutationTests : public MutationTestsBase bool compareAllExceptActivationFunctions(GenomeDesc expected, GenomeDesc actual) { auto reset = [](GenomeDesc& genome) { - genome._lineageId = 0; - genome._prevLineageId = std::nullopt; - genome._accumulatedMutations = 0.0f; for (auto& gene : genome._genes) { for (auto& node : gene._nodes) { std::fill(node._neuralNetwork._activationFunctions.begin(), node._neuralNetwork._activationFunctions.end(), ActivationFunction_Identity); diff --git a/source/EngineTests/ReconnectorTests.cpp b/source/EngineTests/ReconnectorTests.cpp index 7b47ea8725..8d8db32213 100644 --- a/source/EngineTests/ReconnectorTests.cpp +++ b/source/EngineTests/ReconnectorTests.cpp @@ -36,8 +36,8 @@ class ReconnectorTests : public IntegrationTestFramework .color(color) .type(CellDesc().signal({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), // Signal on connected cell will propagate }, - CreatureDesc(), - GenomeDesc().lineageId(lineageId)); + CreatureDesc().lineageId(lineageId), + GenomeDesc()); data.addConnection(1, 2); return data; } @@ -54,8 +54,8 @@ class ReconnectorTests : public IntegrationTestFramework .color(color) .type(CellDesc().signal({-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})), // Signal on connected cell will propagate }, - CreatureDesc(), - GenomeDesc().lineageId(lineageId)); + CreatureDesc().lineageId(lineageId), + GenomeDesc()); data.addConnection(1, 2); return data; } @@ -407,8 +407,8 @@ TEST_F(ReconnectorTests, creatureMode_relatedLineage_success) ObjectDesc().id(10).pos({99.0f, 100.0f}), ObjectDesc().id(11).pos({98.0f, 100.0f}), }, - CreatureDesc(), - GenomeDesc().lineageId(5)); + CreatureDesc().lineageId(5), + GenomeDesc()); data.addConnection(10, 11); _simulationFacade->setSimulationData(data); @@ -429,8 +429,8 @@ TEST_F(ReconnectorTests, creatureMode_relatedLineage_failed) ObjectDesc().id(10).pos({99.0f, 100.0f}), ObjectDesc().id(11).pos({98.0f, 100.0f}), }, - CreatureDesc(), - GenomeDesc().lineageId(6)); + CreatureDesc().lineageId(6), + GenomeDesc()); data.addConnection(10, 11); _simulationFacade->setSimulationData(data); @@ -451,8 +451,8 @@ TEST_F(ReconnectorTests, creatureMode_unrelatedLineage_success) ObjectDesc().id(10).pos({99.0f, 100.0f}), ObjectDesc().id(11).pos({98.0f, 100.0f}), }, - CreatureDesc(), - GenomeDesc().lineageId(6)); + CreatureDesc().lineageId(6), + GenomeDesc()); data.addConnection(10, 11); _simulationFacade->setSimulationData(data); @@ -473,8 +473,8 @@ TEST_F(ReconnectorTests, creatureMode_unrelatedLineage_failed) ObjectDesc().id(10).pos({99.0f, 100.0f}), ObjectDesc().id(11).pos({98.0f, 100.0f}), }, - CreatureDesc(), - GenomeDesc().lineageId(5)); + CreatureDesc().lineageId(5), + GenomeDesc()); data.addConnection(10, 11); _simulationFacade->setSimulationData(data); diff --git a/source/EngineTests/SensorTests.cpp b/source/EngineTests/SensorTests.cpp index 1054e52c20..5b062c4b60 100644 --- a/source/EngineTests/SensorTests.cpp +++ b/source/EngineTests/SensorTests.cpp @@ -1504,13 +1504,13 @@ TEST_F(SensorTests, detectCreature_restrictToLineage_relatedLineage_found) SensorDesc().autoTrigger(true).mode(DetectCreatureDesc().restrictToLineage(LineageRestriction_RelatedLineage)))), ObjectDesc().id(2).pos({101.0f, 100.0f}), }, - CreatureDesc().id(0), - GenomeDesc().lineageId(42)); + CreatureDesc().id(0).lineageId(42), + GenomeDesc()); data.addConnection(1, 2); // Create a large creature with same lineage auto creatureData = createCreature(); - creatureData._genomes.front()._lineageId = 42; + creatureData._creatures.front()._lineageId = 42; data.add(std::move(creatureData)); _simulationFacade->setSimulationData(data); @@ -1534,13 +1534,13 @@ TEST_F(SensorTests, detectCreature_restrictToLineage_relatedLineage2_found) SensorDesc().autoTrigger(true).mode(DetectCreatureDesc().restrictToLineage(LineageRestriction_RelatedLineage)))), ObjectDesc().id(2).pos({101.0f, 100.0f}), }, - CreatureDesc().id(0), - GenomeDesc().lineageId(41).prevLineageId(42)); + CreatureDesc().id(0).lineageId(41).prevLineageId(42), + GenomeDesc()); data.addConnection(1, 2); // Create a large creature with same lineage auto creatureData = createCreature(); - creatureData._genomes.front().lineageId(43).prevLineageId(42); + creatureData._creatures.front().lineageId(43).prevLineageId(42); data.add(std::move(creatureData)); _simulationFacade->setSimulationData(data); @@ -1565,13 +1565,13 @@ TEST_F(SensorTests, detectCreature_restrictToLineage_relatedLineage_notFound) SensorDesc().autoTrigger(true).mode(DetectCreatureDesc().restrictToLineage(LineageRestriction_RelatedLineage)))), ObjectDesc().id(2).pos({101.0f, 100.0f}), }, - CreatureDesc().id(0), - GenomeDesc().lineageId(42)); + CreatureDesc().id(0).lineageId(42), + GenomeDesc()); data.addConnection(1, 2); // Create a large creature with different lineage auto creatureData = createCreature(); - creatureData._genomes.front()._lineageId = 41; + creatureData._creatures.front()._lineageId = 41; data.add(std::move(creatureData)); _simulationFacade->setSimulationData(data); @@ -1594,13 +1594,13 @@ TEST_F(SensorTests, detectCreature_restrictToLineage_unrelatedLineage_found) SensorDesc().autoTrigger(true).mode(DetectCreatureDesc().restrictToLineage(LineageRestriction_UnrelatedLineage)))), ObjectDesc().id(2).pos({101.0f, 100.0f}), }, - CreatureDesc().id(0), - GenomeDesc().lineageId(42)); + CreatureDesc().id(0).lineageId(42), + GenomeDesc()); data.addConnection(1, 2); // Create a large creature with different lineage auto creatureData = createCreature(); - creatureData._genomes.front()._lineageId = 41; + creatureData._creatures.front()._lineageId = 41; data.add(std::move(creatureData)); _simulationFacade->setSimulationData(data); @@ -1624,13 +1624,13 @@ TEST_F(SensorTests, detectCreature_restrictToLineage_unrelatedLineage_notFound) SensorDesc().autoTrigger(true).mode(DetectCreatureDesc().restrictToLineage(LineageRestriction_UnrelatedLineage)))), ObjectDesc().id(2).pos({101.0f, 100.0f}), }, - CreatureDesc().id(0), - GenomeDesc().lineageId(42)); + CreatureDesc().id(0).lineageId(42), + GenomeDesc()); data.addConnection(1, 2); // Create a large creature with same lineage auto creatureData = createCreature(); - creatureData._genomes.front()._lineageId = 42; + creatureData._creatures.front()._lineageId = 42; data.add(std::move(creatureData)); _simulationFacade->setSimulationData(data); diff --git a/source/Gui/GenomeEditorWidget.cpp b/source/Gui/GenomeEditorWidget.cpp index 3412bff56c..10298e2be1 100644 --- a/source/Gui/GenomeEditorWidget.cpp +++ b/source/Gui/GenomeEditorWidget.cpp @@ -75,9 +75,6 @@ void _GenomeEditorWidget::processHeaderData() AlienGui::Group(AlienGui::GroupParameters().text("Base properties and info")); AlienGui::InputText(AlienGui::InputTextParameters().name("Genome name").textWidth(rightColumnWidth), _editData->genome._name); - if (AlienGui::InputInt(AlienGui::InputIntParameters().name("Lineage").textWidth(rightColumnWidth), _editData->genome._lineageId)) { - _editData->genome._prevLineageId.reset(); - } auto numNodesString = std::to_string(GenomeDescInfoService::get().getNumberOfNodes(_editData->genome)); AlienGui::InputText(AlienGui::InputTextParameters().name("Node count").readOnly(true).textWidth(rightColumnWidth), numNodesString); @@ -103,15 +100,6 @@ void _GenomeEditorWidget::processHeaderData() MutationRatesWidget::process(_editData->genome._mutationRates, rightColumnWidth); AlienGui::Checkbox(AlienGui::CheckboxParameters().name("Apply meta-mutations").textWidth(rightColumnWidth), _editData->genome._applyMetaMutations); - AlienGui::SliderFloat( - AlienGui::SliderFloatParameters() - .name("Accumulated mutations") - .format("%.5f") - .min(0.0f) - .max(10000.0f) - .logarithmic(true) - .textWidth(rightColumnWidth), - &_editData->genome._accumulatedMutations); table.next(); diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index bea42eee9d..30c14faf2f 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -315,6 +315,7 @@ void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) auto& object = extendedObject.object; auto origObject = object; + auto origCreature = extendedObject.creature; AlienGui::DynamicTableLayout table(TableColumnWidth); if (table.begin()) { @@ -352,7 +353,7 @@ void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) DescValidationService::get().validateAndCorrect(object); applyPendingSignalEntries(extendedObject); - if (object != origObject) { + if (object != origObject || extendedObject.creature != origCreature) { _SimulationFacade::get()->changeCell(extendedObject); } } @@ -564,11 +565,11 @@ void _InspectionWindow::processCreatureProperties(ExtendedObjectDesc& extendedOb inspectorHexId("Creature id", creature._id); inspectorText("Generation", std::to_string(creature._generation)); inspectorText("Num cells", std::to_string(creature._numCells)); + AlienGui::InputInt(AlienGui::InputIntParameters().name("Lineage id").textWidth(TextWidth), creature._lineageId); + AlienGui::InputOptionalInt(AlienGui::InputIntParameters().name("Prev lineage id").textWidth(TextWidth), creature._prevLineageId); + AlienGui::InputFloat(AlienGui::InputFloatParameters().name("Accumulated mutations").format("%.5f").textWidth(TextWidth), creature._accumulatedMutations); auto& genome = extendedObject.genome.value(); - std::stringstream frontAngle; - frontAngle << std::fixed << std::setprecision(1) << genome._frontAngle; inspectorText("Genome name", genome._name); - inspectorText("Lineage id", std::to_string(genome._lineageId)); inspectorText("Resistance to injection", genome._resistanceToInjection ? "Yes" : "No"); inspectorText("Apply meta-mutations", genome._applyMetaMutations ? "Yes" : "No"); if (AlienGui::Button(AlienGui::ButtonParameters().buttonText("Edit").name("Edit genome").textWidth(TextWidth))) { diff --git a/source/PersisterInterface/SerializerService.cpp b/source/PersisterInterface/SerializerService.cpp index a23b444aa8..75635dba10 100644 --- a/source/PersisterInterface/SerializerService.cpp +++ b/source/PersisterInterface/SerializerService.cpp @@ -284,9 +284,6 @@ namespace auto constexpr Id_Genome_Id = 0; auto constexpr Id_Genome_Name = 1; auto constexpr Id_Genome_FrontAngle = 2; - auto constexpr Id_Genome_LineageId = 3; - auto constexpr Id_Genome_AccumulatedMutations = 4; - auto constexpr Id_Genome_PrevLineageId = 5; auto constexpr Id_Genome_ResistanceToInjection = 6; auto constexpr Id_Genome_ApplyMetaMutations = 7; @@ -1030,9 +1027,6 @@ namespace cereal auto scope = getSerializationScope(task, ar); scope.addMember(Id_Genome_Id, data._id, defaultObject._id); scope.addMember(Id_Genome_Name, data._name, defaultObject._name); - scope.addMember(Id_Genome_LineageId, data._lineageId, defaultObject._lineageId); - scope.addMember(Id_Genome_AccumulatedMutations, data._accumulatedMutations, defaultObject._accumulatedMutations); - scope.addMember(Id_Genome_PrevLineageId, data._prevLineageId, defaultObject._prevLineageId); scope.addMember(Id_Genome_FrontAngle, data._frontAngle, defaultObject._frontAngle); scope.addMember(Id_Genome_ResistanceToInjection, data._resistanceToInjection, defaultObject._resistanceToInjection); scope.addMember(Id_Genome_ApplyMetaMutations, data._applyMetaMutations, defaultObject._applyMetaMutations); @@ -1060,6 +1054,9 @@ namespace auto constexpr Id_Creature_HeadUpdateId = 5; auto constexpr Id_Creature_GenomeId = 6; auto constexpr Id_Creature_MutationState = 7; + auto constexpr Id_Creature_LineageId = 3; + auto constexpr Id_Creature_PrevLineageId = 8; + auto constexpr Id_Creature_AccumulatedMutations = 9; auto constexpr Id_Solid_Energy = 0; @@ -1789,6 +1786,9 @@ namespace cereal scope.addMember(Id_Creature_HeadUpdateId, data._headUpdateId, defaultObject._headUpdateId); scope.addMember(Id_Creature_GenomeId, data._genomeId, defaultObject._genomeId); scope.addMember(Id_Creature_MutationState, data._mutationState, defaultObject._mutationState); + scope.addMember(Id_Creature_LineageId, data._lineageId, defaultObject._lineageId); + scope.addMember(Id_Creature_PrevLineageId, data._prevLineageId, defaultObject._prevLineageId); + scope.addMember(Id_Creature_AccumulatedMutations, data._accumulatedMutations, defaultObject._accumulatedMutations); } SPLIT_SERIALIZATION(CreatureDesc) From 8023cc3c47fceeec3c2c6935f08d7a22711c09c0 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Fri, 19 Jun 2026 16:38:03 +0200 Subject: [PATCH 2/9] Address review: validate creature in ExtendedObjectDesc, add changeCreatureFromTO Co-Authored-By: Claude Opus 4.8 --- source/EngineInterface/DescValidationService.cpp | 12 +++++++++++- source/EngineInterface/DescValidationService.h | 2 +- source/EngineKernels/EditKernels.cu | 5 +---- source/EngineKernels/EntityFactory.cuh | 8 ++++++++ source/Gui/InspectionWindow.cpp | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/source/EngineInterface/DescValidationService.cpp b/source/EngineInterface/DescValidationService.cpp index 5449901e3e..203ae5a216 100644 --- a/source/EngineInterface/DescValidationService.cpp +++ b/source/EngineInterface/DescValidationService.cpp @@ -298,11 +298,21 @@ void DescValidationService::validateAndCorrect(GenomeDesc& genome) } } -void DescValidationService::validateAndCorrect(ObjectDesc& object) +void DescValidationService::validateAndCorrect(ExtendedObjectDesc& extendedObject) { + auto& object = extendedObject.object; object._stiffness = std::clamp(object._stiffness, 0.0f, 1.0f); object._color = std::clamp(object._color, 0, MAX_COLORS - 1); + if (extendedObject.creature.has_value()) { + auto& creature = extendedObject.creature.value(); + creature._lineageId = std::max(creature._lineageId, 0); + if (creature._prevLineageId.has_value()) { + creature._prevLineageId = std::max(creature._prevLineageId.value(), 0); + } + creature._accumulatedMutations = std::max(creature._accumulatedMutations, 0.0f); + } + if (object.getObjectType() == ObjectType_Cell) { auto& cell = object.getCellRef(); cell._usableEnergy = std::max(0.0f, cell._usableEnergy); diff --git a/source/EngineInterface/DescValidationService.h b/source/EngineInterface/DescValidationService.h index 94ced5b25c..1371deee10 100644 --- a/source/EngineInterface/DescValidationService.h +++ b/source/EngineInterface/DescValidationService.h @@ -11,5 +11,5 @@ class DescValidationService public: void validateAndCorrect(GenomeDesc& genome); - void validateAndCorrect(ObjectDesc& object); + void validateAndCorrect(ExtendedObjectDesc& extendedObject); }; diff --git a/source/EngineKernels/EditKernels.cu b/source/EngineKernels/EditKernels.cu index 921b74b8b8..7244bc25e8 100644 --- a/source/EngineKernels/EditKernels.cu +++ b/source/EngineKernels/EditKernels.cu @@ -32,10 +32,7 @@ __global__ void cudaChangeObject(SimulationData data, TOs changeTO) entityFactory.changeObjectFromTO(changeTO, objectTO, object); if (objectTO.type == ObjectType_Cell) { auto const& creatureTO = changeTO.creatures[objectTO.typeData.cell.creatureIndex]; - auto const& creature = object->typeData.cell.creature; - creature->lineageId = creatureTO.lineageId; - creature->prevLineageId = creatureTO.prevLineageId; - creature->accumulatedMutations = creatureTO.accumulatedMutations; + entityFactory.changeCreatureFromTO(creatureTO, object->typeData.cell.creature); } } } diff --git a/source/EngineKernels/EntityFactory.cuh b/source/EngineKernels/EntityFactory.cuh index 703a3ee0d2..ba9c5cd300 100644 --- a/source/EngineKernels/EntityFactory.cuh +++ b/source/EngineKernels/EntityFactory.cuh @@ -17,6 +17,7 @@ public: __inline__ __device__ Genome* createGenomeFromTO(TOs const& to, int genomeIndex); __inline__ __device__ Object* createObjectFromTO(TOs const& to, int objectIndex, Object* objectArray); __inline__ __device__ void changeObjectFromTO(TOs const& to, ObjectTO const& objectTO, Object* object); + __inline__ __device__ void changeCreatureFromTO(CreatureTO const& creatureTO, Creature* creature); __inline__ __device__ void changeEnergyFromTO(EnergyTO const& particleTO, Energy* particle); __inline__ __device__ Energy* createEnergy(float energy, float2 const& pos, float2 const& vel, int color); @@ -609,6 +610,13 @@ __inline__ __device__ void EntityFactory::changeObjectFromTO(TOs const& to, Obje } } +__inline__ __device__ void EntityFactory::changeCreatureFromTO(CreatureTO const& creatureTO, Creature* creature) +{ + creature->lineageId = creatureTO.lineageId; + creature->prevLineageId = creatureTO.prevLineageId; + creature->accumulatedMutations = creatureTO.accumulatedMutations; +} + __inline__ __device__ void EntityFactory::changeEnergyFromTO(EnergyTO const& particleTO, Energy* particle) { particle->energy = particleTO.energy; diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index 30c14faf2f..76627b9a1b 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -350,7 +350,7 @@ void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) table.end(); } - DescValidationService::get().validateAndCorrect(object); + DescValidationService::get().validateAndCorrect(extendedObject); applyPendingSignalEntries(extendedObject); if (object != origObject || extendedObject.creature != origCreature) { From c5483f33eea1225e5ec783cf94f5288cd6cf71b7 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Fri, 19 Jun 2026 23:15:28 +0200 Subject: [PATCH 3/9] Reuse changeCreatureFromTO in createCreatureFromTO; load legacy genome lineage Migrate old-model genome lineage attributes to the creature on load; save writes them to the creature only. Co-Authored-By: Claude Opus 4.8 --- source/EngineKernels/EntityFactory.cuh | 14 +++-- .../PersisterInterface/SerializerService.cpp | 54 +++++++++++++++++-- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/source/EngineKernels/EntityFactory.cuh b/source/EngineKernels/EntityFactory.cuh index ba9c5cd300..586bd6c25b 100644 --- a/source/EngineKernels/EntityFactory.cuh +++ b/source/EngineKernels/EntityFactory.cuh @@ -325,14 +325,7 @@ __inline__ __device__ Creature* EntityFactory::createCreatureFromTO(TOs const& t creatureTO.creatureIndexOnGpu = static_cast(reinterpret_cast(creature) - _data->entities.heap.getArray()); creature->id = creatureTO.id; - creature->ancestorId = creatureTO.ancestorId; - creature->generation = creatureTO.generation; - creature->numCells = creatureTO.numCells; - creature->mutationState = creatureTO.mutationState; - creature->lineageId = creatureTO.lineageId; - creature->prevLineageId = creatureTO.prevLineageId; - creature->accumulatedMutations = creatureTO.accumulatedMutations; - creature->headUpdateId = creatureTO.headUpdateId; + changeCreatureFromTO(creatureTO, creature); auto const& genomeTO = to.genomes[creatureTO.genomeArrayIndex]; creature->genome = &_data->entities.heap.atType(genomeTO.genomeIndexOnGpu); @@ -612,9 +605,14 @@ __inline__ __device__ void EntityFactory::changeObjectFromTO(TOs const& to, Obje __inline__ __device__ void EntityFactory::changeCreatureFromTO(CreatureTO const& creatureTO, Creature* creature) { + creature->ancestorId = creatureTO.ancestorId; + creature->generation = creatureTO.generation; + creature->numCells = creatureTO.numCells; + creature->mutationState = creatureTO.mutationState; creature->lineageId = creatureTO.lineageId; creature->prevLineageId = creatureTO.prevLineageId; creature->accumulatedMutations = creatureTO.accumulatedMutations; + creature->headUpdateId = creatureTO.headUpdateId; } __inline__ __device__ void EntityFactory::changeEnergyFromTO(EnergyTO const& particleTO, Energy* particle) diff --git a/source/PersisterInterface/SerializerService.cpp b/source/PersisterInterface/SerializerService.cpp index 75635dba10..04ba941e7c 100644 --- a/source/PersisterInterface/SerializerService.cpp +++ b/source/PersisterInterface/SerializerService.cpp @@ -287,6 +287,20 @@ namespace auto constexpr Id_Genome_ResistanceToInjection = 6; auto constexpr Id_Genome_ApplyMetaMutations = 7; + // Compatibility: lineage attributes were stored in the genome in the old model and have been moved to the creature. + // Loading reads these legacy genome keys and migrates them to the creature; saving no longer writes them. Remove later. + auto constexpr Id_Genome_Legacy_LineageId = 3; + auto constexpr Id_Genome_Legacy_AccumulatedMutations = 4; + auto constexpr Id_Genome_Legacy_PrevLineageId = 5; + + struct LegacyLineage + { + int lineageId; + std::optional prevLineageId; + float accumulatedMutations; + }; + std::unordered_map legacyLineageByGenomeId; + auto constexpr Id_NeuronMutation_NodeProbability = 0; auto constexpr Id_NeuronMutation_WeightChangeSigma = 1; auto constexpr Id_NeuronMutation_BiasChangeSigma = 2; @@ -1032,6 +1046,17 @@ namespace cereal scope.addMember(Id_Genome_ApplyMetaMutations, data._applyMetaMutations, defaultObject._applyMetaMutations); scope.addDesc(Id_Genome_Genes, data._genes); scope.addDesc(Id_Genome_MutationRates, data._mutationRates); + + // Compatibility: stash legacy genome-level lineage so it can be migrated to the creature after loading. Remove later. + if (task == SerializationTask::Load) { + LegacyLineage legacy{0, std::nullopt, 0.0f}; + scope.addMember(Id_Genome_Legacy_LineageId, legacy.lineageId, 0); + scope.addMember(Id_Genome_Legacy_PrevLineageId, legacy.prevLineageId, std::optional{}); + scope.addMember(Id_Genome_Legacy_AccumulatedMutations, legacy.accumulatedMutations, 0.0f); + if (legacy.lineageId != 0 || legacy.prevLineageId.has_value() || legacy.accumulatedMutations != 0.0f) { + legacyLineageByGenomeId[data._id] = legacy; + } + } } SPLIT_SERIALIZATION(GenomeDesc) } @@ -1808,11 +1833,30 @@ namespace cereal template void loadSave(SerializationTask task, Archive& ar, Desc& description) { - auto scope = getSerializationScope(task, ar); - scope.addDesc(Id_Desc_Objects, description._objects); - scope.addDesc(Id_Desc_Energies, description._energies); - scope.addDesc(Id_Desc_Creatures, description._creatures); - scope.addDesc(Id_Desc_Genomes, description._genomes); + // Compatibility: collected during genome loading (below), applied to creatures afterwards. Remove later. + if (task == SerializationTask::Load) { + legacyLineageByGenomeId.clear(); + } + { + auto scope = getSerializationScope(task, ar); + scope.addDesc(Id_Desc_Objects, description._objects); + scope.addDesc(Id_Desc_Energies, description._energies); + scope.addDesc(Id_Desc_Creatures, description._creatures); + scope.addDesc(Id_Desc_Genomes, description._genomes); + } + + // Compatibility: migrate legacy genome-level lineage to the owning creatures. Remove later. + if (task == SerializationTask::Load && !legacyLineageByGenomeId.empty()) { + for (auto& creature : description._creatures) { + auto findResult = legacyLineageByGenomeId.find(creature._genomeId); + if (findResult != legacyLineageByGenomeId.end()) { + creature._lineageId = findResult->second.lineageId; + creature._prevLineageId = findResult->second.prevLineageId; + creature._accumulatedMutations = findResult->second.accumulatedMutations; + } + } + legacyLineageByGenomeId.clear(); + } } SPLIT_SERIALIZATION(Desc) } From 8b09b842fd822e4fd13b504da8a8967a6fd07a93 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 09:46:28 +0200 Subject: [PATCH 4/9] Remove genome lineage load/save compatibility; use new format only Co-Authored-By: Claude Opus 4.8 --- .../PersisterInterface/SerializerService.cpp | 54 ++----------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/source/PersisterInterface/SerializerService.cpp b/source/PersisterInterface/SerializerService.cpp index 04ba941e7c..75635dba10 100644 --- a/source/PersisterInterface/SerializerService.cpp +++ b/source/PersisterInterface/SerializerService.cpp @@ -287,20 +287,6 @@ namespace auto constexpr Id_Genome_ResistanceToInjection = 6; auto constexpr Id_Genome_ApplyMetaMutations = 7; - // Compatibility: lineage attributes were stored in the genome in the old model and have been moved to the creature. - // Loading reads these legacy genome keys and migrates them to the creature; saving no longer writes them. Remove later. - auto constexpr Id_Genome_Legacy_LineageId = 3; - auto constexpr Id_Genome_Legacy_AccumulatedMutations = 4; - auto constexpr Id_Genome_Legacy_PrevLineageId = 5; - - struct LegacyLineage - { - int lineageId; - std::optional prevLineageId; - float accumulatedMutations; - }; - std::unordered_map legacyLineageByGenomeId; - auto constexpr Id_NeuronMutation_NodeProbability = 0; auto constexpr Id_NeuronMutation_WeightChangeSigma = 1; auto constexpr Id_NeuronMutation_BiasChangeSigma = 2; @@ -1046,17 +1032,6 @@ namespace cereal scope.addMember(Id_Genome_ApplyMetaMutations, data._applyMetaMutations, defaultObject._applyMetaMutations); scope.addDesc(Id_Genome_Genes, data._genes); scope.addDesc(Id_Genome_MutationRates, data._mutationRates); - - // Compatibility: stash legacy genome-level lineage so it can be migrated to the creature after loading. Remove later. - if (task == SerializationTask::Load) { - LegacyLineage legacy{0, std::nullopt, 0.0f}; - scope.addMember(Id_Genome_Legacy_LineageId, legacy.lineageId, 0); - scope.addMember(Id_Genome_Legacy_PrevLineageId, legacy.prevLineageId, std::optional{}); - scope.addMember(Id_Genome_Legacy_AccumulatedMutations, legacy.accumulatedMutations, 0.0f); - if (legacy.lineageId != 0 || legacy.prevLineageId.has_value() || legacy.accumulatedMutations != 0.0f) { - legacyLineageByGenomeId[data._id] = legacy; - } - } } SPLIT_SERIALIZATION(GenomeDesc) } @@ -1833,30 +1808,11 @@ namespace cereal template void loadSave(SerializationTask task, Archive& ar, Desc& description) { - // Compatibility: collected during genome loading (below), applied to creatures afterwards. Remove later. - if (task == SerializationTask::Load) { - legacyLineageByGenomeId.clear(); - } - { - auto scope = getSerializationScope(task, ar); - scope.addDesc(Id_Desc_Objects, description._objects); - scope.addDesc(Id_Desc_Energies, description._energies); - scope.addDesc(Id_Desc_Creatures, description._creatures); - scope.addDesc(Id_Desc_Genomes, description._genomes); - } - - // Compatibility: migrate legacy genome-level lineage to the owning creatures. Remove later. - if (task == SerializationTask::Load && !legacyLineageByGenomeId.empty()) { - for (auto& creature : description._creatures) { - auto findResult = legacyLineageByGenomeId.find(creature._genomeId); - if (findResult != legacyLineageByGenomeId.end()) { - creature._lineageId = findResult->second.lineageId; - creature._prevLineageId = findResult->second.prevLineageId; - creature._accumulatedMutations = findResult->second.accumulatedMutations; - } - } - legacyLineageByGenomeId.clear(); - } + auto scope = getSerializationScope(task, ar); + scope.addDesc(Id_Desc_Objects, description._objects); + scope.addDesc(Id_Desc_Energies, description._energies); + scope.addDesc(Id_Desc_Creatures, description._creatures); + scope.addDesc(Id_Desc_Genomes, description._genomes); } SPLIT_SERIALIZATION(Desc) } From 1d584423b628a2ddb4b10358fc08d0a79fc16055 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 09:58:40 +0200 Subject: [PATCH 5/9] Layout adjusted --- source/Gui/InspectionWindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index 76627b9a1b..96512d0ab5 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -235,11 +235,11 @@ void _InspectionWindow::process() auto width = calcWindowWidth(); float height; if (_creatureMode) { - height = StyleRepository::get().scale(280.0f); + height = scale(295.0f); } else if (isObject()) { - height = StyleRepository::get().scale(500.0f); + height = scale(500.0f); } else { - height = StyleRepository::get().scale(180.0f); + height = scale(180.0f); } auto borderlessRendering = _SimulationFacade::get()->getSimulationParameters().borderlessRendering.value; ImGui::SetNextWindowBgAlpha(Const::WindowAlpha * ImGui::GetStyle().Alpha); @@ -262,7 +262,7 @@ void _InspectionWindow::process() _isFirstFrame = false; ImDrawList* drawList = ImGui::GetBackgroundDrawList(); auto entityPos = Viewport::get().mapWorldToViewPosition(DescEditService::get().getPos(entity), borderlessRendering); - auto factor = StyleRepository::get().scale(1); + auto factor = scale(1); drawList->AddLine({windowPos.x + 15.0f * factor, windowPos.y - 5.0f * factor}, {entityPos.x, entityPos.y}, Const::InspectorLineColor, 1.5f); drawList->AddRectFilled( From 0371717494d544435353bd14d485405b215ad628 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 10:11:55 +0200 Subject: [PATCH 6/9] Fix: send creature changes to engine in creature-mode inspection Creature-mode inspection returned early without calling changeCell, so edited creature attributes were never transferred and got reset on the next refresh. Add a regression test for the creature change transfer. Co-Authored-By: Claude Opus 4.8 --- source/EngineTests/DataTransferTests.cpp | 30 ++++++++++++++++++++++++ source/Gui/InspectionWindow.cpp | 5 ++++ 2 files changed, 35 insertions(+) diff --git a/source/EngineTests/DataTransferTests.cpp b/source/EngineTests/DataTransferTests.cpp index ab6cc2d14c..84a21553d5 100644 --- a/source/EngineTests/DataTransferTests.cpp +++ b/source/EngineTests/DataTransferTests.cpp @@ -310,6 +310,36 @@ TEST_F(DataTransferTests, injectGenomeToSelectedCreatures_multipleCreatures_only } } +TEST_F(DataTransferTests, changeCell_creatureLineage) +{ + auto genome = GenomeDesc(); + Desc data; + data.addCreature({ObjectDesc().id(1)}, CreatureDesc().id(1).lineageId(5), genome); + _simulationFacade->setSimulationData(data); + + auto inspectedData = _simulationFacade->getInspectedSimulationData({1}); + auto objects = DescEditService::get().getObjects(inspectedData); + ASSERT_EQ(1, objects.size()); + auto extended = std::get(objects.front()); + ASSERT_TRUE(extended.creature.has_value()); + EXPECT_EQ(5, extended.creature->_lineageId); + + extended.creature->_lineageId = 9; + extended.creature->_accumulatedMutations = 3.5f; + _simulationFacade->changeCell(extended); + + auto after = _simulationFacade->getSimulationData(); + auto creature = after.getCreatureRef(1); + EXPECT_EQ(9, creature._lineageId); + EXPECT_EQ(3.5f, creature._accumulatedMutations); + + // Same readback path the GUI uses for its periodic refresh + auto reinspected = _simulationFacade->getInspectedSimulationData({1}); + auto creature2 = reinspected.getCreatureRef(1); + EXPECT_EQ(9, creature2._lineageId); + EXPECT_EQ(3.5f, creature2._accumulatedMutations); +} + TEST_F(DataTransferTests, getInspectedSimulationData) { auto constexpr CreatureId1 = 1; diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index 96512d0ab5..720f9c4fc6 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -308,7 +308,12 @@ void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) { if (_creatureMode) { if (extendedObject.creature.has_value() && extendedObject.genome.has_value()) { + auto origCreature = extendedObject.creature; processCreatureProperties(extendedObject); + DescValidationService::get().validateAndCorrect(extendedObject); + if (extendedObject.creature != origCreature) { + _SimulationFacade::get()->changeCell(extendedObject); + } } return; } From 02ae162e7214338cd24556d6166b98649d384823 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 12:04:44 +0200 Subject: [PATCH 7/9] Refactor: split processExtendedObject into processObject and processCreature Co-Authored-By: Claude Opus 4.8 --- source/Gui/InspectionWindow.cpp | 29 +++++++++++++++++++---------- source/Gui/InspectionWindow.h | 2 ++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index 720f9c4fc6..aec3ea8b4b 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -253,7 +253,7 @@ void _InspectionWindow::process() auto windowPos = ImGui::GetWindowPos(); if (isObject()) { auto extendedObject = std::get(entity); - processObject(extendedObject); + processExtendedObject(extendedObject); EditorModel::get().addInspectedEntity(extendedObject); } else { processParticle(std::get(entity)); @@ -304,20 +304,29 @@ std::string _InspectionWindow::generateTitle() const return ss.str(); } -void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) +void _InspectionWindow::processExtendedObject(ExtendedObjectDesc& extendedObject) { if (_creatureMode) { - if (extendedObject.creature.has_value() && extendedObject.genome.has_value()) { - auto origCreature = extendedObject.creature; - processCreatureProperties(extendedObject); - DescValidationService::get().validateAndCorrect(extendedObject); - if (extendedObject.creature != origCreature) { - _SimulationFacade::get()->changeCell(extendedObject); - } + processCreature(extendedObject); + } else { + processObject(extendedObject); + } +} + +void _InspectionWindow::processCreature(ExtendedObjectDesc& extendedObject) +{ + if (extendedObject.creature.has_value() && extendedObject.genome.has_value()) { + auto origCreature = extendedObject.creature; + processCreatureProperties(extendedObject); + DescValidationService::get().validateAndCorrect(extendedObject); + if (extendedObject.creature != origCreature) { + _SimulationFacade::get()->changeCell(extendedObject); } - return; } +} +void _InspectionWindow::processObject(ExtendedObjectDesc& extendedObject) +{ auto& object = extendedObject.object; auto origObject = object; auto origCreature = extendedObject.creature; diff --git a/source/Gui/InspectionWindow.h b/source/Gui/InspectionWindow.h index a9afb7b27d..4ae1952e3a 100644 --- a/source/Gui/InspectionWindow.h +++ b/source/Gui/InspectionWindow.h @@ -24,7 +24,9 @@ class _InspectionWindow bool isObject() const; std::string generateTitle() const; + void processExtendedObject(ExtendedObjectDesc& extendedObject); void processObject(ExtendedObjectDesc& extendedObject); + void processCreature(ExtendedObjectDesc& extendedObject); void processParticle(EnergyDesc particle); void applyPendingSignalEntries(ExtendedObjectDesc& extendedObject); From f3b9252733aded9f15f3af6b8639ca1d7a04323d Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 12:16:54 +0200 Subject: [PATCH 8/9] Use VALUE_NOT_SET_UINT32 sentinel for unset creature prevLineageId Distinguishes an unset optional prevLineageId from a real value of 0. Co-Authored-By: Claude Opus 4.8 --- source/EngineImpl/DescConverterService.cpp | 4 ++-- source/EngineInterface/DescEditService.cpp | 2 +- source/EngineInterface/EngineConstants.h | 1 + source/EngineKernels/Entities.cuh | 2 +- source/EngineTests/DataTransferTests.cpp | 6 ++++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/source/EngineImpl/DescConverterService.cpp b/source/EngineImpl/DescConverterService.cpp index 00c9b3cde1..440cdfa43c 100644 --- a/source/EngineImpl/DescConverterService.cpp +++ b/source/EngineImpl/DescConverterService.cpp @@ -929,7 +929,7 @@ CreatureDesc DescConverterService::createCreatureDesc(TOs const& to, int creatur result._mutationState = creatureTO.mutationState; result._lineageId = creatureTO.lineageId; NumberGenerator::get().adaptMaxLineageId(creatureTO.lineageId); - result._prevLineageId = creatureTO.prevLineageId != 0 ? std::make_optional(creatureTO.prevLineageId) : std::nullopt; + result._prevLineageId = creatureTO.prevLineageId != VALUE_NOT_SET_UINT32 ? std::make_optional(static_cast(creatureTO.prevLineageId)) : std::nullopt; result._accumulatedMutations = creatureTO.accumulatedMutations; result._headUpdateId = creatureTO.headUpdateId; @@ -1244,7 +1244,7 @@ void DescConverterService::convertCreatureToTO( creatureTO.numCells = creatureDesc._numCells; creatureTO.mutationState = creatureDesc._mutationState; creatureTO.lineageId = creatureDesc._lineageId; - creatureTO.prevLineageId = creatureDesc._prevLineageId.value_or(0); + creatureTO.prevLineageId = creatureDesc._prevLineageId.value_or(VALUE_NOT_SET_UINT32); creatureTO.accumulatedMutations = creatureDesc._accumulatedMutations; creatureTO.genomeArrayIndex = genomeTOIndexById.at(creatureDesc._genomeId); } diff --git a/source/EngineInterface/DescEditService.cpp b/source/EngineInterface/DescEditService.cpp index 87ee87af05..06d3e73539 100644 --- a/source/EngineInterface/DescEditService.cpp +++ b/source/EngineInterface/DescEditService.cpp @@ -517,7 +517,7 @@ void DescEditService::randomizeLineageIds(Desc& description) const { for (auto& creature : description._creatures) { creature._lineageId = NumberGenerator::get().getRandomInt(); - creature._prevLineageId = 0; + creature._prevLineageId = std::nullopt; } } diff --git a/source/EngineInterface/EngineConstants.h b/source/EngineInterface/EngineConstants.h index 4b65554714..e7c9003a7a 100644 --- a/source/EngineInterface/EngineConstants.h +++ b/source/EngineInterface/EngineConstants.h @@ -1,6 +1,7 @@ #pragma once auto constexpr VALUE_NOT_SET_UINT64 = 0x7fffffffffffffff; +auto constexpr VALUE_NOT_SET_UINT32 = 0x7fffffff; auto constexpr VALUE_NOT_SET_FLOAT = 1e16f; auto constexpr MAX_OBJECT_CONNECTIONS = 6; diff --git a/source/EngineKernels/Entities.cuh b/source/EngineKernels/Entities.cuh index 0d0c4ac563..7d5826ca11 100644 --- a/source/EngineKernels/Entities.cuh +++ b/source/EngineKernels/Entities.cuh @@ -462,7 +462,7 @@ struct Creature __device__ __inline__ bool isRelatedLineage(Creature* other) { - if (prevLineageId != 0 && other->prevLineageId != 0) { + if (prevLineageId != VALUE_NOT_SET_UINT32 && other->prevLineageId != VALUE_NOT_SET_UINT32) { return lineageId == other->lineageId || lineageId == other->prevLineageId || prevLineageId == other->lineageId || prevLineageId == other->prevLineageId; } else { diff --git a/source/EngineTests/DataTransferTests.cpp b/source/EngineTests/DataTransferTests.cpp index 84a21553d5..55687f46ff 100644 --- a/source/EngineTests/DataTransferTests.cpp +++ b/source/EngineTests/DataTransferTests.cpp @@ -323,20 +323,26 @@ TEST_F(DataTransferTests, changeCell_creatureLineage) auto extended = std::get(objects.front()); ASSERT_TRUE(extended.creature.has_value()); EXPECT_EQ(5, extended.creature->_lineageId); + EXPECT_FALSE(extended.creature->_prevLineageId.has_value()); // unset optional round-trips through the TO sentinel extended.creature->_lineageId = 9; + extended.creature->_prevLineageId = 7; extended.creature->_accumulatedMutations = 3.5f; _simulationFacade->changeCell(extended); auto after = _simulationFacade->getSimulationData(); auto creature = after.getCreatureRef(1); EXPECT_EQ(9, creature._lineageId); + ASSERT_TRUE(creature._prevLineageId.has_value()); + EXPECT_EQ(7, *creature._prevLineageId); EXPECT_EQ(3.5f, creature._accumulatedMutations); // Same readback path the GUI uses for its periodic refresh auto reinspected = _simulationFacade->getInspectedSimulationData({1}); auto creature2 = reinspected.getCreatureRef(1); EXPECT_EQ(9, creature2._lineageId); + ASSERT_TRUE(creature2._prevLineageId.has_value()); + EXPECT_EQ(7, *creature2._prevLineageId); EXPECT_EQ(3.5f, creature2._accumulatedMutations); } From 67c7112e3182e7622a3d4ae331332e5d8c94bea2 Mon Sep 17 00:00:00 2001 From: Christian Heinemann Date: Sat, 20 Jun 2026 12:23:17 +0200 Subject: [PATCH 9/9] Minor refactor --- source/Gui/InspectionWindow.cpp | 25 ++++++++++--------------- source/Gui/InspectionWindow.h | 3 +-- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/source/Gui/InspectionWindow.cpp b/source/Gui/InspectionWindow.cpp index aec3ea8b4b..ef5bf074bd 100644 --- a/source/Gui/InspectionWindow.cpp +++ b/source/Gui/InspectionWindow.cpp @@ -236,7 +236,7 @@ void _InspectionWindow::process() float height; if (_creatureMode) { height = scale(295.0f); - } else if (isObject()) { + } else if (isExtendedObject()) { height = scale(500.0f); } else { height = scale(180.0f); @@ -251,9 +251,13 @@ void _InspectionWindow::process() ImGui::SetScrollY(*_savedScrollY); } auto windowPos = ImGui::GetWindowPos(); - if (isObject()) { + if (isExtendedObject()) { auto extendedObject = std::get(entity); - processExtendedObject(extendedObject); + if (_creatureMode) { + processCreature(extendedObject); + } else { + processObject(extendedObject); + } EditorModel::get().addInspectedEntity(extendedObject); } else { processParticle(std::get(entity)); @@ -283,7 +287,7 @@ uint64_t _InspectionWindow::getId() const return _entityId; } -bool _InspectionWindow::isObject() const +bool _InspectionWindow::isExtendedObject() const { auto entity = EditorModel::get().getInspectedEntity(_entityId); return std::holds_alternative(entity); @@ -296,7 +300,7 @@ std::string _InspectionWindow::generateTitle() const auto entity = EditorModel::get().getInspectedEntity(_entityId); auto const& creature = std::get(entity).creature; ss << "Creature with id 0x" << std::hex << std::uppercase << (creature.has_value() ? creature->_id : _entityId); - } else if (isObject()) { + } else if (isExtendedObject()) { ss << "Cell with id 0x" << std::hex << std::uppercase << _entityId; } else { ss << "Energy particle with id 0x" << std::hex << std::uppercase << _entityId; @@ -304,15 +308,6 @@ std::string _InspectionWindow::generateTitle() const return ss.str(); } -void _InspectionWindow::processExtendedObject(ExtendedObjectDesc& extendedObject) -{ - if (_creatureMode) { - processCreature(extendedObject); - } else { - processObject(extendedObject); - } -} - void _InspectionWindow::processCreature(ExtendedObjectDesc& extendedObject) { if (extendedObject.creature.has_value() && extendedObject.genome.has_value()) { @@ -885,7 +880,7 @@ float _InspectionWindow::calcWindowWidth() const if (_creatureMode) { return StyleRepository::get().scale(CreatureWindowWidth); } - if (isObject()) { + if (isExtendedObject()) { return StyleRepository::get().scale(CellWindowWidth); } return StyleRepository::get().scale(ParticleWindowWidth); diff --git a/source/Gui/InspectionWindow.h b/source/Gui/InspectionWindow.h index 4ae1952e3a..1f0f8b8c28 100644 --- a/source/Gui/InspectionWindow.h +++ b/source/Gui/InspectionWindow.h @@ -21,10 +21,9 @@ class _InspectionWindow uint64_t getId() const; private: - bool isObject() const; + bool isExtendedObject() const; std::string generateTitle() const; - void processExtendedObject(ExtendedObjectDesc& extendedObject); void processObject(ExtendedObjectDesc& extendedObject); void processCreature(ExtendedObjectDesc& extendedObject); void processParticle(EnergyDesc particle);