Index: cpp/frams/genetics/f4/f4_general.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_general.cpp	(revision 1228)
+++ cpp/frams/genetics/f4/f4_general.cpp	(revision 1229)
@@ -25,5 +25,5 @@
 void rolling_dec(double *v)
 {
-	*v -= 0.7853;  // 0.7853981  45 degrees
+	*v -= 0.7853;  // 0.7853981  45 degrees = pi/4 like in f1
 }
 
@@ -40,10 +40,10 @@
 	{
 		if (i >= slen)  // ran out the string, should never happen with a correct string
-			return 1;
+			return 1; //TODO MacKo 2023-04: interesting: why was this situation made undistinguishable from s[1]==stopchar ? does this have any bad consequences or is "1" just used to tell "advance as little as possible"? Anyway, this function can be eliminated when parsing is simplified.
 		if (stopchar == s[i])  // bumped into stopchar
 			return int(i);
 		if (i < slen - 1) // s[i] is not the last char
 		{
-			if (s[i] == '(')
+			if (s[i] == '(') //not an allowed char in f4, perhaps a remnant of old experiments with code
 			{
 				i += 2 + scanRecur(s + i + 1, slen - i - 1, ')');
@@ -192,14 +192,16 @@
 		// the current genotype code is processed
 		//genoRange.add(gcur->pos,gcur->pos+gcur->name.length()-1);
-		bool neuclasshandler = false; // if set to true, then a separate neuron handler below will identify the neuroclass and assign the cell to the neuron type
 
 		// To detect what genes are valid neuroclass names, but do NOT have is_neuroclass==true
-		// (just as a curiosity to ensure we properly distinguish between, for example, the "G" neuron and "G" modifier):
+		// (just as a curiosity to ensure we properly distinguish between, for example, the "G" neuron and the "G" modifier):
 		//char *TMP = (char*)gcur->name.c_str();
-		//if (gcur->is_neuroclass==false && GenoOperators::parseNeuroClass(TMP,ModelEnum::SHAPETYPE_BALL_AND_STICK))
+		//if (gcur->is_neuroclass==false && GenoOperators::parseNeuroClass(TMP, ModelEnum::SHAPETYPE_BALL_AND_STICK))
 		//	printf("Could be a valid neuroclass, but is_neuroclass==false: %s\n", gcur->name.c_str());
 
-		if (gcur->name.length() == 1 && gcur->neuclass == NULL) //one-character genes and not neuroclass names
-		{
+		if (gcur->neuclass == NULL) //not a neuron
+		{
+			if (gcur->name.length() > 1)
+				logPrintf("f4_Cell", "oneStep", LOG_WARN, "Multiple-character code that is not a neuron class name: '%s'", gcur->name.c_str()); //let's see an example of such a code...
+
 			genoRange.add(gcur->pos, gcur->pos);
 			char name = gcur->name[0];
@@ -542,36 +544,4 @@
 			default:
 			{
-				// because there are one-character neuron classes, default is move control to neuclasshandler
-				neuclasshandler = true;
-			}
-			}
-		}
-		else
-		{
-			// if many characters or single character but is_neuroclass, then it will be handled below
-			neuclasshandler = true;
-		}
-
-		if (neuclasshandler)
-		{
-			genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N:
-			if (type != CELL_UNDIFF)
-			{
-				// fix: delete this node
-				org->setRepairRemove(gcur->pos, gcur);
-				return 1;  // stop
-			}
-			// error: if no previous
-			if (dadlink == NULL)
-			{
-				// fix: delete it
-				org->setRepairRemove(gcur->pos, gcur);
-				return 1;  // stop
-			}
-			// multiple characters are neuron types. Let's check if exists in the current configuration of Framsticks
-			char *temp = (char*)gcur->name.c_str();
-			neuclass = GenoOperators::parseNeuroClass(temp, ModelEnum::SHAPETYPE_BALL_AND_STICK);
-			if (neuclass == NULL)
-			{
 				// error: unknown code
 				string buf = "Unknown code '" + gcur->name + "'";
@@ -580,4 +550,23 @@
 				return 1;
 			}
+			}
+		}
+		else
+		{
+			genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N:
+			if (type != CELL_UNDIFF)
+			{
+				// fix: delete this node
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			// error: if no previous
+			if (dadlink == NULL)
+			{
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			neuclass = gcur->neuclass;
 			type = CELL_NEURON;
 			// change of type also halts development, to give other
@@ -1300,5 +1289,5 @@
 	len = out.length();
 	if (len > 1)
-		if (out[len - 1] == '>') { (out.directWrite())[len - 1] = 0; out.endWrite(); };
+		if (out[len - 1] == '>') { (out.directWrite())[len - 1] = 0; out.endWrite(); }; //Macko 2023-04 TODO "can be omitted", but should we remove it as a rule even in generated genotypes? see if I can somehow detect junk characters after top-level '>' ends properly: /*4*/<X>N:N>whatever
 	// copy back to string
 	// if new is longer, reallocate buf
@@ -1340,12 +1329,12 @@
 // scan genotype string and build tree
 // return >1 for error (errorpos)
-int f4_processRecur(const char* genot, unsigned pos0, f4_Node *parent)
-{
-	unsigned int gpos;
-	f4_Node *par;
-
-	gpos = pos0;
-	par = parent;
-	if (gpos >= strlen(genot)) return 1;
+int f4_processRecur(const char* genot, unsigned int pos0, f4_Node *parent)
+{
+	unsigned int gpos = pos0; //MacKo 2023-04 (TODO): these two variables are often updated before return which has no effect since they are local. Seems like a half step towards making them (or just gpos) in/out parameter which would solve many issues and simplify parsing (getting rid of scanRecur()) while making it more strict.
+	f4_Node *par = parent;
+
+	if (gpos >= strlen(genot))
+		return (int)strlen(genot) + 1;
+
 	while (gpos < strlen(genot))
 	{
@@ -1369,6 +1358,9 @@
 			else // ran out
 			{
-				node = new f4_Node(">", par, int(strlen(genot)) - 1);
-				par = node;
+				//MacKo 2023-04, more strict behavior: instead of silent repair (no visible effect to the user, genotype stays invalid but is interpreted and reported as valid), we now point out where the error is. For example <X> or <X><X or <X><N:N>
+				return gpos + 1; //the problem starts here, occurs because second child (branch) <1..>2..> is not completed
+				//old silent repair:
+				//node = new f4_Node(">", par, int(strlen(genot)) - 1);
+				//par = node;
 			}
 			gpos++;
@@ -1379,5 +1371,5 @@
 			f4_Node *node = new f4_Node(">", par, gpos);
 			par = node;
-			gpos = (unsigned int)strlen(genot);
+			//gpos = (unsigned int)strlen(genot); //MacKo 2023-04: first of all, 'gpos' is a local variable so no effect; second, '>' may be internal (i.e., not the last one in the genotype), so it is a bad hint to assign strlen(). 'par' above is also local...
 			return 0;  // OK
 		}
@@ -1394,5 +1386,5 @@
 			gpos += end - (genot + gpos);
 			//gpos++;
-			//while ((genot[gpos] >= '0') && (genot[gpos] <= '9')) gpos++;node1 = new f4_Node("#", par, oldpos);
+			//while ((genot[gpos] >= '0') && (genot[gpos] <= '9')) gpos++; node1 = new f4_Node("#", par, oldpos);
 			f4_Node *node = new f4_Node("#", par, oldpos);
 			node->reps = reps;
@@ -1407,5 +1399,7 @@
 			else // ran out
 			{
-				node = new f4_Node(">", par, int(strlen(genot)) - 1);
+				return gpos; //MacKo 2023-04: report an error, better to be more strict instead of a silent repair (genotype stays invalid but is interpreted and reported as valid) with non-obvious consequences?
+				//earlier apporach - silently treating this problem (we don't ever see where the error is because it gets corrected in some way here, while parsing the genotype, and error location in the genotype is never reported):
+				//node = new f4_Node(">", par, int(strlen(genot)) - 1); // check if needed and if this is really the best repair operation; seemed to happen too many times in succession for some genotypes even though they were only a result of f4 operators, not manually created... and the operators should not generate invalid genotypes, right? Or maybe crossover does? Seems like too many #N's for closing >'s; removing #N or adding > helped. Operators somehow don't do it properly sometimes? But F4_ADD_REP adds '>'... (TODO)
 			}
 			return 0;  // OK
@@ -1491,11 +1485,10 @@
 
 	// should end with a '>'
-	if (par)
-	{
-		if (par->name != ">")
-		{
-			f4_Node *node = new f4_Node('>', par, int(strlen(genot)) - 1);
-			par = node;
-		}
+	if (par && par->name != ">")
+	{
+		//happens when gpos == strlen(genot)
+		//return gpos; //MacKo 2023-04: could report an error instead of silent repair, but repair operators only work in Cells (i.e., after the f4_Node tree has been parsed without errors and Cells can start developing) so we don't want to make a fatal error because of missing '>' here. Also after conversions from Cells to text, trailing '>' is deliberately removed... and also the simplest genotype is officially X, not X>.
+		f4_Node *node = new f4_Node('>', par, int(strlen(genot)) - 1);
+		par = node;
 	}
 
Index: cpp/frams/genetics/f4/f4_general.h
===================================================================
--- cpp/frams/genetics/f4/f4_general.h	(revision 1228)
+++ cpp/frams/genetics/f4/f4_general.h	(revision 1229)
@@ -42,4 +42,11 @@
 
 /**
+ * TODO MacKo 2023-04: not sure if this function is needed and if f4_processRecur() would not suffice
+ * if it advanced the string pointer (in/out parameter) while processing. Its returned value is always used after
+ * f4_processRecur() anyway, and in two cases likely incorrectly (for [...] to detect closing ']'
+ * and for :...: to detect closing ':') - we don't need recursion in these cases, a simple linear
+ * scan would suffice, but even this would not be needed - since we are parsing the actual characters in these cases,
+ * we do scanning anyway. So looks like this function doubles the work already done more thoroughly by f4_processRecur().
+ * 
  * Scans f4 genotype string for a stopping character and returns the position of
  * this stopping character or 1 if the end of string was reached. This method is used
@@ -494,5 +501,5 @@
  * @return 0 if processing was successful, otherwise returns the position of an error in the genotype
  */
-int f4_processRecur(const char *genot, unsigned pos0, f4_Node *parent);
+int f4_processRecur(const char *genot, unsigned int pos0, f4_Node *parent);
 
 /**
Index: cpp/frams/genetics/f4/f4_oper.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_oper.cpp	(revision 1228)
+++ cpp/frams/genetics/f4/f4_oper.cpp	(revision 1229)
@@ -16,5 +16,5 @@
 //
 // TODO the behavior of neuron input indexes during mutation seems badly implemented (see also TREAT_BAD_CONNECTIONS_AS_INVALID_GENO). Are they kept properly maintained when nodes are added and removed? This could be done well because during mutation we operate on the tree structure with cross-references between nodes (so they should not be affected by local changes in the tree), and then convert the tree back to string. Yet, the f4_Node.conn_from is an integer and these fields in nodes do not seem to be maintained on tree node adding/removal... change these integer offsets to references to node objects? But actually, do the offsets that constitute relative connection references concern the f4_Node tree structure (and all these sophisticated calculations of offsets during mutation are useful) or rather they concern the f4_Cells development? verify all situations in f4_Cell::oneStep(), case '['.
-// TODO add simplifying sequences of modifiers (so capital and small letter cancel out, like in f1) - but seems like each single modifier is a separate f4_Node? and perhaps we don't want to use the repair mechanism for this... maybe mutations, when they add/modify/remove a modifier node, should be "cleaning" the tree by removing nodes when they encounter contradictory modifiers on the same subpath?
+// TODO add simplifying sequences of modifiers (so capital and small letter cancel out, like in f1) - but seems like each single modifier is a separate f4_Node? and perhaps we don't want to use the repair mechanism for this... maybe mutations, when they add/modify/remove a modifier node, should be "cleaning" the tree by removing nodes when they encounter contradictory modifiers on the same subpath, and also limit the number of modifiers of each type just like in f1? To avoid squences like ...<X>llmlIilImmimiimmimifmfl<fifmmimilimmmiimiliffmfliIfififlliflimfliffififmiffmfliflifmIlimimiflimfiffmllliflmimifllifliliflifmIlimimiflimfiffmllliflmimifllfmIlimimiflimfiffmllliflmimiflliflimimmiflimfliffmiflifmfiffllIlififliffififmiffmfliflifIliflimimflimflfflimimifllfflifllfflimlififfiiffifIr<r<...
 // TODO add support for properties of (any class of) neurons - not just sigmoid/force/intertia (':' syntax) for N
 // TODO add mapping genotype character ranges for neural [connections]
@@ -120,8 +120,10 @@
 int Geno_f4::validate(char *& geno, const char *genoname)
 {
-	// convert geno to tree, then try to validate 20 times
+	// convert geno to a tree, then try to validate
 	f4_Node root;
 	if (f4_processRecur(geno, 0, &root) || root.childCount() != 1) return GENOPER_OK; // cannot repair
-	if (ValidateRec(&root, 20) == GENOPER_REPAIR) // if repaired, make it back to string
+
+	const int VALIDATE_TRIALS = 20;
+	if (ValidateRec(&root, VALIDATE_TRIALS) == GENOPER_REPAIR) // if repaired, make it back to string
 	{
 		geno[0] = 0;
@@ -674,6 +676,5 @@
 	if (f4_processRecur(g2, 0, &root2) || (root2.childCount() != 1)) return GENOPER_OPFAIL;
 
-	// decide amounts of crossover, 0.25-0.75
-	// adam: seems 0.1-0.9 -- MacKo
+	// decide amounts of crossover, 0.1-0.9
 	chg1 = 0.1 + rndDouble(0.8);
 	chg2 = 0.1 + rndDouble(0.8);
