Index: cpp/frams/genetics/f4/f4_conv.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_conv.cpp	(revision 1226)
+++ cpp/frams/genetics/f4/f4_conv.cpp	(revision 1227)
@@ -90,8 +90,8 @@
 	if (cells) delete cells;
 	cells = new f4_Cells(geno, 0);
-	if (GENOPER_OK != cells->geterror())
-	{
-		error = cells->geterror();
-		errorpos = cells->geterrorpos();
+	if (GENOPER_OK != cells->getErrorCode())
+	{
+		error = cells->getErrorCode();
+		errorpos = cells->getErrorPos();
 		//delete cells;
 		return error;
@@ -99,13 +99,13 @@
 
 	cells->simulate();
-	if (GENOPER_OK != cells->geterror())
-	{
-		error = cells->geterror();
-		errorpos = cells->geterrorpos();
+	if (GENOPER_OK != cells->getErrorCode())
+	{
+		error = cells->getErrorCode();
+		errorpos = cells->getErrorPos();
 		return error;
 	}
 
 	// reset recursive traverse flags
-	for (i = 0; i < cells->nc; i++)
+	for (i = 0; i < cells->cell_count; i++)
 		cells->C[i]->recProcessedFlag = 0;
 
@@ -114,10 +114,10 @@
 	// process every cell
 	int res;
-	for (i = 0; i < cells->nc; i++)
+	for (i = 0; i < cells->cell_count; i++)
 	{
 		res = buildModelRec(cells->C[i]);
 		if (res)
 		{
-			logMessage("f4_Model", "buildModelRec", 2, "Error in building Model");
+			logMessage("f4_Model", "buildFromF4", LOG_ERROR, "Error in building a Model");
 			error = res;
 			break;
@@ -135,18 +135,17 @@
 f4_Cell* f4_Model::getStick(f4_Cell *C)
 {
-	if (T_STICK4 == C->type) return C;
+	if (C->type == CELL_STICK) return C;
 	if (NULL != C->dadlink)
 		return getStick(C->dadlink);
 	// we have no more dadlinks, find any stick
-	for (int i = 0; i < cells->nc; i++)
-		if (cells->C[i]->type == T_STICK4)
+	for (int i = 0; i < cells->cell_count; i++)
+		if (cells->C[i]->type == CELL_STICK)
 			return cells->C[i];
 	// none!
-	logMessage("f4_Model", "getStick", 2, "Not a single stick");
+	logMessage("f4_Model", "getStick", LOG_ERROR, "Not a single stick");
 	return NULL;
 }
 
 
-/// updated by MacKo to follow new SDK standards (no more neuroitems)
 int f4_Model::buildModelRec(f4_Cell *C)
 {
@@ -164,7 +163,7 @@
 	// make sure parent is a stick
 	if (NULL != C->dadlink)
-		if (C->dadlink->type != T_STICK4)
-		{
-		C->dadlink = getStick(C->dadlink);
+		if (C->dadlink->type != CELL_STICK)
+		{
+			C->dadlink = getStick(C->dadlink);
 		}
 
@@ -179,5 +178,5 @@
 
 	range = C->genoRange;
-	if (C->type == T_STICK4)
+	if (C->type == CELL_STICK)
 	{
 		int jj_p1_refno;  // save for later
@@ -190,5 +189,5 @@
 				/*1.0/C->P.mass,*/ C->P.friction, C->P.ingestion, C->P.assimilation
 				//C->firstend.x, C->firstend.y, C->firstend.z
-				);
+			);
 			partidx = addFromString(PartType, tmpLine, &range);
 			if (partidx < 0) return -1;
@@ -207,5 +206,5 @@
 			//C->lastend.x, C->lastend.y, C->lastend.z
 			/*"vol=" 1.0/C->P.mass,*/ C->P.friction, C->P.ingestion, C->P.assimilation
-			);
+		);
 		partidx = addFromString(PartType, tmpLine, &range);
 		if (partidx < 0) return -2;
@@ -227,5 +226,5 @@
 			//C->P.ruch,   // rotstif
 			C->P.stamina
-			);
+		);
 		partidx = addFromString(JointType, tmpLine, &range);
 		if (partidx < 0) return -13;
@@ -234,5 +233,5 @@
 	}
 
-	if (C->type == T_NEURON4) ///<this case was updated by MacKo
+	if (C->type == CELL_NEURON)
 	{
 		const char* nclass = C->neuclass->name.c_str();
@@ -259,12 +258,10 @@
 			partno = C->dadlink->p2_refno;
 			if ((partno < 0) || (partno >= getPartCount())) return -21;
+
 			if (strcmp(nclass, "N") == 0)
-			{
 				sprintf(tmpLine, "p=%d,d=\"N:in=%g,fo=%g,si=%g\"", partno, C->inertia, C->force, C->sigmo);
-			}
 			else
-			{
 				sprintf(tmpLine, "p=%d,d=\"%s\"", partno, nclass);
-			}
+
 			partidx = addFromString(NeuronType, tmpLine, &range);
 			if (partidx < 0) return -22;
@@ -275,5 +272,12 @@
 		{
 			jointno = C->dadlink->joint_refno;
-			sprintf(tmpLine, "j=%d,d=\"%s\"", jointno, nclass);
+
+			if (strcmp(nclass, "@") == 0)
+				sprintf(tmpLine, "j=%d,d=\"@:p=%g\"", jointno, C->P.muscle_power);
+			else if (strcmp(nclass, "|") == 0)
+				sprintf(tmpLine, "j=%d,d=\"|:p=%g,r=%g\"", jointno, C->P.muscle_power, C->mz);
+			else
+				sprintf(tmpLine, "j=%d,d=\"%s\"", jointno, nclass);
+
 			partidx = addFromString(NeuronType, tmpLine, &range);
 			if (partidx < 0) return -32;
@@ -283,68 +287,20 @@
 		int n_refno = C->neuro_refno;
 
-		if ((strcmp(nclass,"N") == 0) && C->ctrl)
-		{
-			if (1 == C->ctrl)
-				sprintf(tmpLine, "j=%d,d=\"@:p=%g\"", C->dadlink->joint_refno, C->P.muscle_power);
-			else
-				sprintf(tmpLine, "j=%d,d=\"|:p=%g,r=%g\"", C->dadlink->joint_refno, C->P.muscle_power, C->mz);
-			partidx = addFromString(NeuronType, tmpLine, &range);
-			if (partidx < 0) return -32;
-			sprintf(tmpLine, "%d,%d", partidx, n_refno);
-			if (addFromString(NeuronConnectionType, tmpLine, &range) < 0) return -33;
-			this->checkpoint();
-		}
-
-		for (j = 0; j < C->nolink; j++)
-		{
-			if (NULL != C->links[j]->from)
-				buildModelRec(C->links[j]->from);
+		for (j = 0; j < C->conns_count; j++)
+		{
+			if (C->conns[j]->from != NULL)
+				buildModelRec(C->conns[j]->from);
 
 			tmpLine[0] = 0;
-			if (C->links[j]->from == NULL)
+			if (C->conns[j]->from == NULL)
 			{
-				const char* nclass = C->links[j]->t.c_str();
-				char* temp = (char*)C->links[j]->t.c_str();
-				NeuroClass *sensortest = GenoOperators::parseNeuroClass(temp);
-				//backward compatibility for G*TS
-				if (C->links[j]->t == "*" || C->links[j]->t == "S" || C->links[j]->t == "T")
-				{
-					partno = C->dadlink->p2_refno;
-					sprintf(tmpLine, "p=%d,d=\"%s\"", partno, nclass);
-				}
-				else if (C->links[j]->t == "G")
-				{
-					jointno = C->dadlink->joint_refno;
-					sprintf(tmpLine, "j=%d,d=\"%s\"", jointno, nclass);
-				}				
-				else if (sensortest->getPreferredLocation() == 0)
-				{
-					sprintf(tmpLine, "d=\"%s\"",nclass);
-				}
-				else if (sensortest->getPreferredLocation() == 1)
-				{
-					partno = C->dadlink->p2_refno;
-					sprintf(tmpLine, "p=%d,d=\"%s\"", partno, nclass);
-				}
-				else
-				{
-					jointno = C->dadlink->joint_refno;
-					sprintf(tmpLine, "j=%d,d=\"%s\"", jointno, nclass);
-				}
-
+				logMessage("f4_Model", "buildModelRec", LOG_ERROR, "Old code for sensors as inputs embedded in [connection]: C->conns[j]->from == NULL");
 			}
 			int from = -1;
-			if (tmpLine[0]) //input from receptor
-			{
-				from = addFromString(NeuronType, tmpLine, &range);
-				if (from < 0) return -34;
-				this->checkpoint();
-			} /*could be 'else'...*/
-
-			if (NULL != C->links[j]->from) // input from another neuron
-				from = C->links[j]->from->neuro_refno;
+			if (C->conns[j]->from != NULL) // input from another neuron
+				from = C->conns[j]->from->neuro_refno;
 			if (from >= 0)
 			{
-				sprintf(tmpLine, "%d,%d,%g", n_refno, from, C->links[j]->w);
+				sprintf(tmpLine, "%d,%d,%g", n_refno, from, C->conns[j]->weight);
 				if (addFromString(NeuronConnectionType, tmpLine, &range) < 0) return -35;
 				this->checkpoint();
Index: cpp/frams/genetics/f4/f4_general.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_general.cpp	(revision 1226)
+++ cpp/frams/genetics/f4/f4_general.cpp	(revision 1227)
@@ -17,4 +17,10 @@
 #endif
 
+
+#define BREAK_WHEN_REP_COUNTER_NULL //see comments where it is used
+#define EXTRA_STEP_CELL_DEVELOPMENT //see comments where it is used
+#define TREAT_BAD_CONNECTIONS_AS_INVALID_GENO //see comments where it is used
+
+
 void rolling_dec(double *v)
 {
@@ -27,7 +33,7 @@
 }
 
-int scanrec(const char* s, unsigned int slen, char stopchar)
-{
-	unsigned int i = 0;
+int scanrec(const char* s, size_t slen, char stopchar)
+{
+	size_t i = 0;
 	//DB( printf("    scan('%s', '%c')\n", s, stopchar); )
 	while (1)
@@ -36,5 +42,5 @@
 			return 1;
 		if (stopchar == s[i])  // bumped into stopchar
-			return i;
+			return int(i);
 		if (i < slen - 1) // s[i] is not the last char
 		{
@@ -58,18 +64,17 @@
 		i++;
 	}
-	return i;
-}
-
-
-f4_Cell::f4_Cell(int nname,
-	f4_Cell *ndad, int nangle, GeneProps newP)
-{
-	name = nname;
-	type = T_UNDIFF4;
+	return int(i);
+}
+
+
+f4_Cell::f4_Cell(int nnr, f4_Cell *ndad, int nangle, GeneProps newP)
+{
+	nr = nnr;
+	type = CELL_UNDIFF;
 	dadlink = ndad;
 	org = NULL;
 	genot = NULL;
 	gcur = NULL;
-	active = 1;
+	active = true;
 	repeat.clear();
 	//genoRange.clear(); -- implicit
@@ -83,9 +88,8 @@
 	zrot = 0;
 	//OM = Orient_1;
-	ctrl = 0;
 	inertia = 0.8;
 	force = 0.04;
 	sigmo = 2;
-	nolink = 0;
+	conns_count = 0;
 
 	// adjust firstend and OM if there is a stick dad
@@ -93,5 +97,5 @@
 	{
 		// make sure it is a stick (and not a stick f4_Cell!)
-		if (T_STICK4 == ndad->type)
+		if (ndad->type == CELL_STICK)
 		{
 			//firstend = ndad->lastend;
@@ -99,5 +103,5 @@
 			ndad->childcount++;
 		}
-		if (T_NEURON4 == ndad->type)
+		if (ndad->type == CELL_NEURON)
 		{
 			inertia = ndad->inertia;
@@ -112,13 +116,13 @@
 
 
-f4_Cell::f4_Cell(f4_Cells *nO, int nname, f4_node *ngeno, f4_node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP)
-{
-	name = nname;
-	type = T_UNDIFF4;
+f4_Cell::f4_Cell(f4_Cells *nO, int nnr, f4_Node *ngeno, f4_Node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP)
+{
+	nr = nnr;
+	type = CELL_UNDIFF;
 	dadlink = ndad;
 	org = nO;
 	genot = ngeno;
 	gcur = ngcur;
-	active = 1;
+	active = true;
 	repeat.clear();
 	//genoRange.clear(); -- implicit
@@ -135,9 +139,8 @@
 	zrot = 0;
 	//OM = Orient_1;
-	ctrl = 0;
 	inertia = 0.8;
 	force = 0.04;
 	sigmo = 2;
-	nolink = 0;
+	conns_count = 0;
 
 	// adjust firstend and OM if there is a stick dad
@@ -145,5 +148,5 @@
 	{
 		// make sure it is a stick (and not a stick f4_Cell!)
-		if (T_STICK4 == ndad->type)
+		if (ndad->type == CELL_STICK)
 		{
 			//firstend = ndad->lastend;
@@ -151,5 +154,5 @@
 			ndad->childcount++;
 		}
-		if (T_NEURON4 == ndad->type)
+		if (ndad->type == CELL_NEURON)
 		{
 			inertia = ndad->inertia;
@@ -166,11 +169,11 @@
 f4_Cell::~f4_Cell()
 {
-	// remove links
-	if (nolink)
+	// remove connections
+	if (conns_count)
 	{
 		int i;
-		for (i = nolink - 1; i >= 0; i--)
-			delete links[i];
-		nolink = 0;
+		for (i = conns_count - 1; i >= 0; i--)
+			delete conns[i];
+		conns_count = 0;
 	}
 }
@@ -178,16 +181,10 @@
 
 /* return codes:
-	>1 error at pos
-	0  halt development for a cycle
-	-1  development finished OK
+	1 error at pos
+	0  halt development (yield) for a cycle
 	*/
-int f4_Cell::onestep()
-{
-	if (gcur == NULL)
-	{
-		active = 0;
-		return 0;  // stop
-	}
-	while (NULL != gcur)
+int f4_Cell::oneStep()
+{
+	while (gcur != NULL)
 	{
 		//DB( printf("  %d (%d) executing '%c' %d\n", name, type, gcur->name, gcur->pos); )
@@ -195,7 +192,13 @@
 		// the current genotype code is processed
 		//genoRange.add(gcur->pos,gcur->pos+gcur->name.length()-1);
-		bool neuclasshandler = false; // if set to true, then there is a set of characters that can be assigned to a neuron class type
-		// old semantics, one-character
-		if (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):
+		//char *TMP = (char*)gcur->name.c_str();
+		//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
 		{
 			genoRange.add(gcur->pos, gcur->pos);
@@ -209,5 +212,5 @@
 
 				// error: sticks cannot divide
-				if (T_STICK4 == type)
+				if (type == CELL_STICK)
 				{
 					// cannot fix
@@ -217,5 +220,5 @@
 
 				// undiff divides
-				if (T_UNDIFF4 == type)
+				if (type == CELL_UNDIFF)
 				{
 					// commacount is set only when daughter turns into X
@@ -224,13 +227,14 @@
 					GeneProps newP = P;
 					newP.propagateAlong(false);
-					f4_Cell *tmp = new f4_Cell(org, org->nc, genot, gcur->child2, this, commacount, newP);
+					f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2, this, commacount, newP);
 					tmp->repeat = repeat;
 					repeat.clear();
 					org->addCell(tmp);
 				}
-				// a neuron divides: create a new, duplicate links
-				if (T_NEURON4 == type) {
+				// a neuron divides: create a new, duplicate connections
+				if (type == CELL_NEURON)
+				{
 					// daughter cell
-					f4_Cell *tmp = new f4_Cell(org, org->nc, genot, gcur->child2,
+					f4_Cell *tmp = new f4_Cell(org, org->cell_count, genot, gcur->child2,
 						// has the same dadlink
 						this->dadlink, commacount, P);
@@ -238,13 +242,13 @@
 					repeat.clear();
 					// it is a neuron from start
-					tmp->type = T_NEURON4;
+					tmp->type = CELL_NEURON;
 					// it has the same type as the parent neuron
 					tmp->neuclass = neuclass;
-					// duplicate links
-					f4_CellLink *ll;
-					for (int i = 0; i < nolink; i++)
+					// duplicate connections
+					f4_CellConn *conn;
+					for (int i = 0; i < conns_count; i++)
 					{
-						ll = links[i];
-						tmp->addlink(ll->from, ll->w, ll->t);
+						conn = conns[i];
+						tmp->addConnection(conn->from, conn->weight);
 					}
 					org->addCell(tmp);
@@ -258,5 +262,5 @@
 			{
 				// finish
-				// see if there is a repet count
+				// see if there is a repeat count
 				if (repeat.top > 0)
 				{ // there is a repeat counter
@@ -280,4 +284,12 @@
 					{
 						repeat.pop();
+						// MacKo 2023-04: originally, there was no "break" nor "return" here (hence [[fallthrough]]; needed below for modern compilers) - not sure if this was intentional or overlooking.
+						// This case can be tested with "#0" in the genotype. Anyway, there seems to be no difference in outcomes with and without "break".
+						// However, falling through [[fallthrough]] below for count==0 causes performing repeat.push(repeat_ptr(gcur, 0)) while the very reason
+						// we are here is that repeat count==0 (one of the conditions for isNull()), so I opted to add "break", but marked this tentative decision using #define.
+						// The ultimate informed decision would require understanding all the logic and testing all the edge cases.
+#ifdef BREAK_WHEN_REP_COUNTER_NULL
+						break;
+#endif
 					}
 				}
@@ -285,8 +297,8 @@
 				{
 					// error: still undiff
-					if (T_UNDIFF4 == type)
+					if (type == CELL_UNDIFF)
 					{
 						// fix it: insert an 'X'
-						f4_node *insertnode = new f4_node("X", NULL, gcur->pos);
+						f4_Node *insertnode = new f4_Node("X", NULL, gcur->pos);
 						if (org->setRepairInsert(gcur->pos, gcur, insertnode)) // not in repair mode, release
 							delete insertnode;
@@ -294,11 +306,16 @@
 					}
 					repeat.clear();
-					active = 0;  // stop
+					active = false;  // stop
 					// eat up rest
+					int remaining_nodes = gcur->count() - 1;
+					if (remaining_nodes > 0)
+						logPrintf("f4_Cell", "oneStep", LOG_WARN, "Ignoring junk genetic code: %d node(s) at position %d", remaining_nodes, gcur->child->pos); //let's see an example of such a genotype...
 					gcur = NULL;
 					return 0;
 				}
 			}
+#ifndef BREAK_WHEN_REP_COUNTER_NULL
 			[[fallthrough]];
+#endif
 			case '#':
 			{
@@ -311,5 +328,5 @@
 					return 1;  // stop
 				}
-				repeat.push(repeat_ptr(gcur, gcur->i1));
+				repeat.push(repeat_ptr(gcur, gcur->reps));
 				gcur = gcur->child;
 				break;
@@ -324,5 +341,5 @@
 			{
 				// error: if neuron
-				if (T_NEURON4 == type)
+				if (type == CELL_NEURON)
 				{
 					// fix: delete it
@@ -354,6 +371,7 @@
 			{
 				// error: if neuron
-				if (T_NEURON4 == type)
-				{
+				if (type == CELL_NEURON) //some neurons have the same single-letter names as modifiers (for example G,S,D), but they are supposed to have is_neuroclass==true so they should indeed not be handled here
+				{//however, what we see here is actually modifiers such as IdqEbWL (so not valid neuroclasses) that occurred within an already differentiated cell type==CELL_NEURON.
+					//printf("Handled as a modifier, but type==CELL_NEURON: '%c'\n", name);
 					// fix: delete it
 					org->setRepairRemove(gcur->pos, gcur);
@@ -368,5 +386,5 @@
 				// turn undiff. cell into a stick
 				// error: already differentiated
-				if (T_UNDIFF4 != type)
+				if (type != CELL_UNDIFF)
 				{
 					// fix: delete this node
@@ -374,5 +392,5 @@
 					return 1;  // stop
 				}
-				type = T_STICK4;
+				type = CELL_STICK;
 				// fix dad commacount and own anglepos
 				if (NULL != dadlink)
@@ -381,20 +399,13 @@
 					anglepos = dadlink->commacount;
 				}
-				// change of type halts developments, see comment at 'N'
+				// change of type halts developments, see comment at 'neuclasshandler' below
 				gcur = gcur->child;
 				return 0;
 			}
-			case 'N':
-			{
-				// turn undiff. cell into a neuron
-				// error: already differentiated
-				if (T_UNDIFF4 != type)
-				{
-					// fix: delete this node
-					org->setRepairRemove(gcur->pos, gcur);
-					return 1;  // stop
-				}
-				// error: if no previous
-				if (NULL == dadlink)
+			case '[':
+			{
+				// connection to neuron
+				// error: not a neuron
+				if (type != CELL_NEURON)
 				{
 					// fix: delete it
@@ -402,32 +413,91 @@
 					return 1;  // stop
 				}
-				string temp1 = "N";
-				char *temp = (char*)temp1.c_str();
-				neuclass = GenoOperators::parseNeuroClass(temp);
-				type = T_NEURON4;
-				// change of type also halts development, to give other
-				// cells a chance for adjustment.  Namely, it is important
-				// to wait for other cells to turn N before adding links
+				// input [%d:%g]
+				int relfrom = gcur->conn_from;
+				double weight = gcur->conn_weight;
+				f4_Cell *neu_from = NULL;
+
+				// input from other neuron
+				// find neuron at relative i
+				// find own index
+				int this_index = 0, neu_counter = 0;
+				for (int i = 0; i < org->cell_count; i++)
+				{
+					if (org->C[i]->type == CELL_NEURON) neu_counter++;
+					if (org->C[i] == this) { this_index = neu_counter - 1; break; }
+				}
+				// find index of incoming
+				int from_index = this_index + relfrom;
+
+				if (from_index < 0) goto wait_conn;
+				if (from_index >= org->cell_count) goto wait_conn;
+
+				// find that neuron
+				neu_counter = 0;
+				int from;
+				for (from = 0; from < org->cell_count; from++)
+				{
+					if (org->C[from]->type == CELL_NEURON) neu_counter++;
+					if (from_index == (neu_counter - 1)) break;
+				}
+				if (from >= org->cell_count) goto wait_conn;
+				neu_from = org->C[from];
+
+				// add connection
+				// error: could not add connection (too many?)
+				if (addConnection(neu_from, weight))
+				{
+					// cannot fix
+					org->setError(gcur->pos);
+					return 1;  // stop
+				}
 				gcur = gcur->child;
-				return 0;
-			}
-			case '@':
-			case '|':
-			{
-				// neuron rotating / bending
-				int j = 1;
-				if ('@' == name) j = 1; // rot
+				break;
+			}
+		wait_conn:
+			{
+				// wait for other neurons to develop
+				// if there are others still active
+
+				int active_count = 0;
+				for (int i = 0; i < org->cell_count; i++)
+					if (org->C[i]->active) active_count++;
+				active = false; // next time when we reach wait_conn, we will not count ourselves as active (if we were the last active cell, we got a chance to continue development for one development step only)
+				if (active_count > 0)
+					return 0;  // there is at least one active (including ourselves), halt, try again
+
+#ifdef TREAT_BAD_CONNECTIONS_AS_INVALID_GENO // MacKo 2023-04: there were so many invalid connections accumulating in the genotype (and stopping processing of the chain of gcur->child) that it looks like treating them as errors is better... in 2000's, Framsticks neurons were flexible when it comes to inputs and outputs (for example, when asked, muscles would provide an output too, and neurons that ignored inputs would still accept them when connected) so f4 could create connections pretty randomly, but after 2000's we attempt to respect neurons' getPreferredInputs() and getPreferredOutput() so the network of connections has more constraints.
+				if (gcur->parent->name == "#")
+				{
+					// MacKo 2023-04: Unfortunately the logic of multiplicating connections is not ideal...
+					//TREAT_BAD_CONNECTIONS_AS_INVALID_GENO without this "#" exception would break /*4*/<X>N:N#5<[1:1]>
+					// because every neuron wants to get an input from the neuron that will be created next
+					// and all is fine until the last created neuron, which wants to get an input from another one which will not be created
+					// (3 gets from 4, 4 gets from 5, 5 wants to get from 6 (relative connection offset for each of them is 1),
+					// but 6 will not get created and if we want to TREAT_BAD_CONNECTIONS_AS_INVALID_GENO, we produce an error...
+					// We would like to have this multiplication working, but OTAH we don't want to accept bad connections because then they tend to multiply as junk genes and bloat the genotype also causing more and more neutral mutations...
+					//so this condition and checking for "#" is a simple way to be kind to some, but not all, bad connections, and not raise errors. Perhaps too kind and we open the door for too many cases with invalid connections.
+					//Maybe it would be better to perform this check before addConnection(), seeing that for example we process the last iteration of the repetition counter? But how would we know that the (needed here) input neuron will not be developed later by other dividing cells...
+					active = true; //not sure if needed, but let this cell have the chance to continue for as long as many children in the tree are left
+					gcur = gcur->child;
+					return 0;
+				}
 				else
-					if ('|' == name) j = 2; // bend
-
-				// if undiff, then this is a new muscle. Thanks to f4_processrec @ and | case we can skip repairing
-				if (T_UNDIFF4 == type)
-				{
-					neuclasshandler = true;
-					break;
-				}
-
-				// error: not a neuron (stick)
-				if (T_NEURON4 != type)
+				{
+					//org->setError(gcur->pos); //in case setRepairRemove() would not always produce reasonable results
+					org->setRepairRemove(gcur->pos, gcur); //produces unexpected results? or NOT? TODO verify, some genotypes earlier produced strange outcomes of this repair (produced a valid genotype, but some neurons were multiplied/copied after repair - maybe because when a branch of '<' (or something else) is missing, the other branch is copied?)
+					return 1;  // stop
+				}
+#else
+				// no more actives, cannot add connection, ignore, but treat not as an error - before 2023-04
+				gcur = gcur->child;
+#endif
+			}
+			break;
+			case ':':
+			{
+				// neuron parameter
+				// error: not a neuron
+				if (type != CELL_NEURON)
 				{
 					// fix: delete it
@@ -435,122 +505,8 @@
 					return 1;  // stop
 				}
-				// error: already has control
-				if (ctrl != 0)
-				{
-					// fix: delete it
-					org->setRepairRemove(gcur->pos, gcur);
-					return 1;  // stop
-				}
-				// make neuron ctrl = 1 or 2
-				ctrl = j;
-				gcur = gcur->child;
-				break;
-			}
-			case '[':
-			{
-				// link to neuron
-				// error: not a neuron
-				if (T_NEURON4 != type)
-				{
-					// fix: delete it
-					org->setRepairRemove(gcur->pos, gcur);
-					return 1;  // stop
-				}
-				// input (sensor or %d)
-				int t = gcur->i1;
-				int relfrom = gcur->l1;
-				float w = gcur->f1;
-				f4_Cell *tneu = NULL;
-				if (t < 0) // wrong sensor
-				{
-					string buf = "wrong sensor in link '";
-					buf.append(gcur->s1);
-					buf.append("'");
-					logMessage("f4_Cell", "onestep", LOG_ERROR, buf.c_str());
-					org->setRepairRemove(gcur->pos, gcur);
-					return 1;
-				}
-				else if (t > 0) // sensors
-				{
-					char *temp = (char*)gcur->s1.c_str();
-					NeuroClass *sensortest = GenoOperators::parseNeuroClass(temp);
-					if (sensortest == NULL || sensortest->getPreferredInputs() != 0)
-					{
-						// error: unknown code
-						string buf = "wrong sensor in link '";
-						buf.append(gcur->s1);
-						buf.append("'");
-						logMessage("f4_Cell", "onestep", LOG_ERROR, buf.c_str());
-						org->setRepairRemove(gcur->pos, gcur);
-						return 1;
-					}
-				}
-				else {
-					// input from other neuron
-					// find neuron at relative i
-					// find own index
-					int j = 0, k = 0;
-					for (int i = 0; i < org->nc; i++)
-					{
-						if (org->C[i]->type == T_NEURON4) k++;
-						if (org->C[i] == this) { j = k - 1; break; }
-					}
-					// find index of incoming
-					j = j + relfrom;
-					if (j < 0) goto wait_link;
-					if (j >= org->nc) goto wait_link;
-					// find that neuron
-					k = 0;
-					int i;
-					for (i = 0; i < org->nc; i++)
-					{
-						if (org->C[i]->type == T_NEURON4) k++;
-						if (j == (k - 1)) break;
-					}
-					if (i >= org->nc) goto wait_link;
-					tneu = org->C[i];
-				}
-				// add link
-				// error: could not add link (too many?)
-				if (addlink(tneu, w, gcur->s1))
-				{
-					// cannot fix
-					org->setError(gcur->pos);
-					return 1;  // stop
-				}
-				gcur = gcur->child;
-				break;
-			}
-		wait_link:
-			{
-				// wait for other neurons to develop
-				// if there are others still active
-				active = 0;
-				int j = 0;
-				for (int i = 0; i < org->nc; i++)
-				{
-					if (org->C[i]->active) j++;
-				}
-				if (j > 0)
-					return 0;  // there is other active, halt, try again
-				// no more actives, cannot add link, ignore, but treat not as an error
-				gcur = gcur->child;
-			}
-			break;
-			case ':':
-			{
-				// neuron parameter
-				// error: not a neuron
-				if (T_NEURON4 != type)
-				{
-					// fix: delete it
-					org->setRepairRemove(gcur->pos, gcur);
-					return 1;  // stop
-				}
-				int j = gcur->l1;
-				switch ((char)gcur->i1)
+				switch (gcur->prop_symbol)
 				{
 				case '!':
-					if (j)
+					if (gcur->prop_increase)
 						force += (1.0 - force) * 0.2;
 					else
@@ -558,5 +514,5 @@
 					break;
 				case '=':
-					if (j)
+					if (gcur->prop_increase)
 						inertia += (1.0 - inertia) * 0.2;
 					else
@@ -564,5 +520,5 @@
 					break;
 				case '/':
-					if (j)
+					if (gcur->prop_increase)
 						sigmo *= 1.4;
 					else
@@ -586,5 +542,5 @@
 			default:
 			{
-				// because there are one-character neuron classes, default move control to neuclasshandler
+				// because there are one-character neuron classes, default is move control to neuclasshandler
 				neuclasshandler = true;
 			}
@@ -593,5 +549,5 @@
 		else
 		{
-			// if many characters, then it needs to be parsed below
+			// if many characters or single character but is_neuroclass, then it will be handled below
 			neuclasshandler = true;
 		}
@@ -599,6 +555,6 @@
 		if (neuclasshandler)
 		{
-			genoRange.add(gcur->pos, gcur->pos + gcur->name.length() + 2 - 1); // +2 for N:
-			if (T_UNDIFF4 != type)
+			genoRange.add(gcur->pos, gcur->pos + int(gcur->name.length()) + 2 - 1); // +2 for N:
+			if (type != CELL_UNDIFF)
 			{
 				// fix: delete this node
@@ -607,5 +563,5 @@
 			}
 			// error: if no previous
-			if (NULL == dadlink)
+			if (dadlink == NULL)
 			{
 				// fix: delete it
@@ -613,35 +569,35 @@
 				return 1;  // stop
 			}
-			// multiple characters are neuron types. Need to check if exists
+			// 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);
+			neuclass = GenoOperators::parseNeuroClass(temp, ModelEnum::SHAPETYPE_BALL_AND_STICK);
 			if (neuclass == NULL)
 			{
 				// error: unknown code
-				string buf = "unknown code '";
-				buf.append(gcur->name);
-				buf.append("'");
-				logMessage("f4_Cell", "onestep", 2, buf.c_str());
+				string buf = "Unknown code '" + gcur->name + "'";
+				logMessage("f4_Cell", "oneStep", LOG_ERROR, buf.c_str());
 				org->setRepairRemove(gcur->pos, gcur);
 				return 1;
 			}
-			type = T_NEURON4; //they belong to neurons
+			type = CELL_NEURON;
+			// change of type also halts development, to give other
+			// cells a chance for adjustment.  Namely, it is important
+			// to wait for other cells to turn to neurons before adding connections
 			gcur = gcur->child;
 			return 0; //stop
 		}
 	}
-	active = 0;  // done
+	active = false;  // done
 	return 0;
 }
 
 
-int f4_Cell::addlink(f4_Cell *nfrom, double nw, string nt)
-{
-	// if incoming neuron does not produce output, return error
-	if (nfrom != NULL && nfrom->neuclass->getPreferredOutput() == 0) return -1;
-	if (neuclass->getPreferredInputs() != -1 && nolink >= neuclass->getPreferredInputs()) return -1;
-	if (nolink >= MAXINPUTS - 1) return -1; // full!
-	links[nolink] = new f4_CellLink(nfrom, nw, nt);
-	nolink++;
+int f4_Cell::addConnection(f4_Cell *nfrom, double nweight)
+{
+	if (nfrom->neuclass->getPreferredOutput() == 0) return -1; // if incoming neuron does not produce output, return error
+	if (neuclass->getPreferredInputs() != -1 && conns_count >= neuclass->getPreferredInputs()) return -1; //cannot add more inputs to this neuron
+	if (conns_count >= F4_MAX_CELL_INPUTS - 1) return -1; // over hardcoded limit
+	conns[conns_count] = new f4_CellConn(nfrom, nweight);
+	conns_count++;
 	return 0;
 }
@@ -661,19 +617,19 @@
 
 	// make sure its parent is processed first
-	if (NULL != dadlink)
+	if (dadlink != NULL)
 		dadlink->adjustRec();
 
 	// count children
 	childcount = 0;
-	for (i = 0; i < org->nc; i++)
+	for (i = 0; i < org->cell_count; i++)
 	{
 		if (org->C[i]->dadlink == this)
-			if (org->C[i]->type == T_STICK4)
+			if (org->C[i]->type == CELL_STICK)
 				childcount++;
 	}
 
-	if (type == T_STICK4)
-	{
-		if (NULL == dadlink)
+	if (type == CELL_STICK)
+	{
+		if (dadlink == NULL)
 		{
 			//firstend = Pt3D_0;
@@ -721,18 +677,17 @@
 
 
-f4_CellLink::f4_CellLink(f4_Cell *nfrom, double nw, string nt)
+f4_CellConn::f4_CellConn(f4_Cell *nfrom, double nweight)
 {
 	from = nfrom;
-	w = nw;
-	t = nt;
-}
-
-
-
-f4_Cells::f4_Cells(f4_node *genome, int nrepair)
+	weight = nweight;
+}
+
+
+
+f4_Cells::f4_Cells(f4_Node *genome, int nrepair)
 {
 	// create ancestor cell
 	repair = nrepair;
-	error = 0;
+	errorcode = GENOPER_OK;
 	errorpos = -1;
 	repair_remove = NULL;
@@ -742,5 +697,5 @@
 	f4rootnode = NULL;
 	C[0] = new f4_Cell(this, 0, genome, genome, NULL, 0, GeneProps::standard_values);
-	nc = 1;
+	cell_count = 1;
 }
 
@@ -750,5 +705,5 @@
 	int res;
 	repair = nrepair;
-	error = 0;
+	errorcode = GENOPER_OK;
 	errorpos = -1;
 	repair_remove = NULL;
@@ -759,9 +714,9 @@
 
 	// transform geno from string to nodes
-	f4rootnode = new f4_node();
-	res = f4_processrec(genome.c_str(), (unsigned)0, f4rootnode);
+	f4rootnode = new f4_Node();
+	res = f4_processrec(genome.c_str(), 0, f4rootnode);
 	if ((res < 0) || (1 != f4rootnode->childCount()))
 	{
-		error = GENOPER_OPFAIL;
+		errorcode = GENOPER_OPFAIL;
 		errorpos = -1;
 	}
@@ -769,5 +724,5 @@
 	// create ancestor cell
 	C[0] = new f4_Cell(this, 0, f4rootnode->child, f4rootnode->child, NULL, 0, GeneProps::standard_values);
-	nc = 1;
+	cell_count = 1;
 }
 
@@ -776,9 +731,9 @@
 	// release cells
 	int i;
-	if (nc)
-	{
-		for (i = nc - 1; i >= 0; i--)
+	if (cell_count)
+	{
+		for (i = cell_count - 1; i >= 0; i--)
 			delete C[i];
-		nc = 0;
+		cell_count = 0;
 	}
 	if (f4rootnode)
@@ -787,23 +742,21 @@
 
 
-int f4_Cells::onestep()
-{
-	int i, ret, oldnc, ret2;
-	oldnc = nc;
-	ret = 0;
-	for (i = 0; i < oldnc; i++)
-	{
-		ret2 = C[i]->onestep();
-		if (ret2 > 0)
+bool f4_Cells::oneStep()
+{
+	int old_cell_count = cell_count; //cell_count may change in the loop as new cells may be appended because cells may be dividing
+	for (int i = 0; i < old_cell_count; i++)
+	{
+		int cellstep_ret = C[i]->oneStep(); //keeps calling independently of C[i]->active
+		if (cellstep_ret > 0)
 		{
 			// error
-			C[i]->active = 0;  // stop
-			return 0;
-		}
-		// if still active
+			C[i]->active = false;  // stop
+			return false;
+		}
+	}
+	for (int i = 0; i < cell_count; i++) //we check all cells, including newly created ones
 		if (C[i]->active)
-			ret = 1;
-	}
-	return ret;
+			return true; //at least one cell is still active. TODO maybe the development should stop NOT because of the "active" field (there is this strange "yielding" state too), but by observing the progress of all cells and continuing the development while (errorcode==0 AND (gcur of at least one cell changed OR cell_count changed)) ? Also get rid of return 1/return 0, just observe error.
+	return false;
 }
 
@@ -811,20 +764,31 @@
 int f4_Cells::simulate()
 {
-	int i;
-	error = GENOPER_OK;
-
-	for (i = 0; i < nc; i++)  C[i]->active = 1;
-
-	// execute onestep() in a cycle
-	while (onestep());
-
-	if (GENOPER_OK != error) return error;
+	constexpr bool print_debugging = false; //print the state of cells during development
+	errorcode = GENOPER_OK;
+
+	for (int i = 0; i < cell_count; i++)  C[i]->active = true;
+
+	if (print_debugging) f4_Node::print_tree(C[0]->genot, 0);
+	if (print_debugging) print_cells("Initialization");
+
+	// execute oneStep() in a cycle
+	while (oneStep()) if (print_debugging) print_cells("Development step");
+	if (print_debugging) print_cells("After last development step");
+
+#ifdef EXTRA_STEP_CELL_DEVELOPMENT
+	if (errorcode == GENOPER_OK)
+	{
+		oneStep(); if (print_debugging) print_cells("After extra step"); //for these "halted" (yielding) cells (they have active==false) that wait for other cells to develop. Without this step, the last, recently halted one(s) may miss the "retrying" step if all active==true cells became active==false in the last step.
+	}
+#endif
+
+	if (errorcode != GENOPER_OK) return errorcode;
 
 	// fix neuron attachements
-	for (i = 0; i < nc; i++)
-	{
-		if (C[i]->type == T_NEURON4)
-		{
-			while (T_NEURON4 == C[i]->dadlink->type)
+	for (int i = 0; i < cell_count; i++)
+	{
+		if (C[i]->type == CELL_NEURON)
+		{
+			while (C[i]->dadlink->type == CELL_NEURON)
 			{
 				C[i]->dadlink = C[i]->dadlink->dadlink;
@@ -835,10 +799,10 @@
 	// there should be no undiff. cells
 	// make undifferentiated cells sticks
-	for (i = 0; i < nc; i++)
-	{
-		if (C[i]->type == T_UNDIFF4)
-		{
-			C[i]->type = T_STICK4;
-			//seterror();
+	for (int i = 0; i < cell_count; i++)
+	{
+		if (C[i]->type == CELL_UNDIFF)
+		{
+			C[i]->type = CELL_STICK;
+			//setError();
 		}
 	}
@@ -846,13 +810,41 @@
 	// recursive adjust
 	// reset recursive traverse flags
-	for (i = 0; i < nc; i++)
+	for (int i = 0; i < cell_count; i++)
 		C[i]->recProcessedFlag = 0;
 	// process every cell
-	for (i = 0; i < nc; i++)
+	for (int i = 0; i < cell_count; i++)
 		C[i]->adjustRec();
 
 	//DB( printf("Cell simulation done, %d cells. \n", nc); )
 
-	return error;
+	if (print_debugging) print_cells("Final");
+
+	return errorcode;
+}
+
+
+void f4_Cells::print_cells(const char* description)
+{
+	printf("------ %-55s ------ errorcode=%d, errorpos=%d\n", description, getErrorCode(), getErrorPos());
+	for (int i = 0; i < cell_count; i++)
+	{
+		f4_Cell *c = C[i];
+		string type;
+		switch (c->type)
+		{
+		case CELL_UNDIFF: type = "UNDIFF"; break;
+		case CELL_STICK:  type = "STICK"; break;
+		case CELL_NEURON: type = string("NEURON:") + c->neuclass->name.c_str(); break;
+		default: type = std::to_string(c->type);
+		}
+		const char *status = c->active ? "active" : (c->gcur != NULL ? "yielding" : ""); //yielding = not active but waiting for other cells
+		printf("%2d(%-8s)  nr=%d \t type=%-15s \t genot=%s \t gcurrent=%s", i, status, c->nr, type.c_str(), c->genot->name.c_str(), c->gcur ? c->gcur->name.c_str() : "null");
+		if (c->gcur && c->gcur->name == "[")
+			printf("\tfrom=%d  weight=%g", c->gcur->conn_from, c->gcur->conn_weight);
+		printf("\n");
+		for (int l = 0; l < c->conns_count; l++)
+			printf("\tconn:%d from=%d weight=%g\n", l, c->conns[l]->from->nr, c->conns[l]->weight);
+	}
+	printf("\n");
 }
 
@@ -860,11 +852,11 @@
 void f4_Cells::addCell(f4_Cell *newcell)
 {
-	if (nc >= MAX4CELLS - 1)
+	if (cell_count >= F4_MAX_CELLS - 1)
 	{
 		delete newcell;
 		return;
 	}
-	C[nc] = newcell;
-	nc++;
+	C[cell_count] = newcell;
+	cell_count++;
 }
 
@@ -872,19 +864,19 @@
 void f4_Cells::setError(int nerrpos)
 {
-	error = GENOPER_OPFAIL;
+	errorcode = GENOPER_OPFAIL;
 	errorpos = nerrpos;
 }
 
-void f4_Cells::setRepairRemove(int nerrpos, f4_node *rem)
+void f4_Cells::setRepairRemove(int nerrpos, f4_Node *rem)
 {
 	if (!repair)
 	{
 		// not in repair mode, treat as repairable error
-		error = GENOPER_REPAIR;
+		errorcode = GENOPER_REPAIR;
 		errorpos = nerrpos;
 	}
 	else
 	{
-		error = GENOPER_REPAIR;
+		errorcode = GENOPER_REPAIR;
 		errorpos = nerrpos;
 		repair_remove = rem;
@@ -892,10 +884,10 @@
 }
 
-int f4_Cells::setRepairInsert(int nerrpos, f4_node *parent, f4_node *insert)
+int f4_Cells::setRepairInsert(int nerrpos, f4_Node *parent, f4_Node *insert)
 {
 	if (!repair)
 	{
 		// not in repair mode, treat as repairable error
-		error = GENOPER_REPAIR;
+		errorcode = GENOPER_REPAIR;
 		errorpos = nerrpos;
 		return -1;
@@ -903,5 +895,5 @@
 	else
 	{
-		error = GENOPER_REPAIR;
+		errorcode = GENOPER_REPAIR;
 		errorpos = nerrpos;
 		repair_parent = parent;
@@ -911,24 +903,28 @@
 }
 
-void f4_Cells::repairGeno(f4_node *geno, int whichchild)
+void f4_Cells::repairGeno(f4_Node *geno, int whichchild)
 {
 	// assemble repaired geno, if the case
 	if (!repair) return;
-	if ((NULL == repair_remove) && (NULL == repair_insert)) return;
+	if ((repair_remove == NULL) && (repair_insert == NULL)) return;
 	// traverse genotype tree, remove / insert node
-	f4_node *g2;
-	if (1 == whichchild) g2 = geno->child;
-	else             g2 = geno->child2;
-	if (NULL == g2)
+	f4_Node *g2;
+	if (whichchild == 1)
+		g2 = geno->child;
+	else
+		g2 = geno->child2;
+	if (g2 == NULL)
 		return;
 	if (g2 == repair_remove)
 	{
-		f4_node *oldgeno;
+		f4_Node *oldgeno;
 		geno->removeChild(g2);
 		if (g2->child)
 		{
 			// add g2->child as child to geno
-			if (1 == whichchild) geno->child = g2->child;
-			else             geno->child2 = g2->child;
+			if (whichchild == 1)
+				geno->child = g2->child;
+			else
+				geno->child2 = g2->child;
 			g2->child->parent = geno;
 		}
@@ -936,5 +932,5 @@
 		oldgeno->child = NULL;
 		delete oldgeno;
-		if (NULL == geno->child) return;
+		if (geno->child == NULL) return;
 		// check this new
 		repairGeno(geno, whichchild);
@@ -973,10 +969,10 @@
 	char buf[200];
 
-	if (curc >= nc) return;
-
-	if (T_STICK4 != C[curc]->type) return;
+	if (curc >= cell_count) return;
+
+	if (C[curc]->type != CELL_STICK) return;
 
 	thisti = C[curc];
-	if (NULL != thisti->dadlink)
+	if (thisti->dadlink != NULL)
 		*tmpcel = *(thisti->dadlink);
 
@@ -1018,7 +1014,7 @@
 
 	// neurons attached to it
-	for (i = 0; i < nc; i++)
-	{
-		if (C[i]->type == T_NEURON4)
+	for (i = 0; i < cell_count; i++)
+	{
+		if (C[i]->type == CELL_NEURON)
 		{
 			if (C[i]->dadlink == thisti)
@@ -1027,23 +1023,16 @@
 				out += "[";
 				// ctrl
-				if (1 == thneu->ctrl) out += "@";
-				if (2 == thneu->ctrl) out += "|";
-				// links
-				for (j = 0; j < thneu->nolink; j++)
+				//if (1 == thneu->ctrl) out += "@"; // old code; this can be easily generalized to any neuroclass if ever needed
+				//if (2 == thneu->ctrl) out += "|";
+				out += thneu->neuclass->name.c_str(); // not tested, but something like that
+				// connections
+				for (j = 0; j < thneu->conns_count; j++)
 				{
 					if (j) out += ",";
-					if (NULL == thneu->links[j]->from)
-					{
-						// sensors
-						out += thneu->links[j]->t.c_str();
-					}
-					else
-					{
-						sprintf(buf, "%d", thneu->links[j]->from->name - thneu->name);
-						out += buf;
-					}
+					sprintf(buf, "%d", thneu->conns[j]->from->nr - thneu->nr);
+					out += buf;
 					out += ":";
 					// connection weight
-					sprintf(buf, "%g", thneu->links[j]->w);
+					sprintf(buf, "%g", thneu->conns[j]->weight);
 					out += buf;
 				}
@@ -1058,7 +1047,7 @@
 
 	ccount = 1;
-	for (i = 0; i < nc; i++)
-	{
-		if (C[i]->type == T_STICK4)
+	for (i = 0; i < cell_count; i++)
+	{
+		if (C[i]->type == CELL_STICK)
 		{
 			if (C[i]->dadlink == thisti)
@@ -1088,5 +1077,5 @@
 // to organize an f4 genotype in a tree structure
 
-f4_node::f4_node()
+f4_Node::f4_Node()
 {
 	name = "?";
@@ -1095,10 +1084,14 @@
 	child2 = NULL;
 	pos = -1;
-	l1 = 0;
-	i1 = 0;
-	f1 = 0.0f;
-}
-
-f4_node::f4_node(string nname, f4_node *nparent, int npos)
+
+	reps = 0;
+	prop_symbol = '\0';
+	prop_increase = false;
+	conn_from = 0;
+	conn_weight = 0.0;
+	neuclass = NULL;
+}
+
+f4_Node::f4_Node(string nname, f4_Node *nparent, int npos)
 {
 	name = nname;
@@ -1108,10 +1101,14 @@
 	pos = npos;
 	if (parent) parent->addChild(this);
-	l1 = 0;
-	i1 = 0;
-	f1 = 0.0f;
-}
-
-f4_node::f4_node(char nname, f4_node *nparent, int npos)
+
+	reps = 0;
+	prop_symbol = '\0';
+	prop_increase = false;
+	conn_from = 0;
+	conn_weight = 0.0;
+	neuclass = NULL;
+}
+
+f4_Node::f4_Node(char nname, f4_Node *nparent, int npos)
 {
 	name = nname;
@@ -1121,25 +1118,37 @@
 	pos = npos;
 	if (parent) parent->addChild(this);
-	l1 = 0;
-	i1 = 0;
-	f1 = 0.0f;
-}
-
-f4_node::~f4_node()
-{
-	// (destroy() copied here for efficiency)
-	// children are destroyed (recursively) through the destructor
-	if (NULL != child2)  delete child2;
-	if (NULL != child)   delete child;
-}
-
-int f4_node::addChild(f4_node *nchi)
-{
-	if (NULL == child)
+
+	reps = 0;
+	prop_symbol = '\0';
+	prop_increase = false;
+	conn_from = 0;
+	conn_weight = 0.0;
+	neuclass = NULL;
+}
+
+f4_Node::~f4_Node()
+{
+	destroy();
+}
+
+void f4_Node::print_tree(const f4_Node *root, int indent)
+{
+	for (int i = 0; i < indent; i++) printf(" ");
+	printf("%s (%d)", root->name.c_str(), root->count());
+	if (root->name == "[")
+		printf("     from=%-3d  weight=%g", root->conn_from, root->conn_weight);
+	printf("\n");
+	if (root->child) print_tree(root->child, indent + 1);
+	if (root->child2) print_tree(root->child2, indent + 1);
+}
+
+int f4_Node::addChild(f4_Node *nchi)
+{
+	if (child == NULL)
 	{
 		child = nchi;
 		return 0;
 	}
-	if (NULL == child2)
+	if (child2 == NULL)
 	{
 		child2 = nchi;
@@ -1149,5 +1158,5 @@
 }
 
-int f4_node::removeChild(f4_node *nchi)
+int f4_Node::removeChild(f4_Node *nchi)
 {
 	if (nchi == child2)
@@ -1164,32 +1173,32 @@
 }
 
-int f4_node::childCount()
-{
-	if (NULL != child)
-	{
-		if (NULL != child2) return 2;
+int f4_Node::childCount()
+{
+	if (child != NULL)
+	{
+		if (child2 != NULL) return 2;
 		else return 1;
 	}
 	else
 	{
-		if (NULL != child2) return 1;
+		if (child2 != NULL) return 1;
 		else return 0;
 	}
 }
 
-int f4_node::count()
+int f4_Node::count() const
 {
 	int c = 1;
-	if (NULL != child)  c += child->count();
-	if (NULL != child2) c += child2->count();
+	if (child != NULL)  c += child->count();
+	if (child2 != NULL) c += child2->count();
 	return c;
 }
 
-f4_node* f4_node::ordNode(int n)
+f4_Node* f4_Node::ordNode(int n)
 {
 	int n1;
-	if (0 == n) return this;
+	if (n == 0) return this;
 	n--;
-	if (NULL != child)
+	if (child != NULL)
 	{
 		n1 = child->count();
@@ -1197,5 +1206,5 @@
 		n -= n1;
 	}
-	if (NULL != child2)
+	if (child2 != NULL)
 	{
 		n1 = child2->count();
@@ -1206,5 +1215,5 @@
 }
 
-f4_node* f4_node::randomNode()
+f4_Node* f4_Node::randomNode()
 {
 	int n = count();
@@ -1213,10 +1222,10 @@
 }
 
-f4_node* f4_node::randomNodeWithSize(int mn, int mx)
+f4_Node* f4_Node::randomNodeWithSize(int mn, int mx)
 {
 	// try random nodes, and accept if size in range
 	// limit to maxlim tries
 	int i, n, maxlim;
-	f4_node *nod = NULL;
+	f4_Node *nod = NULL;
 	maxlim = count();
 	for (i = 0; i < maxlim; i++)
@@ -1230,5 +1239,5 @@
 }
 
-void f4_node::sprint(SString& out)
+void f4_Node::sprint(SString& out)
 {
 	char buf2[20];
@@ -1237,68 +1246,48 @@
 	{
 		out += "#";
-		if (i1 != 1)
-		{
-			sprintf(buf2, "%d", i1);
+		sprintf(buf2, "%d", reps);
+		out += buf2;
+	}
+	else {
+		// special case: neuron connection
+		if (name == "[")
+		{
+			out += "[";
+			sprintf(buf2, "%d", conn_from);
 			out += buf2;
-		}
-	}
-	else {
-		// special case: neuron link
-		if (name == "[")
-		{
-			out += "[";
-			if (i1 > 0)
-			{
-				// sensor input
-				out += s1.c_str();
-			}
-			else
-			{
-				sprintf(buf2, "%d", l1);
-				out += buf2;
-			}
-			sprintf(buf2, ":%g]", f1);
+			sprintf(buf2, ":%g]", conn_weight);
 			out += buf2;
 		}
 		else if (name == ":")
 		{
-			sprintf(buf2, ":%c%c:", l1 ? '+' : '-', (char)i1);
+			sprintf(buf2, ":%c%c:", prop_increase ? '+' : '-', prop_symbol);
 			out += buf2;
 		}
-		else if (name == "@" || name == "|")
-		{
-			if (parent->name == "N")
-			{
-				out += name.c_str();
-			}
-			else
-			{
-				out += "N:";
-				out += name.c_str();
-			}
+		else if (neuclass != NULL)
+		{
+			out += "N:";
+			out += neuclass->name.c_str();
 		}
 		else
 		{
-			char *temp = (char*)name.c_str();
-			NeuroClass *nc = GenoOperators::parseNeuroClass(temp);
-			if (nc != NULL)
-			{
-				out += "N:";
-			}
 			out += name.c_str();
 		}
 	}
-	if (NULL != child)     child->sprint(out);
+
+	if (child != NULL)
+		child->sprint(out);
 	// if two children, make sure last char is a '>'
-	if (2 == childCount())
-		if (0 == out[0]) out += ">"; else
-			if ('>' != out[out.length() - 1]) out += ">";
-	if (NULL != child2)    child2->sprint(out);
+	if (childCount() == 2)
+		if (out[0] == 0) out += ">"; else
+			if (out[out.length() - 1] != '>') out += ">";
+
+	if (child2 != NULL)
+		child2->sprint(out);
 	// make sure last char is a '>'
-	if (0 == out[0]) out += ">"; else
-		if ('>' != out[out.length() - 1]) out += ">";
-}
-
-void f4_node::sprintAdj(char *& buf)
+	if (out[0] == 0) out += ">"; else
+		if (out[out.length() - 1] != '>') out += ">";
+}
+
+void f4_Node::sprintAdj(char *& buf)
 {
 	unsigned int len;
@@ -1312,5 +1301,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(); };
 	// copy back to string
 	// if new is longer, reallocate buf
@@ -1322,17 +1311,17 @@
 }
 
-f4_node* f4_node::duplicate()
-{
-	f4_node *copy;
-	copy = new f4_node(*this);
+f4_Node* f4_Node::duplicate()
+{
+	f4_Node *copy;
+	copy = new f4_Node(*this);
 	copy->parent = NULL;  // set later
 	copy->child = NULL;
 	copy->child2 = NULL;
-	if (NULL != child)
+	if (child != NULL)
 	{
 		copy->child = child->duplicate();
 		copy->child->parent = copy;
 	}
-	if (NULL != child2)
+	if (child2 != NULL)
 	{
 		copy->child2 = child2->duplicate();
@@ -1343,23 +1332,19 @@
 
 
-void f4_node::destroy()
+void f4_Node::destroy()
 {
 	// children are destroyed (recursively) through the destructor
-	if (NULL != child2)  delete child2;
-	if (NULL != child)   delete child;
+	if (child2 != NULL) delete child2;
+	if (child != NULL) delete child;
 }
 
 // scan genotype string and build tree
 // return >1 for error (errorpos)
-int f4_processrec(const char* genot, unsigned pos0, f4_node *parent)
-{
-	int i, j, res, t;
-	char tc1, tc2, tc3[2]; // tc3 is only to ensure that neuron parameter definition is completed
-	int relfrom;
-	double w;
+int f4_processrec(const char* genot, unsigned pos0, f4_Node *parent)
+{
+	int i, res;
 	unsigned gpos, oldpos;
-	f4_node *node1, *par;
+	f4_Node *node1, *par;
 	unsigned beginindex;
-	string neutype = "";
 
 	gpos = pos0;
@@ -1368,5 +1353,4 @@
 	while (gpos < strlen(genot))
 	{
-		neutype = "";
 		// first switch across cell dividers and old semantics
 		switch (genot[gpos])
@@ -1375,7 +1359,7 @@
 		{
 			// find out genotype start for child
-			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
-
-			node1 = new f4_node("<", par, gpos);
+			int j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
+
+			node1 = new f4_Node("<", par, gpos);
 			par = node1;
 			res = f4_processrec(genot, gpos + 1, par);
@@ -1388,5 +1372,5 @@
 			else // ran out
 			{
-				node1 = new f4_node(">", par, int(strlen(genot)) - 1);
+				node1 = new f4_Node(">", par, int(strlen(genot)) - 1);
 				par = node1;
 			}
@@ -1396,7 +1380,7 @@
 		case '>':
 		{
-			node1 = new f4_node(">", par, gpos);
+			node1 = new f4_Node(">", par, gpos);
 			par = node1;
-			gpos = strlen(genot);
+			gpos = (unsigned int)strlen(genot);
 			return 0;  // OK
 		}
@@ -1409,12 +1393,12 @@
 			else i = val.getInt();
 			// find out genotype start for continuation
-			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
+			int j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
 			// skip number
 			oldpos = gpos;
 			gpos += end - (genot + gpos);
 			//gpos++;
-			//while ((genot[gpos] >= '0') && (genot[gpos] <= '9')) gpos++;node1 = new f4_node("#", par, oldpos);
-			node1 = new f4_node("#", par, oldpos);
-			node1->i1 = i;
+			//while ((genot[gpos] >= '0') && (genot[gpos] <= '9')) gpos++;node1 = new f4_Node("#", par, oldpos);
+			node1 = new f4_Node("#", par, oldpos);
+			node1->reps = i;
 			par = node1;
 			res = f4_processrec(genot, gpos, node1);
@@ -1427,5 +1411,5 @@
 			else // ran out
 			{
-				node1 = new f4_node(">", par, int(strlen(genot)) - 1);
+				node1 = new f4_Node(">", par, int(strlen(genot)) - 1);
 			}
 			return 0;  // OK
@@ -1440,122 +1424,71 @@
 			break;
 		}
-		case 'l':  case 'L':
-		case 'c':  case 'C':
-		case 'q':  case 'Q':
-		case 'r':  case 'R':
-		case 'X':  case ',':
-		case 'a':  case 'A':
-		case 's':  case 'S':
-		case 'm':  case 'M':
-		case 'i':  case 'I':
-		case 'f':  case 'F':
-		case 'w':  case 'W':
-		case 'e':  case 'E':
-		{
-			node1 = new f4_node(genot[gpos], par, gpos);
+		case 'N':
+		{
+			int forgenorange = gpos;
+			if (genot[gpos + 1] != ':')
+				return gpos + 1; //error
+			gpos += 2; //skipping "N:"
+			beginindex = gpos;
+			char* end = (char*)genot + beginindex;
+			NeuroClass *neuclass = GenoOperators::parseNeuroClass(end, ModelEnum::SHAPETYPE_BALL_AND_STICK);
+			if (neuclass == NULL)
+				return gpos + 1; //error
+			gpos += end - genot - beginindex;
+			string neutype = string(genot + beginindex, genot + gpos);
+			node1 = new f4_Node(neutype, par, forgenorange);
+			node1->neuclass = neuclass;
+			par = node1;
+			// if it continues with a colon that determines a neuron parameter (e.g. N:N:+=: ), then let the switch case for colon handle this
+			break;
+		}
+		case ':':
+		{
+			// neuron parameter  +! -! += -= +/ or -/
+			// in the future this could be generalized to all neuron properties, for example N:|:power:0.6:range:1.4, or can even use '=' or ',' instead of ':' if no ambiguity
+			char prop_dir, prop_symbol, prop_end[2]; // prop_end is only to ensure that neuron parameter definition is completed
+			if (sscanf(genot + gpos, ":%c%c%1[:]", &prop_dir, &prop_symbol, &prop_end) != 3)
+				// error: incorrect format
+				return gpos + 1 + 1;
+			if (prop_dir != '-' && prop_dir != '+')
+				return gpos + 1 + 1; //error
+			switch (prop_symbol)
+			{
+			case '!':  case '=':  case '/':  break;
+			default:
+				return gpos + 1 + 1; //error
+			}
+			node1 = new f4_Node(":", par, gpos);
+			node1->prop_symbol = prop_symbol;
+			node1->prop_increase = prop_dir == '+' ? true : false; // + or -
+			par = node1;
+			int chars = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), ':');
+			gpos += chars + 2;
+			break;
+		}
+		case '[':
+		{
+			double weight = 0;
+			int relfrom;
+			const char *end = parseConnection(genot + gpos, relfrom, weight);
+			if (end == NULL)
+				return gpos + 1; //error
+
+			node1 = new f4_Node("[", par, gpos);
+			node1->conn_from = relfrom;
+			node1->conn_weight = weight;
+			par = node1;
+			int j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), ']');
+			gpos += j + 2;
+			break;
+		}
+		default: // 'X' and ',' and all modifiers and also invalid symbols - add a node, for invalid symbols build will give the error or repair
+		{
+			//printf("any regular character '%c'\n", genot[gpos]);
+			node1 = new f4_Node(genot[gpos], par, gpos);
 			par = node1;
 			gpos++;
 			break;
 		}
-		case '@':  case '|':
-		{
-			// in order to prevent the presence of "free muscles", we need to ensure that a muscle is written as N@/N| or N:@/N:|
-			if (par != NULL && par->name == "N")
-			{
-				node1 = new f4_node(genot[gpos], par, gpos);
-				par = node1;
-				gpos++;
-			}
-			else
-			{
-				return gpos + 1;
-			}
-			break;
-		}
-
-		case 'N':
-		{
-			// if there is no colon after N, then there is no class definition
-			if (gpos + 1 >= strlen(genot) || genot[gpos + 1] != ':')
-			{
-				node1 = new f4_node(genot[gpos], par, gpos);
-				par = node1;
-				gpos++;
-				break;
-			}
-			// if there is a colon determining neuron parameter, then let the switch case colon handle this
-			else if (sscanf(genot + gpos + 1, ":%c%c%1[:]", &tc1, &tc2, &tc3) == 3)
-			{
-				node1 = new f4_node(genot[gpos], par, gpos);
-				par = node1;
-				gpos++;
-				break;
-			}
-			int forgenorange = gpos;
-			gpos += 2; //skipping "N:"
-			beginindex = gpos;
-			char* end = (char*)genot + beginindex;
-			GenoOperators::parseNeuroClass(end);
-			gpos += end - genot - beginindex;
-			neutype = string(genot + beginindex, genot + gpos);
-			node1 = new f4_node(neutype, par, forgenorange);
-			par = node1;
-			break;
-		}
-		case ':':
-		{
-			// neuron parameter  +! -! += -= +/ or -/
-			if (sscanf(genot + gpos, ":%c%c%1[:]", &tc1, &tc2, &tc3) != 3)
-				// error: incorrect format
-				return gpos + 1 + 1;
-			if ('+' == tc1) j = 1;
-			else if ('-' == tc1) j = 0;
-			else return gpos + 1 + 1;
-			switch (tc2)
-			{
-			case '!':  case '=':  case '/':  break;
-			default:
-				return gpos + 1 + 1;
-			}
-			node1 = new f4_node(":", par, gpos);
-			node1->l1 = j;
-			node1->i1 = (int)tc2;
-			par = node1;
-			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), ':');
-			gpos += j + 2;
-			break;
-		}
-		case '[':
-		{
-			const char *end = parseConnection(genot + gpos, relfrom, w);
-			if (end == NULL)
-			{
-				end = parseConnectionWithNeuron(genot + gpos, neutype, w);
-				if (end == NULL) t = -1;
-				else t = 1;
-			}
-			else
-			{
-				t = 0;
-			}
-			node1 = new f4_node("[", par, gpos);
-			node1->s1 = neutype;
-			node1->i1 = t;
-			node1->l1 = relfrom;
-			node1->f1 = w;
-			par = node1;
-			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), ']');
-			gpos += j + 2;
-			break;
-		}
-		default:
-		{
-			//DB( printf("unknown character '%c' ! \n", genot[gpos]); )
-			//add it, build will give the error or repair
-			node1 = new f4_node(genot[gpos], par, gpos);
-			par = node1;
-			gpos++;
-			break;
-		}
 		}
 	}
@@ -1566,5 +1499,5 @@
 		if (par->name != ">")
 		{
-			node1 = new f4_node('>', par, int(strlen(genot)) - 1);
+			node1 = new f4_Node('>', par, int(strlen(genot)) - 1);
 			par = node1;
 		}
@@ -1593,30 +1526,8 @@
 }
 
-const char* parseConnectionWithNeuron(const char *fragm, string &neutype, double &weight)
-{
-	const char *parser = fragm;
-	if (*parser != '[') return NULL;
-	parser++;
-	char* p = (char*)parser;
-	if (GenoOperators::parseNeuroClass(p) == NULL) return NULL;
-	neutype = string(parser, (const char *)p);
-	parser = p;
-	if (*parser != ':') return NULL;
-	parser++;
-	ExtValue val;
-	parser = val.parseNumber(parser, ExtPType::TDouble);
-	if (parser == NULL) return NULL;
-	weight = val.getDouble();
-	if (*parser != ']') return NULL;
-	parser++;
-	return parser;
-}
-
-f4_node* f4_processtree(const char* geno)
-{
-	f4_node *root;
-	int res;
-	root = new f4_node();
-	res = f4_processrec(geno, 0, root);
+f4_Node* f4_processtree(const char* geno)
+{
+	f4_Node *root = new f4_Node();
+	int res = f4_processrec(geno, 0, root);
 	if (res) return NULL;
 	//DB( printf("test f4  "); )
Index: cpp/frams/genetics/f4/f4_general.h
===================================================================
--- cpp/frams/genetics/f4/f4_general.h	(revision 1226)
+++ cpp/frams/genetics/f4/f4_general.h	(revision 1227)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2015  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -29,5 +29,5 @@
 void rolling_inc(double *v);
 
-class f4_node;   // later
+class f4_Node;   // later
 class f4_Cell;   // later
 class f4_Cells;  // later
@@ -36,7 +36,7 @@
 /** @name Types of f4_Cell's */
 //@{
-#define T_UNDIFF4 40 ///<undifferentiated cell
-#define T_STICK4  41 ///<differentiated to stick, cannot divide
-#define T_NEURON4 42 ///<differentiated to neuron, can divide
+#define CELL_UNDIFF 40 ///<undifferentiated cell
+#define CELL_STICK  41 ///<differentiated to stick, cannot divide
+#define CELL_NEURON 42 ///<differentiated to neuron, can divide
 //@}
 
@@ -54,14 +54,14 @@
 
 
-class f4_CellLink;
+class f4_CellConn;
 
 /** @name Constraints of f4 genotype structures */
 //@{
-#define MAXINPUTS 100 ///<maximum number of neuron inputs
-#define MAX4CELLS 100 ///<maximum number of f4 organism cells
+#define F4_MAX_CELL_INPUTS  10 ///<maximum number of neuron inputs in a developing organism
+#define F4_MAX_CELLS 100 ///<maximum number of f4 organism cells
 //@}
 
 /**
- * Abstract cell type - the representation of single component in the  developmental
+ * Abstract cell type - the representation of a single component in the developmental
  * encoding. In the beginning, each f4_Cell is undifferentiated. During the process
  * of development it can divide or differentiate into a stick or a neuron. If it
@@ -86,8 +86,8 @@
 		/**
 		 * A constructor that takes the pointer to the repetition node and the count of repetitions.
-		 * @param a pointer to f4_node for repetition character
+		 * @param a pointer to f4_Node for repetition character
 		 * @param b the number of repetitions
 		 */
-		repeat_ptr(f4_node *a, int b) : node(a), count(b) { };
+		repeat_ptr(f4_Node *a, int b) : node(a), count(b) { };
 
 		inline void makeNull() { node = NULL; count = -1; };
@@ -96,5 +96,5 @@
 
 		inline void dec() { count--; };
-		f4_node    *node; ///<pointer to the repetition code
+		f4_Node    *node; ///<pointer to the repetition code
 		int       count; ///<repetition counter
 	};
@@ -130,26 +130,26 @@
 		static const int stackSize = 4;  ///<max 4 nested levels
 		repeat_ptr ptr[stackSize]; ///<array holding pointers to repeat_ptr
-		short int top;  ///<index of the top of the stack
+		int top;  ///<index of the top of the stack
 	};
 
 	/**
 	 * Creates a new f4_Cell object.
-	 * @param nname name of a cell, can be T_UNDIFF4, T_STICK4 or T_NEURON4
+	 * @param nnr number of the cell
 	 * @param ndad pointer to the parent of the created cell
 	 * @param nangle the amount of commas affecting branch angles
 	 * @param newP genotype properties of a given cell
 	 */
-	f4_Cell(int nname, f4_Cell *ndad, int nangle, GeneProps newP);
+	f4_Cell(int nnr, f4_Cell *ndad, int nangle, GeneProps newP);
 	/**
 	 * Creates a new f4_Cell object.
 	 * @param nO pointer to an organism containing the cell
-	 * @param nname name of the cell, can be T_UNDIFF4, T_STICK4 or T_NEURON4
+	 * @param nnr number of the cell
 	 * @param ngeno pointer to the root of the genotype tree
-	 * @param ngcur pointer to the f4_node representing the current cell in the genotype tree
+	 * @param ngcur pointer to the f4_Node representing the current cell in the genotype tree
 	 * @param ndad pointer to the parent of the created cell
 	 * @param nangle the number of commas affecting branch angles
 	 * @param newP genotype properties of a given cell
 	 */
-	f4_Cell(f4_Cells *nO, int nname, f4_node *ngeno, f4_node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP);
+	f4_Cell(f4_Cells *nO, int nnr, f4_Node *ngeno, f4_Node *ngcur, f4_Cell *ndad, int nangle, GeneProps newP);
 
 	~f4_Cell();
@@ -172,5 +172,4 @@
 	 *  - the stick modifiers, like rotation, will be applied on neuron cell,
 	 *  - the differentiated cell will be differentiated again,
-	 *  - the neuron class inside cell connection (i.e. N[G:5]) is not a sensor,
 	 *  - the connection between neurons cannot be established,
 	 *  - the neuron class is not valid.
@@ -178,15 +177,13 @@
 	 * @return 0 if development was successful, 1 if there was an error in genotype tree
 	 */
-	int onestep();
-
-	/**
-	 * Adds a link between this neuron cell and a given neuron cell in nfrom. If the nfrom object
-	 * is not given, neuron type in nt should be a sensor type.
-	 * @param nfrom input neuron cell, or NULL if not given
-	 * @param nw weight of connection
-	 * @param nt empty string or name of sensor class
-	 * @return 0 if link is established, -1 otherwise
-	 */
-	int   addlink(f4_Cell *nfrom, double nw, string nt);
+	int oneStep();
+
+	/**
+	 * Adds a connection between this neuron cell and a given neuron cell in nfrom.
+	 * @param nfrom input neuron cell
+	 * @param nweight weight of connection
+	 * @return 0 if connection is established, -1 otherwise
+	 */
+	int   addConnection(f4_Cell *nfrom, double nweight);
 
 	/**
@@ -195,12 +192,12 @@
 	void  adjustRec();
 
-	int        name;               ///<name of cell (number)
+	int        nr;                 ///<number of cell (seems to be used only in old f1 converter for neuron connections)
 	int        type;               ///<type
 	f4_Cell *dadlink;              ///<pointer to cell parent
 	f4_Cells  *org;	               ///<uplink to organism
 
-	f4_node *genot; 	           ///<genotype tree
-	f4_node *gcur;                 ///<current genotype execution pointer
-	int active;                    ///<determines whether development is still active
+	f4_Node *genot; 	           ///<genotype tree
+	f4_Node *gcur;                 ///<current genotype execution pointer
+	bool active;                   ///<determines whether development is still active; even if false, the cell may "yield" - may be halted (but still having its onStep() called) due to neural connections waiting for other cells to potentially develop neurons
 	repeat_stack repeat;           ///<stack holding repetition nodes and counters
 	int recProcessedFlag;          ///<used during recursive traverse
@@ -220,33 +217,28 @@
 	int          neuro_refno;      ///<the number of the neuro object, used in f0
 
-	int          ctrl;             ///<neuron type
 	double       inertia;          ///<inertia of neuron
 	double       force;            ///<force of neuron
 	double       sigmo;            ///<sigmoid of neuron
-	f4_CellLink *links[MAXINPUTS]; ///<array of neuron links
-	int          nolink;           ///<number of links
+	f4_CellConn *conns[F4_MAX_CELL_INPUTS]; ///<array of neuron connections
+	int          conns_count;      ///<number of connections
 	NeuroClass *neuclass;          ///<pointer to neuron class
 };
 
 /**
- * Class representing link between neuron cells.
- */
-class f4_CellLink
+ * Class representing a connection between neuron cells.
+ */
+class f4_CellConn
 {
 public:
 	/**
 	 * Constructor for f4_CellLink class. Parameter nfrom represents input
-	 * neuron cell or NULL if connection has defined sensor type inside, like "[G:5]".
-	 * The name of sensor class defined inside neuron connection is stored in the nt
-	 * parameter.
-	 * @param nfrom pointer to input neuron cell or NULL
-	 * @param nw weight of connection
-	 * @param nt name of neuron class or empty string
-	 */
-	f4_CellLink(f4_Cell *nfrom, double nw, string nt);
-
-	f4_Cell *from; ///<pointer to input neuron cell
-	string t;      ///<empty if 'from' cell is given, NeuroClass name otherwise
-	double w;      ///<weight of connection
+	 * neuron cell.
+	 * @param nfrom pointer to input neuron cell
+	 * @param nweight weight of connection
+	 */
+	f4_CellConn(f4_Cell *nfrom, double nweight);
+
+	f4_Cell *from;  ///<pointer to input neuron cell
+	double weight;  ///<weight of connection
 };
 
@@ -264,5 +256,5 @@
 	 * @param nrepair 0 if nothing to repair
 	 */
-	f4_Cells(f4_node *genome, int nrepair);
+	f4_Cells(f4_Node *genome, int nrepair);
 
 	/**
@@ -291,9 +283,8 @@
 
 	/**
-	 * Performs a single step of organism development. It runs each active cell
-	 * in the organism.
-	 * @return 0 if all cells are developed, or 1 otherwise
-	 */
-	int  onestep();
+	 * Performs a single step of organism development. It runs each active cell in the organism.
+	 * @return false if all cells are developed or there is an error, true otherwise
+	 */
+	bool oneStep();
 
 	/**
@@ -302,5 +293,11 @@
 	 * @return 0 if organism developed successfully, error code if something went wrong
 	 */
-	int  simulate();
+	int simulate();
+
+	/**
+	 * Prints the current state of the organism (for debugging purposes).
+	 * @param description printout header
+	 */
+	void print_cells(const char* description);
 
 	/**
@@ -308,5 +305,5 @@
 	 * @return error code
 	 */
-	int  geterror() { return error; };
+	int getErrorCode() { return errorcode; };
 
 	/**
@@ -314,5 +311,5 @@
 	 * @return position of an error
 	 */
-	int  geterrorpos() { return errorpos; };
+	int getErrorPos() { return errorpos; };
 
 	/**
@@ -325,7 +322,7 @@
 	 * Sets the element of genotype to be repaired by removal.
 	 * @param nerrpos position of an error in genotype
-	 * @param rem the f4_node to be removed from the  genotype tree in order to repair
-	 */
-	void setRepairRemove(int nerrpos, f4_node *rem);
+	 * @param rem the f4_Node to be removed from the  genotype tree in order to repair
+	 */
+	void setRepairRemove(int nerrpos, f4_Node *rem);
 
 	/**
@@ -336,5 +333,5 @@
 	 * @return 0 if repair can be performed, or -1 otherwise because the repair flag wasn't set in the constructor
 	 */
-	int  setRepairInsert(int nerrpos, f4_node *parent, f4_node *insert);
+	int setRepairInsert(int nerrpos, f4_Node *parent, f4_Node *insert);
 
 	/**
@@ -343,21 +340,21 @@
 	 * @param whichchild 1 if first child, 2 otherwise
 	 */
-	void repairGeno(f4_node *geno, int whichchild);
+	void repairGeno(f4_Node *geno, int whichchild);
 
 	// the cells
-	f4_Cell *C[MAX4CELLS];  ///<Array of all cells of an organism
-	int       nc;           ///<Number of cells in an organism
+	f4_Cell *C[F4_MAX_CELLS];  ///<Array of all cells of an organism
+	int     cell_count;        ///<Number of cells in an organism
 
 private:
 	// for error reporting / genotype fixing
 	int repair;
-	int error;
+	int errorcode;
 	int errorpos;
-	f4_node *repair_remove;
-	f4_node *repair_parent;
-	f4_node *repair_insert;
+	f4_Node *repair_remove;
+	f4_Node *repair_parent;
+	f4_Node *repair_insert;
 	void toF1GenoRec(int curc, SString &out);
 	f4_Cell *tmpcel;		// needed by toF1Geno
-	f4_node *f4rootnode;          // used by constructor
+	f4_Node *f4rootnode;          // used by constructor
 };
 
@@ -366,18 +363,21 @@
  * A class to organize a f4 genotype in a tree structure.
  */
-class f4_node
+class f4_Node
 {
 public:
-	string name; ///<one-letter 'name', multiple characters for classes
-	f4_node *parent; ///<parent link or NULL
-	f4_node *child; ///<child or NULL
-	f4_node *child2; ///<second child or NULL
+	string name; ///<one-letter gene code or multiple characters for neuron classes (then neuclass != NULL)
+	f4_Node *parent; ///<parent link or NULL
+	f4_Node *child; ///<child or NULL
+	f4_Node *child2; ///<second child or NULL
 	int pos; ///<original position in the string
-	int i1; ///<internal int parameter1
-	int l1; ///<internal long parameter1 (now also int, since long is not well specified and it is in our scenarios equivalent to int)
-	double f1; ///<internal double parameter1
-	string s1; ///<internal string parameter1
-
-	f4_node();
+
+	int reps; ///<repetition counter for the '#' gene
+	char prop_symbol; ///<old-style properties (force,intertia,sigmoid) of the N neuron: !=/
+	bool prop_increase; ///<false=decrease neuron property (force,intertia,sigmoid), true=increase it
+	int conn_from; ///<relative number of the neuron this neuron get an input from
+	double conn_weight; ///<neuron connection weight
+	NeuroClass *neuclass; ///< NULL or not if "name" is a neuroclass name with a proper genotype context ("N:neuroclassname"). New in 2023-04 - to fix fatal flaw with fundamental assumptions: it was impossible to distinguish between single-character neuron names such as S, D, G and single-character modifiers. They were all stored in the "name" field. Before 2018 this was never a problem because the only supported neuroclasses had distinctive symbols such as @|*GTS, and the set of supported modifiers was small and different from neuroclass letters (no G,D,S clash).
+
+	f4_Node();
 
 	/**
@@ -387,5 +387,5 @@
 	 * @param npos position of node substring in the genotype string
 	 */
-	f4_node(string nname, f4_node *nparent, int npos);
+	f4_Node(string nname, f4_Node *nparent, int npos);
 
 	/**
@@ -395,7 +395,14 @@
 	 * @param npos position of node character in the genotype string
 	 */
-	f4_node(char nname, f4_node *nparent, int npos);
-
-	~f4_node();
+	f4_Node(char nname, f4_Node *nparent, int npos);
+
+	~f4_Node();
+
+	/**
+	 * Recursively print subtree (for debugging).
+	 * @param root starting node
+	 * @param indent initial indentation
+	 */
+	static void print_tree(const f4_Node *root, int indent);
 
 	/**
@@ -404,5 +411,5 @@
 	 * @return 0 if the child could be added, -1 otherwise
 	 */
-	int addChild(f4_node *nchi);
+	int addChild(f4_Node *nchi);
 
 	/**
@@ -411,5 +418,5 @@
 	 * @return 0 if child could be removed, -1 otherwise
 	 */
-	int removeChild(f4_node *nchi);
+	int removeChild(f4_Node *nchi);
 
 	/**
@@ -423,5 +430,5 @@
 	 * @return the number of nodes from this node
 	 */
-	int count();
+	int count() const;
 
 	/**
@@ -430,5 +437,5 @@
 	 * @return pointer to the nth subnode or NULL if not found
 	 */
-	f4_node* ordNode(int n);
+	f4_Node* ordNode(int n);
 
 	/**
@@ -436,5 +443,5 @@
 	 * @return random subnode
 	 */
-	f4_node* randomNode();
+	f4_Node* randomNode();
 
 	/**
@@ -444,5 +451,5 @@
 	 * @return a random subnode with a given size or NULL
 	 */
-	f4_node* randomNodeWithSize(int min, int max);
+	f4_Node* randomNodeWithSize(int min, int max);
 
 	/**
@@ -456,5 +463,5 @@
 	 * @return pointer to a tree copy
 	 */
-	f4_node* duplicate();
+	f4_Node* duplicate();
 
 	/**
@@ -468,15 +475,15 @@
 /**
  * The main function for converting a string of f4 encoding to a tree structure. Prepares
- * f4_node root of tree and runs f4_processrec function for it.
+ * f4_Node root of tree and runs f4_processrec function for it.
  * @param geno the string representing an f4 genotype
- * @return a pointer to the f4_node object representing the f4 tree root
- */
-f4_node* f4_processtree(const char *geno);
+ * @return a pointer to the f4_Node object representing the f4 tree root
+ */
+f4_Node* f4_processtree(const char *geno);
 
 /**
  * Scans a genotype string starting from a given position. This recursive method creates
- * a tree of f4_node objects. This method extracts each potentially functional element
- * of a genotype string to a separate f4_nodes. When the branching character '<' occurs,
- * f4_processrec is deployed for the latest f4_node element. This method does not
+ * a tree of f4_Node objects. This method extracts each potentially functional element
+ * of a genotype string to a separate f4_Nodes. When the branching character '<' occurs,
+ * f4_processrec is deployed for the latest f4_Node element. This method does not
  * analyse the genotype semantically, it only checks if the syntax is proper. The only
  * semantic aspect is neuron class name extraction, where the GenoOperators
@@ -487,5 +494,5 @@
  * @return 0 if processing was successful, otherwise returns the position of an error in the genotype
  */
-int f4_processrec(const char *genot, unsigned pos0, f4_node *parent);
+int f4_processrec(const char *genot, unsigned pos0, f4_Node *parent);
 
 /**
@@ -502,17 +509,3 @@
 const char *parseConnection(const char *fragm, int &relfrom, double &weight);
 
-/**
- * Parses the notation of the neuron connection with neuron definition - takes
- * the beginning of the connection definition, extracts the name of neuron class
- * that will be the input for the current neuron and the weight of the connection.
- * After successful parsing, returns a pointer to the first character after the connection
- * definition, or NULL if the connection definition was not valid due to the lack of [, :, ]
- * characters, an invalid value of the weight or an invalid neuron class name.
- * @param fragm the beginning of the connection definition, should be the '[' character
- * @param neutype the reference to a string representing the input neuron class name. The name of the class is validated with GenoOperators::parseNeuroClass()
- * @param weight the reference to a double variable in which the weight of the connection will be stored
- * @return the pointer to the first character in string after the connection definition
- */
-const char *parseConnectionWithNeuron(const char *fragm, string &neutype, double &weight);
-
 #endif
Index: cpp/frams/genetics/f4/f4_oper.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_oper.cpp	(revision 1226)
+++ cpp/frams/genetics/f4/f4_oper.cpp	(revision 1227)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -6,4 +6,18 @@
 // Copyright (C) since 2001 Maciej Komosinski
 // 2018, Grzegorz Latosinski, added support for new API for neuron types and their properties
+
+
+// This representation has a tendency to bloat - adding a small penalty to fitness such as "this.velocity - 0.000000001*String.len(this.genotype);"
+// may help, but it would be better to improve the source code to make genetic operators neutral in terms of genotype length. Adding such a penalty
+// removes "work in progress" changes in genotypes thus promoting immediate, straightforward improvements while hindering slower, multifaceted progress.
+// TODO getting rid of redundancy (having valid genotypes with a lot of "junk code") in this representation looks like a good idea.
+// 
+// Note: symbols after the last > are ignored, for example /*4*/<X>N:N>N:N[2:-0.5]XXXwhatever but since they are not parsed into the f4_Node tree, they will be lost after any mutation.
+//
+// TODO the behavior of neuron input indexes during mutation seems badly implemented (see also TREAT_BAD_CONNECTIONS_AS_ERRORS). 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 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]
+
 
 #include "f4_oper.h"
@@ -20,6 +34,5 @@
 
 // codes that can be changed (apart from being added/deleted)
-#define MUT_CHAN_CODES "<[#"
-#define REP_MAXCOUNT 19
+#define F4_MUT_CHANGE_CODES "<[#"
 
 #define FIELDSTRUCT Geno_f4
@@ -27,13 +40,19 @@
 static ParamEntry geno_f4_paramtab[] =
 {
-	{ "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT + 1, },
-	{ "f4_mut_add", 0, 0, "Add node", "f 0 100 50", FIELD(prob[F4_ADD]), "mutation: probability of adding a node", },
-	{ "f4_mut_add_div", 0, 0, "- add division", "f 0 100 20", FIELD(probadd[F4_ADD_DIV]), "add node mutation: probability of adding a division", },
-	{ "f4_mut_add_conn", 0, 0, "- add connection", "f 0 100 15", FIELD(probadd[F4_ADD_CONN]), "add node mutation: probability of adding a neural connection", },
-	{ "f4_mut_add_neupar", 0, 0, "- add neuron property", "f 0 100 5", FIELD(probadd[F4_ADD_NEUPAR]), "add node mutation: probability of adding a neuron property/modifier", },
-	{ "f4_mut_add_rep", 0, 0, "- add repetition", "f 0 100 10", FIELD(probadd[F4_ADD_REP]), "add node mutation: probability of adding a repetition", },
-	{ "f4_mut_add_simp", 0, 0, "- add simple node", "f 0 100 50", FIELD(probadd[F4_ADD_SIMP]), "add node mutation: probability of adding a random, simple gene", },
-	{ "f4_mut_del", 0, 0, "Delete node", "f 0 100 20", FIELD(prob[F4_DEL]), "mutation: probability of deleting a node", },
-	{ "f4_mut_mod", 0, 0, "Modify node", "f 0 100 30", FIELD(prob[F4_MOD]), "mutation: probability of changing a node", },
+	{ "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT + F4_MODNEU_COUNT + 2, },
+	{ "f4_mut_add", 0, 0, "Add node", "f 0 100 50", FIELD(prob[F4_ADD]), "Mutation: probability of adding a node", },
+	{ "f4_mut_add_div", 0, 0, "- add division", "f 0 100 20", FIELD(probadd[F4_ADD_DIV]), "Add node mutation: probability of adding a division", },
+	{ "f4_mut_add_conn", 0, 0, "- add connection", "f 0 100 15", FIELD(probadd[F4_ADD_CONN]), "Add node mutation: probability of adding a neural connection", },
+	{ "f4_mut_add_neupar", 0, 0, "- add neuron property", "f 0 100 5", FIELD(probadd[F4_ADD_NEUPAR]), "Add node mutation: probability of adding a neuron property/modifier", },
+	{ "f4_mut_add_rep", 0, 0, "- add repetition '#'", "f 0 100 10", FIELD(probadd[F4_ADD_REP]), "Add node mutation: probability of adding the '#' repetition gene", },
+	{ "f4_mut_add_simp", 0, 0, "- add simple node", "f 0 100 50", FIELD(probadd[F4_ADD_SIMP]), "Add node mutation: probability of adding a random, simple gene", },
+
+	{ "f4_mut_del", 0, 0, "Delete node", "f 0 100 20", FIELD(prob[F4_DEL]), "Mutation: probability of deleting a node", },
+
+	{ "f4_mut_mod", 0, 0, "Modify node", "f 0 100 30", FIELD(prob[F4_MOD]), "Mutation: probability of changing a node", },
+	{ "f4_mut_modneu_conn", 0, 0, "- neuron input: modify source", "f 0 100 60", FIELD(probmodneu[F4_MODNEU_CONN]), "Neuron input mutation: probability of changing its source neuron", },
+	{ "f4_mut_modneu_weight", 0, 0, "- neuron input: modify weight", "f 0 100 40", FIELD(probmodneu[F4_MODNEU_WEIGHT]), "Neuron input mutation: probability of changing its weight", },
+
+	{ "f4_mut_max_rep", 1, 0, "Maximum number for '#' repetitions", "d 2 20 6", FIELD(mut_max_rep), "Maximum allowed number of repetitions for the '#' repetition gene", },
 	{ "f4_mut_exmod", 1, 0, "Excluded modifiers", "s 0 30", FIELD(excluded_modifiers), "Modifiers that will not be added nor deleted during mutation\n(all: " F14_MODIFIERS ")", },
 	{ 0, },
@@ -59,5 +78,5 @@
 	mutation_method_names[index++] = "deleted a node";
 	mutation_method_names[index++] = "modified a node";
-	if (index != F4_COUNT + F4_ADD_COUNT - 1) logMessage("Geno_f4", "Constructor", 3, "Mutation names init error");
+	if (index != F4_COUNT + F4_ADD_COUNT - 1) logMessage("Geno_f4", "Constructor", LOG_CRITICAL, "Mutation names init error");
 }
 
@@ -67,5 +86,5 @@
 }
 
-int Geno_f4::ValidateRec(f4_node *geno, int retrycount) const
+int Geno_f4::ValidateRec(f4_Node *geno, int retrycount) const
 {
 	// ! the genotype is geno->child (not geno) !
@@ -76,11 +95,11 @@
 
 	// errors not fixed:
-	if (GENOPER_OPFAIL == cells.geterror())
-	{
-		if (cells.geterrorpos() >= 0) return 1 + cells.geterrorpos();
+	if (cells.getErrorCode() == GENOPER_OPFAIL)
+	{
+		if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos();
 		return GENOPER_OPFAIL;
 	}
 	// errors can be fixed
-	if (GENOPER_REPAIR == cells.geterror())
+	if (cells.getErrorCode() == GENOPER_REPAIR)
 	{
 		cells.repairGeno(geno, 1);
@@ -102,5 +121,5 @@
 {
 	// convert geno to tree, then try to validate 20 times
-	f4_node root;
+	f4_Node root;
 	if (f4_processrec(geno, 0, &root) || root.childCount() != 1) return GENOPER_OK; // cannot repair
 	if (ValidateRec(&root, 20) == GENOPER_REPAIR) // if repaired, make it back to string
@@ -115,5 +134,5 @@
 int Geno_f4::checkValidity(const char* geno, const char *genoname)
 {
-	f4_node root;
+	f4_Node root;
 	int res = f4_processrec(geno, 0, &root);
 	if (res) return res;  // errorpos, >0
@@ -121,7 +140,7 @@
 	f4_Cells cells(root.child, 0);
 	cells.simulate();
-	if (cells.geterror() == GENOPER_OPFAIL || cells.geterror() == GENOPER_REPAIR)
-	{
-		if (cells.geterrorpos() >= 0) return 1 + cells.geterrorpos();
+	if (cells.getErrorCode() == GENOPER_OPFAIL || cells.getErrorCode() == GENOPER_REPAIR)
+	{
+		if (cells.getErrorPos() >= 0) return 1 + cells.getErrorPos();
 		else return 1; //earlier: GENOPER_OPFAIL;
 	}
@@ -130,5 +149,5 @@
 
 
-int Geno_f4::MutateOne(f4_node *& g, int &method) const
+int Geno_f4::MutateOne(f4_Node *& g, int &method) const
 {
 	// ! the genotype is g->child (not g) !
@@ -136,8 +155,6 @@
 	// do the mutation
 	// pick a random node
-	f4_node *n1 = g->child->randomNode();
-	vector<NeuroClass*> neulist;
-	//DB( printf("%c\n", n1->name); )
-	int neuronid = -1;
+	f4_Node *node_mutated = g->child->randomNode();
+	//DB( printf("%c\n", node_mutated->name); )
 
 	switch (roulette(prob, F4_COUNT))
@@ -151,138 +168,136 @@
 		{
 			// add division ('<')
-			f4_node *n3 = n1->parent;
-			n3->removeChild(n1);
-			f4_node *n2 = new f4_node('<', n3, n3->pos);
-			n2->addChild(n1);
+			f4_Node *node_mutated_parent = node_mutated->parent;
+			node_mutated_parent->removeChild(node_mutated);
+			f4_Node *node_new_div = new f4_Node('<', node_mutated_parent, node_mutated_parent->pos);
+			node_new_div->addChild(node_mutated);
 			// new cell is stick or neuron
 			// "X>" or "N>"
-			f4_node *n5 = NULL;
-			double pr = rndDouble(1);
-			pr -= 0.5;
-			if (pr < 0) n5 = new f4_node('X', n2, n2->pos);
+			constexpr double STICK_OR_NEURON = 0.5; // hardcoded probability... could be parametrized, but in a general case (unknown fitness goal) 0.5 makes sense?
+			f4_Node *node_new = NULL; //stick or neuron or neural connection
+			if (rndDouble(1) < STICK_OR_NEURON)
+				node_new = new f4_Node('X', node_new_div, node_new_div->pos);
 			else
 			{
 				// make neuron
 				NeuroClass *rndclass = GenoOperators::getRandomNeuroClass(Model::SHAPETYPE_BALL_AND_STICK);
-				if (rndclass == NULL)
+				if (rndclass == NULL) //no active neurons?
 				{
-					n5 = new f4_node('X', n2, n2->pos);
+					node_new = new f4_Node('X', node_new_div, node_new_div->pos);
 				}
 				else
 				{
-					f4_node *n4 = new f4_node(rndclass->getName().c_str(), n2, n2->pos);
-					if (rndclass->getPreferredInputs() != 0)
+					f4_Node *node_new_neuron = new f4_Node(rndclass->getName().c_str(), node_new_div, node_new_div->pos);
+					node_new_neuron->neuclass = rndclass;
+					node_new = node_new_neuron; //can be changed below if all goes well and we add a new connection too
+					if (probadd[F4_ADD_CONN] > 0) //user wants to add connections
 					{
-						neuronid = -1;
-						for (int i = 0; i < g->count(); i++)
+						if (rndclass->getPreferredInputs() != 0) //neuron also wants connections?
 						{
-							f4_node *gcur = g->ordNode(i);
-							char* temp = (char*)gcur->name.c_str();
-							NeuroClass *neuclass = GenoOperators::parseNeuroClass(temp);
-							if (neuclass != NULL)
+							int node_new_neuron_index, other_neuron_index;
+							bool ok = findConnectionNeuronIndexes(g, node_new_neuron, true, node_new_neuron_index, other_neuron_index); //node_new_neuron_index==-1 should never happen, we just added node_new_neuron we are looking for
+							if (ok) //we can create a new connection
 							{
-								neulist.push_back(neuclass);
-							}
-							if (g->ordNode(i) == n3)
-							{
-								neuronid = int(neulist.size()) - 1;
+								node_new = new f4_Node('[', node_new_neuron, node_new_div->pos);
+								connectionNodeChangeRandom(node_new, node_new_neuron_index, other_neuron_index);
 							}
 						}
-						if (neuronid == -1)
+						else if (rndclass->getPreferredOutput() > 0) //neuron also wants connections?
 						{
-							return GENOPER_OPFAIL;
+							// Not so easy: we would need to add a '[' node as a child not of node_new_neuron, but of other neuron that would get an input from node_new_neuron (and need to properly calculate relative connection reference).
+							// The "false" argument in findConnectionNeuronIndexes() below is not suffient, because we also need to access (find) the f4_Node of the other neuron.
+							// A similar logic is implemented in F4_ADD_CONN below, but let's not complicate this F4_ADD_DIV mutation anymore.
+							// A disadvantage is that the node_new_neuron added here which is a neuron that provides output (e.g., a receptor, N, etc.) will not get connected immediately here even when there are already existing neurons wanting inputs (e.g., muscles, N, etc.).
+							//bool ok = findConnectionNeuronIndexes(g, ... , false, ..., ...);
 						}
-						n5 = new f4_node('[', n4, n2->pos);
-						linkNodeMakeRandom(n5, neuronid, neulist);
-					}
-					else {
-						n5 = n4;
 					}
 				}
 			}
-			new f4_node('>', n5, n5->pos);
-			n1->parent = n2;
-			// now with 50% chance swap children
+			new f4_Node('>', node_new, node_new->pos); //adds to node_new
+			node_mutated->parent = node_new_div;
+			// now, swap children with 50% chance
 			if (rndUint(2) == 0)
 			{
-				n3 = n2->child;
-				n2->child = n2->child2;
-				n2->child2 = n3;
-			}
-		}
-			break;
+				node_mutated_parent = node_new_div->child;
+				node_new_div->child = node_new_div->child2;
+				node_new_div->child2 = node_mutated_parent;
+			}
+		}
+		break;
 		case F4_ADD_CONN:
 		{
-			// add link
-			f4_node *par = n1->parent;
-			char* temp = (char*)par->name.c_str();
-			NeuroClass *neuclass = GenoOperators::parseNeuroClass(temp);
-			if (neuclass != NULL)
-			{
-				n1->parent->removeChild(n1);
-				f4_node *n2 = new f4_node('[', n1->parent, n1->parent->pos);
-				n2->addChild(n1);
-				n1->parent = n2;
-				neuronid = -1;
-				for (int i = 0; i < g->count(); i++)
-				{
-					f4_node *gcur = g->ordNode(i);
-					temp = (char*)gcur->name.c_str();
-					NeuroClass *neuclass = GenoOperators::parseNeuroClass(temp);
-					if (neuclass != NULL)
-					{
-						neulist.push_back(neuclass);
-					}
-					if (gcur == par)
-					{
-						neuronid = int(neulist.size()) - 1;
-					}
-				}
-				if (neuronid == -1)
-				{
-					return GENOPER_OPFAIL;
-				}
-				linkNodeMakeRandom(n2, neuronid, neulist);
-			}
-		}
-			break;
+			// add connection
+
+			// the probability that a randomly selected node will be a neuron and additionally this neuron will accept inputs is low,
+			// so we disregard randomly picked node_mutated and build a list of all valid candidate nodes here, then select a random one from them.
+
+			vector<f4_Node*> candidate_nodes; //neurons that accept input(s)
+			for (int i = 0; i < g->count(); i++)
+			{
+				f4_Node *node = g->ordNode(i);
+				f4_Node *node_parent = node->parent;
+				if (node_parent == NULL || node_parent->neuclass == NULL) continue;
+				int prefinputs = node_parent->neuclass->getPreferredInputs();
+				if (prefinputs == -1 ||
+					prefinputs > 0) //would be nice if we could easily and quickly check if the parent already has its preferred inputs used, so that we do not produce an invalid mutation here... it is possible through the f4_Cell.n_conns field, but only during organism development
+					candidate_nodes.push_back(node);
+			}
+
+			if (candidate_nodes.size() == 0)
+				return GENOPER_OPFAIL;
+
+			node_mutated = candidate_nodes[rndUint((unsigned int)candidate_nodes.size())];
+			f4_Node *node_mutated_parent = node_mutated->parent;
+
+			int node_mutated_parent_index, other_neuron_index;
+			bool ok = findConnectionNeuronIndexes(g, node_mutated_parent, true, node_mutated_parent_index, other_neuron_index); //node_mutated_parent_index==-1 should never happen, we earlier selected the neuron we are now looking for
+			if (!ok)
+				return GENOPER_OPFAIL;
+
+			node_mutated->parent->removeChild(node_mutated); //this subtree will be reconnected below, as a child to node_new_conn
+			f4_Node *node_new_conn = new f4_Node('[', node_mutated->parent, node_mutated->parent->pos);
+			node_new_conn->addChild(node_mutated);
+			node_mutated->parent = node_new_conn; // node_mutated_parent is the neuron, node_mutated->parent is '['
+			connectionNodeChangeRandom(node_new_conn, node_mutated_parent_index, other_neuron_index);
+		}
+		break;
 		case F4_ADD_NEUPAR:
 		{
 			// add neuron modifier
-			n1->parent->removeChild(n1);
-			f4_node *n2 = new f4_node(':', n1->parent, n1->parent->pos);
+			node_mutated->parent->removeChild(node_mutated);
+			f4_Node *n2 = new f4_Node(':', node_mutated->parent, node_mutated->parent->pos);
 			nparNodeMakeRandom(n2);
-			n2->addChild(n1);
-			n1->parent = n2;
-		}
-			break;
+			n2->addChild(node_mutated);
+			node_mutated->parent = n2;
+		}
+		break;
 		case F4_ADD_REP:
 		{
 			// add repetition ('#')
-			// repeated code (left child) is the original, right child is empty, count is 2
-			f4_node *n3 = n1->parent;
-			n3->removeChild(n1);
-			f4_node *n2 = new f4_node('#', n3, n3->pos);
-			n2->i1 = 2;
-			n2->addChild(n1);
-			new f4_node('>', n2, n2->pos);
-			n1->parent = n2;
-		}
-			break;
+			// repeated code (left child) is the original, right child is empty, count is set to 2
+			f4_Node *n3 = node_mutated->parent;
+			n3->removeChild(node_mutated);
+			f4_Node *n2 = new f4_Node('#', n3, n3->pos);
+			n2->reps = 2;
+			n2->addChild(node_mutated);
+			new f4_Node('>', n2, n2->pos);
+			node_mutated->parent = n2;
+		}
+		break;
 		case F4_ADD_SIMP:
 		{
 			// add simple node
 			// choose a simple node from ADD_SIMPLE_CODES
-			n1->parent->removeChild(n1);
-			//f4_node *n2 = new f4_node(ADD_SIMPLE_CODES[rndUint(strlen(ADD_SIMPLE_CODES))], n1->parent, n1->parent->pos);
+			node_mutated->parent->removeChild(node_mutated);
+			//f4_Node *n2 = new f4_Node(ADD_SIMPLE_CODES[rndUint(strlen(ADD_SIMPLE_CODES))], n1->parent, n1->parent->pos);
 			int modifierid = GenoOperators::getRandomChar(all_modifiers, excluded_modifiers.c_str());
-			f4_node *n2 = new f4_node(all_modifiers[modifierid], n1->parent, n1->parent->pos);
-			n2->addChild(n1);
-			n1->parent = n2;
-		}
-			break;
-		}
-	}
+			f4_Node *n2 = new f4_Node(all_modifiers[modifierid], node_mutated->parent, node_mutated->parent->pos);
+			n2->addChild(node_mutated);
+			node_mutated->parent = n2;
+		}
 		break;
+		}
+	}
+	break;
 
 	case F4_DEL:
@@ -294,72 +309,72 @@
 		for (int i = 0; i < 10; i++)
 		{
-			if ((NULL != n1->parent) && (g != n1->parent))
-				if (NULL != n1->child)
+			if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
+				if (node_mutated->child != NULL)
 					break;
 			// try a new one
-			n1 = g->child->randomNode();
-		}
-		if ((NULL != n1->parent) && (g != n1->parent))
-		{
-			switch (n1->childCount())
+			node_mutated = g->child->randomNode();
+		}
+		if ((node_mutated->parent != NULL) && (g != node_mutated->parent))
+		{
+			switch (node_mutated->childCount())
 			{
 			case 0: break;
 			case 1:  // one child
 			{
-				f4_node *n2 = n1->parent;
-				n2->removeChild(n1);
-				if (NULL != n1->child)
+				f4_Node *node_mutated_parent = node_mutated->parent;
+				node_mutated_parent->removeChild(node_mutated);
+				if (node_mutated->child != NULL)
 				{
-					n1->child->parent = n2;
-					n2->addChild(n1->child);
-					n1->child = NULL;
+					node_mutated->child->parent = node_mutated_parent;
+					node_mutated_parent->addChild(node_mutated->child);
+					node_mutated->child = NULL;
 				}
-				if (NULL != n1->child2)
+				if (node_mutated->child2 != NULL)
 				{
-					n1->child2->parent = n2;
-					n2->addChild(n1->child2);
-					n1->child2 = NULL;
+					node_mutated->child2->parent = node_mutated_parent;
+					node_mutated_parent->addChild(node_mutated->child2);
+					node_mutated->child2 = NULL;
 				}
 				// destroy n1
-				n1->parent = NULL;
-				delete n1;
-			}
-				break;
+				node_mutated->parent = NULL;
+				delete node_mutated;
+			}
+			break;
 
 			case 2:  // two children
 			{
 				// two children
-				f4_node *n2 = n1->parent;
-				n2->removeChild(n1);
+				f4_Node *n2 = node_mutated->parent;
+				n2->removeChild(node_mutated);
 				// n1 has two children. pick one randomly 50-50, destroy other
 				if (rndUint(2) == 0)
 				{
-					n1->child->parent = n2;
-					n2->addChild(n1->child);
-					n1->child = NULL;
-					n1->child2->parent = NULL;
+					node_mutated->child->parent = n2;
+					n2->addChild(node_mutated->child);
+					node_mutated->child = NULL;
+					node_mutated->child2->parent = NULL;
 				}
 				else
 				{
-					n1->child2->parent = n2;
-					n2->addChild(n1->child2);
-					n1->child2 = NULL;
-					n1->child->parent = NULL;
+					node_mutated->child2->parent = n2;
+					n2->addChild(node_mutated->child2);
+					node_mutated->child2 = NULL;
+					node_mutated->child->parent = NULL;
 				}
 				// destroy n1
-				n1->parent = NULL;
-				delete n1;
-			}
-				break;
+				node_mutated->parent = NULL;
+				delete node_mutated;
+			}
+			break;
 			}
 		}
 		else return GENOPER_OPFAIL;
 	}
-		break;
+	break;
 	case F4_MOD:
 	{
 		method = F4_ADD_COUNT - 1 + F4_MOD;
 		// change a node
-		// the only nodes that are modifiable are MUT_CHAN_CODES
+		// the only nodes that are modifiable are F4_MUT_CHANGE_CODES
 		// try to get a modifiable node
 		// already picked a node, but repeat may be needed
@@ -367,168 +382,130 @@
 		while (1)
 		{
-			if (strchr(MUT_CHAN_CODES, n1->name[0])) break;
+			if (strchr(F4_MUT_CHANGE_CODES, node_mutated->name[0])) break;
 			// try a new one
-			n1 = g->child->randomNode();
+			node_mutated = g->child->randomNode();
 			i++;
 			if (i >= 20) return GENOPER_OPFAIL;
 		}
-		switch (n1->name[0])
+		switch (node_mutated->name[0])
 		{
 		case '<':
 		{
 			// swap children
-			f4_node *n2 = n1->child; n1->child = n1->child2; n1->child2 = n2;
-		}
-			break;
+			f4_Node *n2 = node_mutated->child;
+			node_mutated->child = node_mutated->child2;
+			node_mutated->child2 = n2;
+		}
+		break;
 		case '[':
 		{
-			neuronid = -1;
-			for (int i = 0; i < g->count(); i++)
-			{
-				f4_node *gcur = g->ordNode(i);
-				char *temp = (char*)gcur->name.c_str();
-				NeuroClass *neuclass = GenoOperators::parseNeuroClass(temp);
-				if (neuclass != NULL)
-				{
-					neulist.push_back(neuclass);
-				}
-				if (gcur == n1)
-				{
-					neuronid = int(neulist.size()) - 1;
-				}
-			}
-			if (neuronid == -1)
-			{
-				return GENOPER_OPFAIL;
-			}
-			linkNodeChangeRandom(n1, neuronid, neulist);
-		}
-			break;
+			switch (roulette(probmodneu, F4_MODNEU_COUNT))
+			{
+			case F4_MODNEU_CONN:
+			{
+				f4_Node *neuron = node_mutated; //we start in '[' node and follow up parents until we find the neuron with these connections
+				while (neuron != NULL && neuron->neuclass == NULL) neuron = neuron->parent;
+				if (neuron == NULL)
+					return GENOPER_OPFAIL; //did not find a neuron on the way up tree
+
+
+				int neuron_index, other_neuron_index;
+				bool ok = findConnectionNeuronIndexes(g, neuron, true, neuron_index, other_neuron_index); //neuron_index==-1 should never happen, we know the neuron is in the tree
+				if (!ok)
+					return GENOPER_OPFAIL;
+
+				connectionNodeChangeRandom(node_mutated, neuron_index, other_neuron_index);
+				break;
+			}
+			case F4_MODNEU_WEIGHT:
+				node_mutated->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(node_mutated->conn_weight);
+				break;
+			}
+		}
+		break;
 
 		case '#':
 		{
-			repeatNodeChangeRandom(n1);
-		}
-			break;
-		}
-	}
+			repeatNodeChangeRandom(node_mutated);
+		}
 		break;
+		}
+	}
+	break;
 
 	default: //no mutations allowed?
 		return GENOPER_OPFAIL;
 	}
-
 	return GENOPER_OK;
 }
 
-// make a random [ node
-void Geno_f4::linkNodeMakeRandom(f4_node *nn, int neuid, vector<NeuroClass*> neulist) const
-{
-	float prob1;
-	NeuroClass *nc = NULL;
-
-	// 35% chance one of *GTS
-	prob1 = rndDouble(1);
-	prob1 -= 0.35f;
-	if (prob1 < 0)
-	{
-		// '*', 'G', 'T', or 'S', 1/4 chance each
-		nc = GenoOperators::getRandomNeuroClassWithOutputAndNoInputs(Model::SHAPETYPE_BALL_AND_STICK);
-	}
-	if (nc != NULL)
-	{
-		nn->i1 = 1;
-		nn->s1 = nc->getName().c_str();
-		nn->l1 = 0;
-	}
-	else
-	{
-		// relative input link
-		int id = GenoOperators::getRandomNeuroClassWithOutput(neulist);
-		int relid = neuid - id;
-		nn->l1 = relid;
-		//nn->l1 = (int)(4.0f * (rndDouble(1) - 0.5f));
-	}
-	// weight
-	nn->f1 = GenoOperators::getMutatedNeuronConnectionWeight(nn->f1);
-	//nn->f1 = 10.0f * (rndDouble(1) - 0.5f);
+// find all neurons and the needle
+vector<NeuroClass*> Geno_f4::findAllNeuronsAndNode(f4_Node * const & g, f4_Node* const &needle_neuron, int &found_index)
+{
+	found_index = -1; // not found (for example, needle_neuron is not a neuroclass node or not added to the "g" tree)
+	vector<NeuroClass*> neulist;
+	for (int i = 0; i < g->count(); i++)
+	{
+		f4_Node *node = g->ordNode(i);
+		if (node->neuclass != NULL)
+		{
+			neulist.push_back(node->neuclass);
+			if (node == needle_neuron)
+				found_index = int(neulist.size()) - 1;
+		}
+	}
+	return neulist;
+}
+
+bool Geno_f4::findConnectionNeuronIndexes(f4_Node * const &g, f4_Node *neuron, bool other_has_output, int &neuron_index, int &other_neuron_index)
+{
+	vector<NeuroClass*> neulist = findAllNeuronsAndNode(g, neuron, neuron_index);
+	if (neuron_index == -1)
+		return false;
+
+	other_neuron_index = other_has_output ?
+		GenoOperators::getRandomNeuroClassWithOutput(neulist) //find an existing neuron that provides an output
+		:
+		GenoOperators::getRandomNeuroClassWithInput(neulist); //find an existing neuron that accepts input(s)
+	return other_neuron_index >= 0;
 }
 
 // change a [ node
-void Geno_f4::linkNodeChangeRandom(f4_node *nn, int neuid, std::vector<NeuroClass*> neulist) const      //rewritten by M.K. - should work as before (not tested)
-{
-	double probs[3] = { 0.1, 0.3, 0.6 };
-	NeuroClass *cl;
-	// 10% change type
-	// 30% change link
-	// 60% change weight
-
-	switch (roulette(probs, 3))
-	{
-	case 0: // change type
-		// 80% for link, 20% for random sensor
-		if (rndDouble(1) < 0.2f)
-		{
-			cl = GenoOperators::getRandomNeuroClassWithOutputAndNoInputs(Model::SHAPETYPE_BALL_AND_STICK);
-			if (cl != NULL)
-			{
-				nn->i1 = 1;
-				nn->s1 = cl->name.c_str();
-				nn->l1 = 0;
-			}
-		}
-		break;
-	case 1: // change link
-		if (0 == nn->i1) // relative input link
-		{
-			int id = GenoOperators::getRandomNeuroClassWithOutput(neulist);
-			nn->l1 = neuid - id;
-		}
-		//nn->l1 += (int)(2.0f * (rndDouble(1) - 0.5f));
-		break;
-	case 2: // change weight
-		nn->f1 = GenoOperators::getMutatedNeuronConnectionWeight(nn->f1);
-		//nn->f1 += 1.0f * (rndDouble(1) - 0.5f);
-		break;
-	}
-}
+void Geno_f4::connectionNodeChangeRandom(f4_Node *nn, int nn_index, int other_index) const
+{
+	// relative input connection to some existing neuron
+	nn->conn_from = nn_index - other_index;
+	//nn->conn_from = (int)(4.0f * (rndDouble(1) - 0.5f)); //in very old times - did not care about neuron input/output preferences
+
+	nn->conn_weight = GenoOperators::getMutatedNeuronConnectionWeight(nn->conn_weight);
+}
+
 
 // make a random : node
-void Geno_f4::nparNodeMakeRandom(f4_node *nn) const
-{
-	int sign = (int)rndDouble(2);
-	int param = (int)rndDouble(3);
-	if (param > 2) param = 2;
-	nn->l1 = sign;
-	nn->i1 = "!=/"[param];
+void Geno_f4::nparNodeMakeRandom(f4_Node *nn) const
+{
+	unsigned int prop = rndUint(3); //random neuron property
+	nn->prop_symbol = "!=/"[prop];
+	nn->prop_increase = rndUint(2) == 1;
 }
 
 // change a repeat # node
-void Geno_f4::repeatNodeChangeRandom(f4_node *nn) const
-{
-	int count;
-	float prob1;
-
-	// change count
-	count = nn->i1;
-	prob1 = rndDouble(1);
-	if (prob1 < 0.5f) count++;
-	else count--;
-	if (count < 1) count = 1;
-	if (count > REP_MAXCOUNT) count = REP_MAXCOUNT;
-	nn->i1 = count;
-}
-
-
-int Geno_f4::MutateOneValid(f4_node *& g, int &method) const
+void Geno_f4::repeatNodeChangeRandom(f4_Node *nn) const
+{
+	if (rndDouble(1) < 0.5f) nn->reps++; else nn->reps--; // change count
+	if (nn->reps < 1) nn->reps = 1;
+	if (nn->reps > mut_max_rep) nn->reps = mut_max_rep;
+}
+
+
+int Geno_f4::MutateOneValid(f4_Node *& g, int &method) const
 // mutate one, until a valid genotype is obtained
 {
 	// ! the genotype is g->child (not g) !
 	int i, res;
-	f4_node *gcopy = NULL;
-	// try this max 20 times:
-	//   copy, mutate, then validate
-
-	for (i = 0; i < 20; i++)
+	f4_Node *gcopy = NULL;
+	const int TRY_MUTATE = 20;
+	// try this at most TRY_MUTATE times: copy, mutate, then validate
+	for (i = 0; i < TRY_MUTATE; i++)
 	{
 		gcopy = g->duplicate();
@@ -569,5 +546,5 @@
 int Geno_f4::mutate(char *& g, float & chg, int &method)
 {
-	f4_node *root = new f4_node;
+	f4_Node *root = new f4_Node;
 	if (f4_processrec(g, 0, root) || root->childCount() != 1)
 	{
@@ -600,6 +577,6 @@
 
 // convert to tree
-f4_node *root;
-root = new f4_node();
+f4_Node *root;
+root = new f4_Node();
 res = f4_processrec(g, 0, root);
 if (res) {
@@ -654,5 +631,5 @@
 
 
-int Geno_f4::CrossOverOne(f4_node *g1, f4_node *g2, float chg) const
+int Geno_f4::CrossOverOne(f4_Node *g1, f4_Node *g2, float chg) const
 {
 	// ! the genotypes are g1->child and g2->child (not g1 g2) !
@@ -660,10 +637,10 @@
 	int smin, smax;
 	float size;
-	f4_node *n1, *n2, *n1p, *n2p;
+	f4_Node *n1, *n2, *n1p, *n2p;
 
 	// determine desired size
 	size = (1 - chg) * (float)g1->count();
-	smin = (int)(size*0.9f - 1);
-	smax = (int)(size*1.1f + 1);
+	smin = (int)(size * 0.9f - 1);
+	smax = (int)(size * 1.1f + 1);
 	// get a random node with desired size
 	n1 = g1->child->randomNodeWithSize(smin, smax);
@@ -671,6 +648,6 @@
 	// determine desired size
 	size = (1 - chg) * (float)g2->count();
-	smin = (int)(size*0.9f - 1);
-	smax = (int)(size*1.1f + 1);
+	smin = (int)(size * 0.9f - 1);
+	smax = (int)(size * 1.1f + 1);
 	// get a random node with desired size
 	n2 = g2->child->randomNodeWithSize(smin, smax);
@@ -691,5 +668,5 @@
 int Geno_f4::crossOver(char *&g1, char *&g2, float &chg1, float &chg2)
 {
-	f4_node root1, root2, *copy1, *copy2;
+	f4_Node root1, root2, *copy1, *copy2;
 
 	// convert genotype strings into tree structures
@@ -720,8 +697,8 @@
 	// style categories
 #define STYL4CAT_MODIFIC F14_MODIFIERS ","
-#define STYL4CAT_NEUMOD "[]:+-/!="
+#define STYL4CAT_NEUMOD "/!="
 #define STYL4CAT_NEUSPECIAL "|@*"
-#define STYL4CAT_DIGIT "0123456789."
-#define STYL4CAT_REST "XN<># "
+#define STYL4CAT_DIGIT "+-0123456789.[]" //'+' is only for adjusting old-style properties "/!="
+#define STYL4CAT_REST ":XN<># "
 
 	if (!isalpha(ch) && !strchr(STYL4CAT_MODIFIC STYL4CAT_NEUMOD STYL4CAT_NEUSPECIAL STYL4CAT_DIGIT STYL4CAT_REST "\t", ch))
@@ -730,22 +707,36 @@
 	}
 	uint32_t style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT); //default, should be changed below
-	if (strchr("X ", ch))                    style = GENSTYLE_CS(0, GENSTYLE_NONE);
-	else if (strchr("N", ch))                style = GENSTYLE_RGBS(0, 200, 0, GENSTYLE_NONE);
+	if (strchr("X", ch))                     style = GENSTYLE_CS(0, GENSTYLE_BOLD);
+	else if (strchr(":", ch))                style = GENSTYLE_CS(0, GENSTYLE_NONE);
+	else if (strchr("#", ch))                style = GENSTYLE_RGBS(220, 0, 0, GENSTYLE_BOLD);
+	else if (strchr("/=!", ch))              style = GENSTYLE_RGBS(255, 140, 0, GENSTYLE_BOLD); //property... for now, f4 does not supoprt properties in general for any neuron class, like f1 does
+	else if (strchr("N@|*", ch))             style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); //neuroclass
 	else if (strchr("<", ch))                style = GENSTYLE_RGBS(0, 0, 200, GENSTYLE_BOLD);
 	else if (strchr(">", ch))                style = GENSTYLE_RGBS(0, 0, 100, GENSTYLE_NONE);
-	else if (strchr(STYL4CAT_DIGIT, ch))     style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
+	else if (strchr(STYL4CAT_DIGIT, ch))     style = GENSTYLE_CS(GENCOLOR_NUMBER, GENSTYLE_NONE);
 	else if (strchr(STYL4CAT_MODIFIC, ch))   style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
 	else if (strchr(STYL4CAT_NEUMOD, ch))    style = GENSTYLE_RGBS(0, 150, 0, GENSTYLE_NONE);
-	if (isalpha(ch) || strchr(STYL4CAT_NEUSPECIAL, ch))
-	{
+	if (isalpha(ch))
+	{
+		// allowed neuron formats:
+		//   N:CLASSNAME
+		//   N:@
+		//   N:|
+		// old syntax still supported in coloring, but no longer valid:
+		//   [SENSOR, WEIGHT]
+		//   N@
+		//   N|
+		// ...so must have N: or [ before neuroclass name (or just N, but this is handled above - for N@|* only)
+
 		while (pos > 0)
 		{
 			pos--;
-			if (!(isalpha(g[pos]) || strchr(STYL4CAT_NEUSPECIAL, ch)))
-			{
-				if (isupper(g[pos + 1]) && (g[pos] == ':' || g[pos] == '[')) // name of neuron class
-					style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_ITALIC);
-				else // property
-					style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
+			if (!isalpha(g[pos]))
+			{
+				if (isupper(g[pos + 1]) && (g[pos] == '[') || (g[pos] == ':' && pos > 0 && g[pos - 1] == 'N')) //we may have sequences like :-/:I (even though they are not valid) - in this example "I" should not be treated as neuron name, hence there must also be a "N" before ":"
+					style = GENSTYLE_RGBS(150, 0, 150, GENSTYLE_BOLD); // neuroclass
+				//(...) else (...)
+				//	style = GENSTYLE_RGBS(255, 140, 0, GENSTYLE_BOLD); // property - current f4 does not support neuron properties in a general case, only those old-style "/=!" as +! -! += -= +/ -/
+				break;
 			}
 		}
Index: cpp/frams/genetics/f4/f4_oper.h
===================================================================
--- cpp/frams/genetics/f4/f4_oper.h	(revision 1226)
+++ cpp/frams/genetics/f4/f4_oper.h	(revision 1227)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2017  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -33,4 +33,11 @@
 //@}
 
+/** @name Codes for specific F4_MOD neuron mutation subtypes (not included in mutation_method_names, all are considered F4_MOD there) */
+//@{
+#define F4_MODNEU_CONN     0
+#define F4_MODNEU_WEIGHT   1
+#define F4_MODNEU_COUNT    2
+//@}
+
 class Geno_f4 : public GenoOperators
 {
@@ -48,5 +55,7 @@
 	// mutation probabilities
 	double prob[F4_COUNT];            ///<relative probabilities of selecting mutation types in f4 genotype
-	double probadd[F4_ADD_COUNT];     ///<relative probabilities of selecting mutation addition subtypes
+	double probadd[F4_ADD_COUNT];     ///<relative probabilities of selecting addition mutation subtypes
+	double probmodneu[F4_MODNEU_COUNT];     ///<relative probabilities of selecting neuron mutation subtypes
+	paInt mut_max_rep;                ///maximum allowed number of repetitions for the '#' repetition gene
 
 	SString excluded_modifiers;       ///<Modifiers that are excluded in mutation process
@@ -63,5 +72,5 @@
 	 * @return GENOOPER_OK if genotype is valid, GENOPER_REPAIR if genotype can be repaired, GENOPER_OPFAIL if genotype can't be repaired
 	 */
-	int  ValidateRec(f4_node *geno, int retrycount) const;
+	int  ValidateRec(f4_Node *geno, int retrycount) const;
 
 	/**
@@ -86,23 +95,36 @@
 	 * @return GENOPER_OK if mutation was performed successfully, GENOPER_FAIL otherwise
 	 */
-	int  MutateOne(f4_node *& g, int &method) const;
+	int  MutateOne(f4_Node *& g, int &method) const;
 
 	/**
-	 * Creates a random connection to an existing neuron or creates an additional
+	 * Finds all neurons in g (in the order of ordNode()) and returns their neuroclasses in a vector.
+	 * Additionally, looks for the needle_neuron node and returns its index (in the list of the returned vector) as found_index,
+	 * or -1 if not found (for example, it was not a neuroclass node or not added to the "g" tree).
+	 * @param g root node
+	 * @param needle_neuron neuroclass node to look for in all nodes
+	 * @param found_index returned index of needle
+	 * @return all nodes that are neurons
+	 */
+	static vector<NeuroClass*> findAllNeuronsAndNode(f4_Node * const & g, f4_Node* const &needle_neuron, int &found_index);
+
+	/**
+	 * Finds indexes of a given neuron and another random (output- or input-providing) neuron in the list of all neurons present in the "g" tree.
+	 * @param g root node
+	 * @param neuron neuroclass node to look for in all nodes in g
+	 * @param other_has_output if true, other neuron will provide output; otherwise, it will accept input(s)
+	 * @param neuron_index returned index of neuron
+	 * @param other_neuron_index returned index of a random neuron that provides an output or accepts inputs
+	 * @return true if succeeded, false otherwise
+	 */
+	static bool findConnectionNeuronIndexes(f4_Node * const &g, f4_Node *neuron, bool other_has_output, int &neuron_index, int &other_neuron_index);
+
+	/**
+	 * Creates a random connection to an existing neuron and randomizes connection weight
 	 * sensor for a neuron.
 	 * @param nn neuron class node
-	 * @param neuid id of a neuron
-	 * @param neulist list of genotype neuron classes
+	 * @param nn_index index of the nn neuron
+	 * @param other_index index of the neuron providing output, to get input from
 	 */
-	void linkNodeMakeRandom(f4_node *nn, int neuid, std::vector<NeuroClass*> neulist) const;
-
-	/**
-	 * Changes connection to an existing neuron or creates an additional
-	 * sensor for neuron.
-	 * @param nn neuron connection node
-	 * @param neuid id of a neuron
-	 * @param neulist list of genotype neuron classes
-	 */
-	void linkNodeChangeRandom(f4_node *nn, int neuid, std::vector<NeuroClass*> neulist) const;
+	void connectionNodeChangeRandom(f4_Node *nn, int nn_index, int other_index) const;
 
 	/**
@@ -110,5 +132,5 @@
 	 * @param nn neuron node
 	 */
-	void nparNodeMakeRandom(f4_node *nn) const;
+	void nparNodeMakeRandom(f4_Node *nn) const;
 
 	/**
@@ -116,5 +138,5 @@
 	 * @param nn repetition node
 	 */
-	void repeatNodeChangeRandom(f4_node *nn) const;
+	void repeatNodeChangeRandom(f4_Node *nn) const;
 
 	/**
@@ -124,5 +146,5 @@
 	 * @return GENOPER_OK if performed successful mutation, GENOPER_FAIL otherwise
 	 */
-	int  MutateOneValid(f4_node *&g, int &method) const;
+	int  MutateOneValid(f4_Node *&g, int &method) const;
 
 	/**
@@ -134,5 +156,5 @@
 	 * @param chg percentage of the first parent in offspring (the second parent has the rest)
 	 */
-	int  CrossOverOne(f4_node *g1, f4_node *g2, float chg) const;
+	int  CrossOverOne(f4_Node *g1, f4_Node *g2, float chg) const;
 };
 
