Index: cpp/frams/genetics/f9/f9_conv.cpp
===================================================================
--- cpp/frams/genetics/f9/f9_conv.cpp	(revision 1336)
+++ cpp/frams/genetics/f9/f9_conv.cpp	(revision 1337)
@@ -204,4 +204,53 @@
 void GenoConv_f90::perturbPartLocations(Model &m) //deterministic "body noise", see APPLY_DETERMINISTIC_BODY_NOISE
 {
+	// If you want to know why some structures that have different f9 genotypes, but "look the same", still get different fitness values, read on!
+	//
+	// For the purpose of the following discussion, consider 4 genotypes and the resulting structures as examples:
+	// 1. square                        /*9*/LFRB
+	// 2. square overlapping twice      /*9*/LFRBLFRB
+	// 3. reverse square                /*9*/LRFLB
+	// 4. cyclic permutation square     /*9*/FRBL
+	//
+	// All these structures "look the same" (i.e., the phenotypes are squares and a human does not distinguish any differences).
+	// However, due to noise added to location of Parts based on the consecutive number of a Part,
+	// only the phenotypes of 1. and 2. are truly, perfectly identical.
+	//
+	// Since the noise is determined by consecutive Part numbers, it is possible to create structures that "look the same",
+	// but since Parts in them are created in different order, the structures would have slightly differently disturbed
+	// coordinates (and thus fitness).
+	//
+	// To avoid this effect, the noise could instead be generated from x,y,z (i.e., XYZ_LOC) of Parts so that structures that
+	// "look the same" have exactly the same noise added to each Part (and thus identical fitness), but then the structures that are 
+	// shifted, rotated, or mirrored would still have different noise added to each Part, even though they could also be considered as
+	// "looking the same".
+	// In that approach, 3. would be the same as 1. and 2., because it is specifically constructed to use the same x,y,z Part coordinates as 1.
+	// However, 4. would still be different from 1. and 2., because it is a rotated version of 1, and coordinates of Parts are different.
+	//
+	// So there is no quick and simple way to add exactly the same noise to structures that "look the same" independently of
+	// various potential transformations. Also, what is considered "the same" may vary: is the structure (e.g. a tetrahedron) turned
+	// upside-down "the same" and do we require the same noise disturbance? It depends on the context and what we use this structure for.
+	// So that's the question of identifying transformation-invariant features in the structure that would become the source of the noise.
+	//
+	// With the current approach, structures that have precisely the same randomly perturbed Part coordinates (and thus fitness) are
+	// those that have the same Part coordinates before random perturbations AND the order of creating Parts is the same (their genotypes
+	// may still be different though, like 1. and 2.). Structures which have the same order of created Parts and are only shifted
+	// relative to each other (i.e., all Parts uniformly shifted) can also be considered precisely the same (ignoring floating point
+	// differences). There is no such example provided here because it is impossible to make in f9, because we always start
+	// "drawing" Part #0 from (0,0,0) - it is not possible in f9 to start "drawing" Part #0 from another initial point in space.
+	//
+	// A simple alternative (quite opposite to the current approach) could be to add the same amount of shift (noise) to each Part
+	// depending only on their vertical coordinate - then only vertical transformations would influence that shift, while Part creation
+	// order and all horizontal shifts, rotations, and reflections would not matter.
+	// Another determinant of randomness could be the structure of the phenotype graph, traversed depth-first, from some specific Part
+	// (i.e., closest to the centroid).
+	// Another determinant of randomness could be the integer (avoiding floating point operations if possible) squared distance from
+	// the structure center/centroid of Parts (likely displaced by some constant to avoid symmetry and ties in distances, e.g. for
+	// a vertical stick).
+	// Another (best?) approach: [start] calculate centroid of current structure, calculate distances from centroid to all Parts, take closest Part, add noise to its coordinates based on its distance from centroid and stddev(x),stddev(y),stddev(z) for all Parts in the entire phenotype (this ensures that noise depends on the orientation of the entire phenotype, i.e., a "horizontal square" and a "vertical square", after added noise, still "look the same"), then go back to [start]. In case many Parts have identical distance, take Part with lowest number (id) - i.e., use the same number as we currently use as the only source of noise, assuming that the same distance from centroid for many Parts means "any of them could be taken as first, it does not matter which one we take". Note that this approach starts to resemble some mechanisms used in the similarity estimation measures - PCA to align/"normalize" the phenotype in 3D etc.
+
+	// For now, the source of the noise stays as it is because:
+	// 1) the source is integer (consecutive number of a Part), not distances or other stats prone to floating point inaccuracies
+	// 2) the idea is extremely simple
+	// 3) "complaints/surprises" detailed above are only due to the fact that we know what is the phenotype (solution) and we have some specific expectations (like "rotated or mirrored or shifted or alternatively encoded structures should be equivalent, should behave the same way in simulation, and thus should have the same fitness"). For the optimization algorithm it does not matter and it has no such assumptions, because genotypes for "rotated or mirrored or shifted or alternatively encoded structures" are actually different, so since they are formally different solutions, it is perfectly fine that they may get a different fitness value. Also, while they are extremely close in the phenotype space, they are not neccessarily close in the f9 genotype space where the search takes place.
 	for (int i = 0; i < m.getPartCount(); i++)
 	{
