Index: cpp/frams/genetics/f4/conv_f4.cpp
===================================================================
--- cpp/frams/genetics/f4/conv_f4.cpp	(revision 196)
+++ cpp/frams/genetics/f4/conv_f4.cpp	(revision 196)
@@ -0,0 +1,285 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+// Copyright (C) since 2001 Maciej Komosinski
+
+#include "conv_f4.h"
+#include <common/framsg.h>
+#include "../oper_fx.h" //for GENOPER_OK constant
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+
+
+GenoConv_f40::GenoConv_f40()
+{
+	name = "Developmental encoding";
+	in_format = '4';
+	out_format = '0';
+	mapsupport = 1;
+}
+
+
+SString GenoConv_f40::convert(SString &in, MultiMap * map)
+{
+	int res;
+	f4_Model * model = new f4_Model();
+	res = model->buildFromF4(in);
+	if (GENOPER_OK != res) return SString();  // oops
+	if (NULL != map)
+		// generate to-f0 conversion map
+		model->getCurrentToF0Map(*map);
+	SString out = model->getF0Geno().getGene();
+	delete model;
+	return out;
+}
+
+
+GenoConv_F41_TestOnly::GenoConv_F41_TestOnly()
+{
+	name = "test-only approximate f4 -> f1 converter";
+	in_format = '4';
+	out_format = '1';
+	mapsupport = 0;
+	info = "This is for testing.  Do not use in production! (adam)";
+}
+
+
+SString GenoConv_F41_TestOnly::convert(SString &in, MultiMap * map)
+{
+	int res;
+	f4_Model * model = new f4_Model();
+	res = model->buildFromF4(in);
+	if (GENOPER_OK != res) return SString();  // oops
+	SString out;
+	model->toF1Geno(out);
+	delete model;
+	return out;
+}
+
+
+f4_Model::f4_Model() : Model()
+{
+	cells = NULL;
+}
+
+f4_Model::~f4_Model()
+{
+	if (cells) delete cells;
+}
+
+int f4_Model::buildFromF4(SString &geno)
+{
+	int i;
+
+	error = GENOPER_OK;
+	errorpos = -1;
+
+	// build cells, and simulate
+	if (cells) delete cells;
+	cells = new f4_Cells(geno, 0);
+	if (GENOPER_OK != cells->geterror())
+	{
+		error = cells->geterror();
+		errorpos = cells->geterrorpos();
+		//delete cells;
+		return error;
+	}
+
+	cells->simulate();
+	if (GENOPER_OK != cells->geterror())
+	{
+		error = cells->geterror();
+		errorpos = cells->geterrorpos();
+		return error;
+	}
+
+	// reset recursive traverse flags
+	for (i = 0; i < cells->nc; i++)
+		cells->C[i]->recProcessedFlag = 0;
+
+	open(); // begin model build
+
+	// process every cell
+	int res;
+	for (i = 0; i < cells->nc; i++)
+	{
+		res = buildModelRec(cells->C[i]);
+		if (res)
+		{
+			FramMessage("f4_Model", "buildModelRec", "Error in building Model", 2);
+			error = res;
+			break;
+		}
+	}
+
+	res = close();
+	if (0 == res) // invalid
+		error = -10;
+
+	return error;
+}
+
+
+f4_Cell * f4_Model::getStick(f4_Cell * C)
+{
+	if (T_STICK4 == C->type) 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)
+			return cells->C[i];
+	// none!
+	FramMessage("f4_Model", "getStick", "Not a single stick", 2);
+	return NULL;
+}
+
+
+/// updated by Macko to follow new GDK standards (no more neuroitems)
+int f4_Model::buildModelRec(f4_Cell * C)
+{
+	int partidx;
+	int j, res;
+	MultiRange range;
+
+	if (C->recProcessedFlag)
+		// already processed
+		return 0;
+
+	// mark it processed
+	C->recProcessedFlag = 1;
+
+	// make sure parent is a stick
+	if (NULL != C->dadlink)
+		if (C->dadlink->type != T_STICK4)
+		{
+			C->dadlink = getStick(C->dadlink);
+		}
+
+	// make sure its parent is processed first
+	if (NULL != C->dadlink)
+	{
+		res = buildModelRec(C->dadlink);
+		if (res) return res;
+	}
+
+	char tmpLine[100];
+
+	range = C->genoRange;
+	if (C->type == T_STICK4)
+	{
+		int jj_p1_refno;  // save for later
+		// first end is connected to dad, or new
+		if (C->dadlink == NULL)
+		{
+			// new part object for firstend
+			// coordinates are left to be computed by Model
+			sprintf(tmpLine, "p:m=1,fr=%g,ing=%g,as=%g",
+				/*1.0/C->P.mass,*/ C->P.friction, C->P.ingest, C->P.assim
+				//C->firstend.x, C->firstend.y, C->firstend.z
+				);
+			partidx = singleStepBuild(tmpLine, &range);
+			if (partidx < 0) return -1;
+			jj_p1_refno = partidx;
+		}
+		else {
+			// adjust mass/vol of first endpoint
+			jj_p1_refno = C->dadlink->p2_refno;
+			Part * p1 = getPart(jj_p1_refno);
+			p1->mass += 1.0;
+			//      p1->volume += 1.0/C->P.mass;
+		}
+		// new part object for lastend
+		sprintf(tmpLine, "p:m=1,fr=%g,ing=%g,as=%g",
+			//C->lastend.x, C->lastend.y, C->lastend.z
+			/*"vol=" 1.0/C->P.mass,*/ C->P.friction, C->P.ingest, C->P.assim
+			);
+		partidx = singleStepBuild(tmpLine, &range);
+		if (partidx < 0) return -2;
+		C->p2_refno = partidx;
+
+		// new joint object
+		// check that the part references are valid
+		int jj_p2_refno = C->p2_refno;
+		if ((jj_p1_refno < 0) || (jj_p1_refno >= getPartCount())) return -11;
+		if ((jj_p2_refno < 0) || (jj_p2_refno >= getPartCount())) return -12;
+		sprintf(tmpLine, "j:p1=%ld,p2=%ld,dx=%g,dy=0,dz=0,rx=%g,ry=0,rz=%g"\
+			",stam=%g",
+			jj_p1_refno, jj_p2_refno,
+			// relative position -- always (len, 0, 0), along the stick
+			// this is optional!
+			C->P.len,
+			// relative rotation
+			C->xrot, C->zrot,
+			//C->P.ruch,   // rotstif
+			C->P.odpor
+			);
+		partidx = singleStepBuild(tmpLine, &range);
+		if (partidx < 0) return -13;
+		C->joint_refno = partidx;
+	}
+
+	if (C->type == T_NEURON4) ///<this case was updated by MacKo
+	{
+		// new neuron object
+		// it is linked to a part (not a joint!)
+		int p_refno = C->dadlink->p2_refno;
+		if ((p_refno < 0) || (p_refno >= getPartCount())) return -21;
+		// joint_refno is currently not used
+		sprintf(tmpLine, "n:p=%ld,d=\"N:in=%g,fo=%g,si=%g\"",
+			p_refno,
+			C->inertia, C->force, C->sigmo);
+		partidx = singleStepBuild(tmpLine, &range);
+		if (partidx < 0) return -22;
+		C->neuro_refno = partidx;
+		int n_refno = C->neuro_refno;
+
+		if (C->ctrl)
+		{
+			if (1 == C->ctrl)
+				sprintf(tmpLine, "n:j=%d,d=\"@:p=%g\"", C->dadlink->joint_refno, C->P.ruch);
+			else
+				sprintf(tmpLine, "n:j=%d,d=\"|:p=%g,r=%g\"", C->dadlink->joint_refno, C->P.ruch, C->mz);
+			partidx = singleStepBuild(tmpLine, &range);
+			if (partidx < 0) return -32;
+			sprintf(tmpLine, "c:%d,%d", partidx, n_refno);
+			if (singleStepBuild(tmpLine, &range) < 0) return -33;
+		}
+
+		for (j = 0; j < C->nolink; j++)
+		{
+			if (NULL != C->links[j]->from)
+				buildModelRec(C->links[j]->from);
+
+			tmpLine[0] = 0;
+			if (1 == C->links[j]->t) sprintf(tmpLine, "n:p=%d,d=\"*\"", p_refno);
+			if (2 == C->links[j]->t) sprintf(tmpLine, "n:j=%d,d=\"G\"", C->dadlink->joint_refno);
+			if (3 == C->links[j]->t) sprintf(tmpLine, "n:p=%d,d=\"T\"", p_refno);
+			if (4 == C->links[j]->t) sprintf(tmpLine, "n:p=%d,d=\"S\"", p_refno);
+			int from = -1;
+			if (tmpLine[0]) //input from receptor
+			{
+				from = singleStepBuild(tmpLine, &range);
+				if (from < 0) return -34;
+			} /*could be 'else'...*/
+			if (NULL != C->links[j]->from) // input from another neuron
+				from = C->links[j]->from->neuro_refno;
+			if (from >= 0)
+			{
+				sprintf(tmpLine, "c:%d,%d,%g", n_refno, from, C->links[j]->w);
+				if (singleStepBuild(tmpLine, &range) < 0) return -35;
+			}
+		}
+	}
+	return 0;
+}
+
+
+void f4_Model::toF1Geno(SString &out)
+{
+	cells->toF1Geno(out);
+}
Index: cpp/frams/genetics/f4/conv_f4.h
===================================================================
--- cpp/frams/genetics/f4/conv_f4.h	(revision 196)
+++ cpp/frams/genetics/f4/conv_f4.h	(revision 196)
@@ -0,0 +1,55 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+// Copyright (C) since 2001 Maciej Komosinski
+
+#ifndef _CONV_F4_H_
+#define _CONV_F4_H_
+
+#include <frams/model/model.h>
+#include <frams/model/modelparts.h>
+#include <frams/genetics/genoconv.h>
+#include "f4_general.h"
+
+
+// The f4->f0 converter
+class GenoConv_f40 : public GenoConverter
+{
+public:
+	GenoConv_f40();
+	SString convert(SString &in, MultiMap * map);
+};
+
+
+// a test-only f4->f1 converter, approximates only
+class GenoConv_F41_TestOnly : public GenoConverter
+{
+public:
+	GenoConv_F41_TestOnly();
+	SString convert(SString &in, MultiMap * map);
+};
+
+
+// A Model descendant, which support build from an f4 genotype.
+class f4_Model : public Model
+{
+public:
+	f4_Model();
+	~f4_Model();
+	int      buildFromF4(SString &geno);
+	void     toF1Geno(SString &out);       // output to f1 format, approximation
+private:
+	f4_Cells * cells;
+	int        buildModelRec(f4_Cell * ndad);
+	/**
+	 * Get a cell which is a stick, by traversing dadlinks.
+	 */
+	f4_Cell *  getStick(f4_Cell * C);
+	int        error;
+	int        errorpos;
+};
+
+
+#endif
Index: cpp/frams/genetics/f4/f4_general.cpp
===================================================================
--- cpp/frams/genetics/f4/f4_general.cpp	(revision 196)
+++ cpp/frams/genetics/f4/f4_general.cpp	(revision 196)
@@ -0,0 +1,1407 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+// Copyright (C) since 2001 Maciej Komosinski
+
+#include "f4_general.h"
+#include <common/nonstd_stl.h>
+#include <common/framsg.h>
+#include <frams/model/model.h> // for min and max attributes
+#include "../oper_fx.h" //for GENOPER_ constants
+#include <stdio.h>
+#include <common/nonstd_math.h>
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+
+
+f4_Props::f4_Props()
+{
+	len = 1.0;
+	curv = 0.0;
+	mass = 1.0;
+	friction = 0.4;
+	ruch = 0.25; // bio
+	assim = 0.25; // bio
+	odpor = 0.25; // bio
+	ingest = 0.25; // bio
+	twist = 0.0;
+	energ = 1.0;
+	normalizeBiol4();
+}
+
+void f4_Props::normalizeBiol4()
+{
+	// must sum to 1
+	double sum = ruch + assim + odpor + ingest;
+	if (0 == sum)
+	{
+		ruch = assim = odpor = ingest = 0.25;
+	}
+	else {
+		ruch /= sum;
+		assim /= sum;
+		odpor /= sum;
+		ingest /= sum;
+	}
+}
+
+void f4_Props::executeModifier(char modif)
+{
+	switch (modif)
+	{
+	case 'L': len += (2.5 - len) * 0.3;
+		len = min(len, Model::getMaxJoint().d.x); break;
+	case 'l': len += (0.3 - len) * 0.3;
+		len = max(len, Model::getMinJoint().d.x); break;
+	case 'C': curv += (2.0 - curv) * 0.25;  break;
+	case 'c': curv += (-2.0 - curv) * 0.25;  break;
+	case 'Q': twist += (1.58 - twist) * 0.3; break;
+	case 'q': twist += (-1.58 - twist) * 0.3; break;
+	case 'A': assim += (1 - assim) * 0.8; normalizeBiol4(); break;
+	case 'a': assim -= assim * 0.4;       normalizeBiol4(); break;
+	case 'I': ingest += (1 - ingest) * 0.8;   normalizeBiol4(); break;
+	case 'i': ingest -= ingest * 0.4;         normalizeBiol4(); break;
+	case 'S': odpor += (1 - odpor) * 0.8; normalizeBiol4(); break;
+	case 's': odpor -= odpor * 0.4;       normalizeBiol4(); break;
+	case 'M': ruch += (1 - ruch) * 0.8;   normalizeBiol4(); break;
+	case 'm': ruch -= ruch * 0.4;         normalizeBiol4(); break;
+	case 'F': friction += (4 - friction) * 0.2; break;
+	case 'f': friction -= friction * 0.2;       break;
+	case 'W': mass += (2.0 - mass) * 0.3;   break;
+	case 'w': mass += (0.5 - mass) * 0.3;   break;
+	case 'E': energ += (10.0 - energ) * 0.1; break;
+	case 'e': energ -= energ * 0.1;          break;
+	}
+}
+
+void f4_Props::adjust()
+{
+	len = 0.5*len + 0.5*stdProps.len;
+	curv = 0.66 * curv;
+	twist = 0.66 * twist;
+}
+
+f4_Props stdProps;
+
+
+void rolling_dec(double * v) {
+	*v -= 0.7853;  // 0.7853981  45 degrees
+}
+void rolling_inc(double * v) {
+	*v += 0.7853;  // 0.7853981  45 degrees
+}
+
+
+int scanrec(const char * s, unsigned int slen, char stopchar)
+{
+	unsigned int i = 0;
+	//DB( printf("    scan('%s', '%c')\n", s, stopchar); )
+	while (1)
+	{
+		if (i >= slen)  // ran out the string, should never happen with correct string
+			return 1;
+		if (stopchar == s[i])  // bumped into stopchar
+			return i;
+		if (i < slen - 1)
+		{ // s[i] is not the last char
+			if (s[i] == '(')
+			{
+				i += 2 + scanrec(s + i + 1, slen - i - 1, ')');
+				continue;
+			}
+			if (s[i] == '<')
+			{
+				i += 2 + scanrec(s + i + 1, slen - i - 1, '>');
+				continue;
+			}
+			if (s[i] == '#')
+			{
+				i += 2 + scanrec(s + i + 1, slen - i - 1, '>');
+				continue;
+			}
+		}
+		// s[i] a non-special character
+		i++;
+	}
+	return i;
+}
+
+
+f4_Cell::f4_Cell(int nname,
+	f4_Cell * ndad, int nangle, f4_Props newP)
+{
+	name = nname;
+	type = T_UNDIFF4;
+	dadlink = ndad;
+	org = NULL;
+	genot = NULL;
+	gcur = NULL;
+	active = 1;
+	repeat.null();
+	//genoRange.clear(); -- implicit
+
+	anglepos = nangle;
+	commacount = 0;
+	childcount = 0;
+	P = newP;
+	rolling = 0;
+	xrot = 0;
+	zrot = 0;
+	//OM = Orient_1;
+	ctrl = 0;
+	state = 0;
+	inertia = 0.8;
+	force = 0.04;
+	sigmo = 2;
+	nolink = 0;
+
+	// adjust firstend and OM if there is a stick dad
+	if (ndad != NULL)
+	{
+		// make sure it is a stick (and not a stick f4_Cell!)
+		if (T_STICK4 == ndad->type)
+		{
+			//firstend = ndad->lastend;
+			//OM = ndad->OM;
+			ndad->childcount++;
+		}
+		if (T_NEURON4 == ndad->type)
+		{
+			state = ndad->state;
+			inertia = ndad->inertia;
+			force = ndad->force;
+			sigmo = ndad->sigmo;
+		}
+	}
+	// adjust lastend
+	//lastend = firstend + ((Orient)OM * (Pt3D(1,0,0) * P.len));
+	mz = 1;
+}
+
+
+f4_Cell::f4_Cell(f4_Cells * nO, int nname, f4_node * ngeno, f4_node * ngcur,
+	f4_Cell * ndad, int nangle, f4_Props newP)
+{
+	name = nname;
+	type = T_UNDIFF4;
+	dadlink = ndad;
+	org = nO;
+	genot = ngeno;
+	gcur = ngcur;
+	active = 1;
+	repeat.null();
+	//genoRange.clear(); -- implicit
+	// preserve geno range of parent cell
+	if (NULL != ndad)
+		genoRange.add(ndad->genoRange);
+
+	anglepos = nangle;
+	commacount = 0;
+	childcount = 0;
+	P = newP;
+	rolling = 0;
+	xrot = 0;
+	zrot = 0;
+	//OM = Orient_1;
+	ctrl = 0;
+	state = 0;
+	inertia = 0.8;
+	force = 0.04;
+	sigmo = 2;
+	nolink = 0;
+
+	// adjust firstend and OM if there is a stick dad
+	if (ndad != NULL)
+	{
+		// make sure it is a stick (and not a stick f4_Cell!)
+		if (T_STICK4 == ndad->type) {
+			//firstend = ndad->lastend;
+			//OM = ndad->OM;
+			ndad->childcount++;
+		}
+		if (T_NEURON4 == ndad->type)
+		{
+			state = ndad->state;
+			inertia = ndad->inertia;
+			force = ndad->force;
+			sigmo = ndad->sigmo;
+		}
+	}
+	// adjust lastend
+	//lastend = firstend + ((Orient)OM * (Pt3D(1,0,0) * P.len));
+	mz = 1;
+}
+
+
+f4_Cell::~f4_Cell()
+{
+	// remove links
+	if (nolink)
+	{
+		int i;
+		for (i = nolink - 1; i >= 0; i--)
+			delete links[i];
+		nolink = 0;
+	}
+}
+
+
+/* return codes:
+	>1 error at pos
+	0  halt development for a cycle
+	-1  development finished OK
+	*/
+int f4_Cell::onestep()
+{
+	int i, j, k, relfrom, t;
+	double w;
+	f4_Cell * tmp;
+	f4_Cell * tneu;
+	if (gcur == NULL)
+	{
+		active = 0;
+		return 0;  // stop
+	}
+	while (NULL != gcur)
+	{
+		//DB( printf("  %d (%d) executing '%c' %d\n", name, type, gcur->name, gcur->pos); )
+		// currently this is the last one processed
+		// the current genotype code is processed
+		genoRange.add(gcur->pos);
+		switch (gcur->name)
+		{
+		case '<':
+			// cell division!
+			//DB( printf("  div! %d\n", name); )
+
+			// error: sticks cannot divide
+			if (T_STICK4 == type) {
+				// cannot fix
+				org->setError(gcur->pos);
+				return 1;  // stop
+			}
+
+			// undiff divides
+			if (T_UNDIFF4 == type) {
+				// commacount is set only when daughter turns into X
+				// daughter cell
+				// adjust new len
+				f4_Props newP = P;
+				newP.adjust();
+				tmp = new f4_Cell(org, org->nc, genot, gcur->child2,
+					this, commacount, newP);
+				tmp->repeat = repeat;
+				repeat.null();
+				org->addCell(tmp);
+			}
+			// a neuron divides: create a new, duplicate links
+			if (T_NEURON4 == type) {
+				// daughter cell
+				tmp = new f4_Cell(org, org->nc, genot, gcur->child2,
+					// has the same dadlink
+					this->dadlink, commacount, P);
+				tmp->repeat = repeat;
+				repeat.null();
+				// it is a neuron from start
+				tmp->type = T_NEURON4;
+				// duplicate links
+				f4_CellLink * ll;
+				for (i = 0; i < nolink; i++) {
+					ll = links[i];
+					tmp->addlink(ll->from, ll->w, ll->t);
+				}
+				org->addCell(tmp);
+			}
+			// adjustments for this cell
+			gcur = gcur->child;
+			// halt development
+			return 0;
+
+		case '>':
+			// finish
+			// see if there is a repet count
+			if (repeat.top > 0)
+			{ // there is a repeat counter
+				if (!repeat.first()->isNull())
+				{ // repeat counter is not null
+					repeat.first()->dec();
+					if (repeat.first()->count > 0)
+					{
+						// return to repeat
+						gcur = repeat.first()->node->child;
+					}
+					else {
+						// continue
+						gcur = repeat.first()->node->child2;
+						repeat.pop();
+					}
+					break;
+				}
+				else {
+					repeat.pop();
+				}
+			}
+			else {
+				// error: still undiff
+				if (T_UNDIFF4 == type)
+				{
+					// fix it: insert an 'X'
+					f4_node * insertnode = new f4_node('X', NULL, gcur->pos);
+					if (org->setRepairInsert(gcur->pos, gcur, insertnode))
+						// not in repair mode, release
+						delete insertnode;
+					return 1;
+				}
+				repeat.null();
+				active = 0;  // stop
+				// eat up rest
+				gcur = NULL;
+				return 0;
+			}
+
+		case '#':
+			// repetition marker
+			if (repeat.top >= repeat_stack::stackSize)
+			{
+				// repepeat pointer stack is full, cannot remember this one.
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			repeat.push(repeat_ptr(gcur, gcur->i1));
+			gcur = gcur->child;
+			break;
+
+		case ',':
+			commacount++;
+			gcur = gcur->child;
+			break;
+
+		case 'r':  case 'R':
+			// error: if neuron
+			if (T_NEURON4 == type) {
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			switch (gcur->name) {
+			case 'r':   rolling_dec(&rolling); break;
+			case 'R':   rolling_inc(&rolling); break;
+			}
+			gcur = gcur->child;
+			break;
+
+		case 'l':  case 'L':
+		case 'c':  case 'C':
+		case 'q':  case 'Q':
+		case 'a':  case 'A':
+		case 'i':  case 'I':
+		case 's':  case 'S':
+		case 'm':  case 'M':
+		case 'f':  case 'F':
+		case 'w':  case 'W':
+		case 'e':  case 'E':
+			// error: if neuron
+			if (T_NEURON4 == type) {
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			P.executeModifier(gcur->name);
+			gcur = gcur->child;
+			break;
+
+		case 'X':
+			// turn undiff. cell into a stick
+			// error: already differentiated
+			if (T_UNDIFF4 != type) {
+				// fix: delete this node
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			type = T_STICK4;
+			// fix dad commacount and own anglepos
+			if (NULL != dadlink) {
+				dadlink->commacount++;
+				anglepos = dadlink->commacount;
+			}
+			// change of type halts developments, see comment at 'N'
+			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) {
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			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
+			gcur = gcur->child;
+			return 0;
+
+		case '@':
+		case '|':
+			// neuron rotating / bending
+			j = 1;
+			if ('@' == gcur->name) j = 1; // rot
+			if ('|' == gcur->name) j = 2; // bend
+			// error: not a neuron (undiff)
+			if (T_UNDIFF4 == type) {
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			// error: not a neuron (stick)
+			if (T_NEURON4 != type) {
+				// fix: delete it
+				org->setRepairRemove(gcur->pos, gcur);
+				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 ('*', 'G', 'T', 'S', or %d)
+			t = gcur->i1;
+			relfrom = gcur->l1;
+			w = gcur->f1;
+			if (t > 0) {
+				// * or G
+				tneu = NULL;
+			}
+			else {
+				// input from other neuron
+				// find neuron at relative i
+				// find own index
+				j = 0; k = 0;
+				for (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;
+				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, t)) {
+				// 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;
+			j = 0;
+			for (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
+			}
+			j = (int)gcur->l1;
+			switch ((char)gcur->i1) {
+			case '!':
+				if (j) force += (1.0 - force) * 0.2;
+				else   force -= force * 0.2; break;
+			case '=':
+				if (j) inertia += (1.0 - inertia) * 0.2;
+				else   inertia -= inertia * 0.2; break;
+			case '/':
+				if (j) sigmo *= 1.4;
+				else   sigmo /= 1.4; break;
+			default:
+				org->setRepairRemove(gcur->pos, gcur);
+				return 1;  // stop
+			}
+			gcur = gcur->child;
+			break;
+
+		case ' ':
+			// space has no effect, should not occur
+			// fix: delete it
+			org->setRepairRemove(gcur->pos, gcur);
+			gcur = gcur->child;
+			break;
+
+		default:
+			// error: unknown code
+			char buf[40];
+			sprintf(buf, "unknown code '%c'", gcur->name);
+			FramMessage("f4_Cell", "onestep", buf, 2);
+			// fix: delete it
+			org->setRepairRemove(gcur->pos, gcur);
+			return 1; // stop
+		}
+	}
+	active = 0;  // done
+	return 0;
+}
+
+
+int f4_Cell::addlink(f4_Cell * nfrom, double nw, long nt)
+{
+	if (nolink >= MAXINPUTS - 1) return -1; // full!
+	links[nolink] = new f4_CellLink(nfrom, nw, nt);
+	nolink++;
+	return 0;
+}
+
+
+void f4_Cell::adjustRec()
+{
+	//f4_OrientMat rot;
+	int     i;
+
+	if (recProcessedFlag)
+		// already processed
+		return;
+
+	// mark it processed
+	recProcessedFlag = 1;
+
+	// make sure its parent is processed first
+	if (NULL != dadlink)
+		dadlink->adjustRec();
+
+	// count children
+	childcount = 0;
+	for (i = 0; i < org->nc; i++)
+	{
+		if (org->C[i]->dadlink == this)
+			if (org->C[i]->type == T_STICK4)
+				childcount++;
+	}
+
+	if (type == T_STICK4)
+	{
+		if (NULL == dadlink)
+		{
+			//firstend = Pt3D_0;
+			// rotation due to rolling
+			xrot = rolling;
+			mz = 1;
+		}
+		else {
+			//firstend = dadlink->lastend;
+			f4_Props Pdad = dadlink->P;
+			f4_Props Padj = Pdad;
+			Padj.adjust();
+
+			//rot = Orient_1;
+
+			// rotation due to rolling
+			xrot = rolling +
+				// rotation due to twist
+				Pdad.twist;
+			if (dadlink->commacount <= 1)
+			{
+				// rotation due to curvedness
+				zrot = Padj.curv;
+			}
+			else {
+				zrot = Padj.curv +
+					// GDK uses 3.141 instead of PI!
+					(anglepos * 1.0 / (dadlink->commacount + 1) - 0.5) * 3.141 * 2.0;
+			}
+
+			//rot = rot * f4_OrientMat(yOz, xrot);
+			//rot = rot * f4_OrientMat(xOy, zrot);
+			// rotation relative to parent stick
+			//OM = rot * OM;
+
+			// rotation in world coordinates
+			//OM =  ((f4_OrientMat)dadlink->OM) * OM;
+			mz = dadlink->mz / dadlink->childcount;
+		}
+		//Pt3D lastoffset = (Orient)OM * (Pt3D(1,0,0)*P.len);
+		//lastend = firstend + lastoffset;
+	}
+}
+
+
+
+f4_CellLink::f4_CellLink(f4_Cell * nfrom, double nw, long nt)
+{
+	from = nfrom;
+	w = nw;
+	t = nt;
+}
+
+
+
+f4_Cells::f4_Cells(f4_node * genome, int nrepair)
+{
+	// create ancestor cell
+	repair = nrepair;
+	error = 0;
+	errorpos = -1;
+	repair_remove = NULL;
+	repair_parent = NULL;
+	repair_insert = NULL;
+	tmpcel = NULL;
+	f4rootnode = NULL;
+
+	C[0] = new f4_Cell(this, 0, genome, genome, NULL, 0, stdProps);
+	nc = 1;
+}
+
+
+f4_Cells::f4_Cells(SString & genome, int nrepair)
+{
+	int res;
+	repair = nrepair;
+	error = 0;
+	errorpos = -1;
+	repair_remove = NULL;
+	repair_parent = NULL;
+	repair_insert = NULL;
+	tmpcel = NULL;
+	f4rootnode = NULL;
+
+	// transform geno from string to nodes
+	f4rootnode = new f4_node();
+	res = f4_processrec((const char*)genome, (unsigned)0, f4rootnode);
+	if ((res < 0) || (1 != f4rootnode->childCount()))
+	{
+		error = GENOPER_OPFAIL;
+		errorpos = -1;
+	}
+
+	// create ancestor cell
+	C[0] = new f4_Cell(this, 0, f4rootnode->child, f4rootnode->child,
+		NULL, 0, stdProps);
+	nc = 1;
+}
+
+f4_Cells::~f4_Cells()
+{
+	// release cells
+	int i;
+	if (nc)
+	{
+		for (i = nc - 1; i >= 0; i--)
+			delete C[i];
+		nc = 0;
+	}
+	if (f4rootnode)
+		delete f4rootnode;
+}
+
+
+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) {
+			// error
+			C[i]->active = 0;  // stop
+			return 0;
+		}
+		// if still active
+		if (C[i]->active)
+			ret = 1;
+	}
+	return ret;
+}
+
+
+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;
+
+	// fix neuron attachements
+	for (i = 0; i < nc; i++)
+		if (C[i]->type == T_NEURON4) {
+			while (T_NEURON4 == C[i]->dadlink->type) {
+				C[i]->dadlink = C[i]->dadlink->dadlink;
+			}
+		}
+
+	// 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();
+		}
+
+	// recursive adjust
+	// reset recursive traverse flags
+	for (i = 0; i < nc; i++)
+		C[i]->recProcessedFlag = 0;
+	// process every cell
+	for (i = 0; i < nc; i++)
+		C[i]->adjustRec();
+
+	//DB( printf("Cell simulation done, %d cells. \n", nc); )
+
+	return error;
+}
+
+
+void f4_Cells::addCell(f4_Cell * newcell)
+{
+	if (nc >= MAX4CELLS - 1) {
+		delete newcell;
+		return;
+	}
+	C[nc] = newcell;
+	nc++;
+}
+
+
+void f4_Cells::setError(int nerrpos)
+{
+	error = GENOPER_OPFAIL;
+	errorpos = nerrpos;
+}
+
+void f4_Cells::setRepairRemove(int nerrpos, f4_node * rem)
+{
+	if (!repair) {
+		// not in repair mode, treat as repairable error
+		error = GENOPER_REPAIR;
+		errorpos = nerrpos;
+	}
+	else {
+		error = GENOPER_REPAIR;
+		errorpos = nerrpos;
+		repair_remove = rem;
+	}
+}
+
+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;
+		errorpos = nerrpos;
+		return -1;
+	}
+	else {
+		error = GENOPER_REPAIR;
+		errorpos = nerrpos;
+		repair_parent = parent;
+		repair_insert = insert;
+		return 0;
+	}
+}
+
+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;
+	// traverse genotype tree, remove / insert node
+	f4_node * g2;
+	if (1 == whichchild) g2 = geno->child;
+	else             g2 = geno->child2;
+	if (NULL == g2)
+		return;
+	if (g2 == repair_remove) {
+		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;
+			g2->child->parent = geno;
+		}
+		oldgeno = g2;
+		oldgeno->child = NULL;
+		delete oldgeno;
+		if (NULL == geno->child) return;
+		// check this new
+		repairGeno(geno, whichchild);
+		return;
+	}
+	if (g2 == repair_parent) {
+		geno->removeChild(g2);
+		geno->addChild(repair_insert);
+		repair_insert->parent = geno;
+		repair_insert->child = g2;
+		repair_insert->child2 = NULL;
+		g2->parent = repair_insert;
+	}
+	// recurse
+	if (g2->child)  repairGeno(g2, 1);
+	if (g2->child2) repairGeno(g2, 2);
+}
+
+
+void f4_Cells::toF1Geno(SString &out)
+{
+	if (tmpcel) delete tmpcel;
+	tmpcel = new f4_Cell(-1, NULL, 0, stdProps);
+	out = "";
+	toF1GenoRec(0, out);
+	delete tmpcel;
+}
+
+
+void f4_Cells::toF1GenoRec(int curc, SString &out)
+{
+	int i, j, ccount;
+	f4_Cell * thisti;
+	f4_Cell * thneu;
+	char buf[200];
+
+	if (curc >= nc) return;
+
+	if (T_STICK4 != C[curc]->type) return;
+
+	thisti = C[curc];
+	if (NULL != thisti->dadlink)
+		*tmpcel = *(thisti->dadlink);
+
+	// adjust length, curvedness, etc.
+	tmpcel->P.adjust();
+	while (tmpcel->P.len > thisti->P.len)
+	{
+		tmpcel->P.executeModifier('l');
+		out += "l";
+	}
+	while (tmpcel->P.len < thisti->P.len)
+	{
+		tmpcel->P.executeModifier('L');
+		out += "L";
+	}
+	while (tmpcel->P.curv > thisti->P.curv)
+	{
+		tmpcel->P.executeModifier('c');
+		out += "c";
+	}
+	while (tmpcel->P.curv < thisti->P.curv)
+	{
+		tmpcel->P.executeModifier('C');
+		out += "C";
+	}
+	while (thisti->rolling > 0.0f) {
+		rolling_dec(&(thisti->rolling));
+		out += "R";
+	}
+	while (thisti->rolling < 0.0f) {
+		rolling_inc(&(thisti->rolling));
+		out += "r";
+	}
+
+	// output X for this stick
+	out += "X";
+
+	// neurons attached to it
+	for (i = 0; i < nc; i++)
+		if (C[i]->type == T_NEURON4) {
+			if (C[i]->dadlink == thisti) {
+				thneu = C[i];
+				out += "[";
+				// ctrl
+				if (1 == thneu->ctrl) out += "@";
+				if (2 == thneu->ctrl) out += "|";
+				// links
+				for (j = 0; j < thneu->nolink; j++)
+				{
+					if (j) out += ",";
+					if (NULL == thneu->links[j]->from)
+					{
+						// sensory
+						if (1 == thneu->links[j]->t) out += "*";
+						if (2 == thneu->links[j]->t) out += "G";
+						if (3 == thneu->links[j]->t) out += "T";
+						if (4 == thneu->links[j]->t) out += "S";
+					}
+					else {
+						sprintf(buf, "%d", thneu->links[j]->from->name - thneu->name);
+						out += buf;
+					}
+					out += ":";
+					// weight
+					sprintf(buf, "%g", thneu->links[j]->w);
+					out += buf;
+				}
+				out += "]";
+			}
+		}
+
+	// sticks connected to it
+	if (thisti->commacount >= 2)
+		out += "(";
+
+	ccount = 1;
+	for (i = 0; i < nc; i++)
+		if (C[i]->type == T_STICK4)
+			if (C[i]->dadlink == thisti) {
+				while (ccount < (C[i])->anglepos) {
+					ccount++;
+					out += ",";
+				}
+				toF1GenoRec(i, out);
+			}
+	while (ccount < thisti->commacount) {
+		ccount++;
+		out += ",";
+	}
+
+	if (thisti->commacount >= 2)
+		out += ")";
+}
+
+
+
+// to organize a f4 genotype in a tree structure
+
+f4_node::f4_node()
+{
+	name = '?';
+	parent = NULL;
+	child = NULL;
+	child2 = NULL;
+	pos = -1;
+}
+
+f4_node::f4_node(char nname, f4_node * nparent, int npos)
+{
+	name = nname;
+	parent = nparent;
+	child = NULL;
+	child2 = NULL;
+	pos = npos;
+	if (parent) parent->addChild(this);
+}
+
+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) {
+		child = nchi;
+		return 0;
+	}
+	if (NULL == child2) {
+		child2 = nchi;
+		return 0;
+	}
+	return -1;
+}
+
+int f4_node::removeChild(f4_node * nchi)
+{
+	if (nchi == child2) {
+		child2 = NULL;
+		return 0;
+	}
+	if (nchi == child) {
+		child = NULL;
+		return 0;
+	}
+	return -1;
+}
+
+int f4_node::childCount()
+{
+	if (NULL != child) {
+		if (NULL != child2) return 2;
+		else return 1;
+	}
+	else {
+		if (NULL != child2) return 1;
+		else return 0;
+	}
+}
+
+int f4_node::count()
+{
+	int c = 1;
+	if (NULL != child)  c += child->count();
+	if (NULL != child2) c += child2->count();
+	return c;
+}
+
+f4_node * f4_node::ordNode(int n)
+{
+	int n1;
+	if (0 == n) return this;
+	n--;
+	if (NULL != child) {
+		n1 = child->count();
+		if (n < n1) return child->ordNode(n);
+		n -= n1;
+	}
+	if (NULL != child2) {
+		n1 = child2->count();
+		if (n < n1) return child2->ordNode(n);
+		n -= n1;
+	}
+	return NULL;
+}
+
+f4_node * f4_node::randomNode()
+{
+	int n = count();
+	// pick a random node, between 0 and n-1
+	return ordNode(randomN(n));
+}
+
+f4_node * f4_node::randomNodeWithSize(int min, int max)
+{
+	// try random nodes, and accept if size in range
+	// limit to maxlim tries
+	int i, n, maxlim;
+	f4_node * nod = NULL;
+	maxlim = count();
+	for (i = 0; i < maxlim; i++) {
+		nod = randomNode();
+		n = nod->count();
+		if ((n >= min) && (n <= max)) return nod;
+	}
+	// failed, doesn't matter
+	return nod;
+}
+
+void f4_node::sprint(SString & out)
+{
+	char buf2[20];
+	// special case: repetition code
+	if ('#' == name)
+	{
+		out += "#";
+		if (i1 != 1) {
+			sprintf(buf2, "%d", i1);
+			out += buf2;
+		}
+	}
+	else {
+		// special case: neuron link
+		if ('[' == name) {
+			out += "[";
+			if (i1 > 0) {
+				// sensor input
+				if (1 == i1) out += "*";
+				if (2 == i1) out += "G";
+				if (3 == i1) out += "T";
+				if (4 == i1) out += "S";
+			}
+			else {
+				sprintf(buf2, "%ld", l1);
+				out += buf2;
+			}
+			sprintf(buf2, ":%g]", f1);
+			out += buf2;
+		}
+		else if (':' == name) {
+			sprintf(buf2, ":%c%c:", l1 ? '+' : '-', (char)i1);
+			out += buf2;
+		}
+		else {
+			buf2[0] = name;
+			buf2[1] = 0;
+			out += buf2;
+		}
+	}
+	if (NULL != child)     child->sprint(out);
+	// if two children, make sure last char is a '>'
+	if (2 == childCount())
+		if (0 == out[0]) out += ">"; else
+			if ('>' != out[out.len() - 1]) out += ">";
+	if (NULL != child2)    child2->sprint(out);
+	// make sure last char is a '>'
+	if (0 == out[0]) out += ">"; else
+		if ('>' != out[out.len() - 1]) out += ">";
+}
+
+void f4_node::sprintAdj(char *& buf)
+{
+	unsigned int len;
+	// build in a SString, with initial size
+	SString out(strlen(buf) + 2000);
+	out = "";
+
+	sprint(out);
+
+	// very last '>' can be omitted
+	len = out.len();
+	if (len > 1)
+		if ('>' == out[len - 1]) { (out.directWrite())[len - 1] = 0; out.endWrite(); };
+	// copy back to string
+	// if new is longer, reallocate buf
+	if (len + 1 > strlen(buf)) {
+		buf = (char*)realloc(buf, len + 1);
+	}
+	strcpy(buf, (const char*)out);
+}
+
+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) {
+		copy->child = child->duplicate();
+		copy->child->parent = copy;
+	}
+	if (NULL != child2) {
+		copy->child2 = child2->duplicate();
+		copy->child2->parent = copy;
+	}
+	return copy;
+}
+
+
+void f4_node::destroy()
+{
+	// children are destroyed (recursively) through the destructor
+	if (NULL != child2)  delete child2;
+	if (NULL != child)   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, t, res;
+	char tc1, tc2;
+	long relfrom;
+	double w;
+	unsigned gpos, oldpos;
+	f4_node * node1, *par;
+
+	gpos = pos0;
+	par = parent;
+	if (gpos >= strlen(genot)) return 1;
+	while (gpos < strlen(genot)) {
+		//DB( printf(" processing '%c' %d %s\n", genot[gpos], gpos, genot); )
+		switch (genot[gpos]) {
+		case '<':
+			// cell division!
+			//DB( printf("  div! %d\n", name); )
+
+			// find out genotype start for child
+			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
+
+			node1 = new f4_node('<', par, gpos);
+			par = node1;
+			res = f4_processrec(genot, gpos + 1, par);
+			if (res) return res;
+			if (gpos + j + 2 < strlen(genot)) {
+				res = f4_processrec(genot, gpos + j + 2, par);
+				if (res) return res;
+			}
+			else {  // ran out
+				node1 = new f4_node('>', par, strlen(genot) - 1);
+				par = node1;
+			}
+			// adjustments
+			gpos++;
+			return 0;  // OK
+
+		case '>':
+			node1 = new f4_node('>', par, gpos);
+			par = node1;
+			gpos = strlen(genot);
+			return 0;  // OK
+
+		case '#':
+			// repetition marker, 1 by default
+			if (sscanf(genot + gpos, "#%d", &i) != 1) i = 1;
+			// find out genotype start for continuation
+			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), '>');
+			// skip number
+			oldpos = gpos;
+			gpos++;
+			while ((genot[gpos] >= '0') && (genot[gpos] <= '9')) gpos++;
+			node1 = new f4_node('#', par, oldpos);
+			node1->i1 = i;
+			par = node1;
+			res = f4_processrec(genot, gpos, node1);
+			if (res) return res;
+			if (oldpos + j + 2 < strlen(genot)) {
+				res = f4_processrec(genot, oldpos + j + 2, node1);
+				if (res) return res;
+			}
+			else {  // ran out
+				node1 = new f4_node('>', par, strlen(genot) - 1);
+			}
+			return 0;  // OK
+
+			// 'simple' nodes:
+		case ',':
+		case 'l':  case 'L':
+		case 'c':  case 'C':
+		case 'q':  case 'Q':
+		case 'r':  case 'R':
+		case 'X':  case 'N':
+		case '@':  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);
+			par = node1;
+			gpos++;
+			break;
+
+		case '[':
+			// link to neuron
+			// input (%d, '*', 'G', 'T', 'S')
+			t = -1;
+			if (sscanf(genot + gpos, "[%ld:%lf]", &relfrom, &w) == 2) t = 0;
+			else if (sscanf(genot + gpos, "[*:%lf]", &w) == 1) t = 1;
+			else if (sscanf(genot + gpos, "[G:%lf]", &w) == 1) t = 2;
+			else if (sscanf(genot + gpos, "[T:%lf]", &w) == 1) t = 3;
+			else if (sscanf(genot + gpos, "[S:%lf]", &w) == 1) t = 4;
+			// error: no correct format
+			if (t < 0) return gpos + 1 + 1;
+			node1 = new f4_node('[', par, gpos);
+			node1->i1 = t;
+			node1->l1 = relfrom;
+			node1->f1 = w;
+			par = node1;
+			j = scanrec(genot + gpos + 1, strlen(genot + gpos + 1), ']');
+			gpos += j + 2;
+			break;
+
+		case ':':
+			// neuron parameter  +! -! += -= +/ or -/
+			if (sscanf(genot + gpos, ":%c%c:", &tc1, &tc2) != 2)
+				// 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 ' ':
+		case '\n':
+		case '\t':
+			// whitespace: ignore
+			//node1 = new f4_node(' ', par, gpos );
+			//par = node1;
+			gpos++;
+			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;
+		}
+	}
+	// should end with a '>'
+	if (par)
+		if ('>' != par->name) {
+			node1 = new f4_node('>', par, strlen(genot) - 1);
+			par = node1;
+		}
+	return 0;  // OK
+}
+
+
+f4_node * f4_processtree(const char * geno)
+{
+	f4_node * root;
+	int res;
+	root = new f4_node();
+	res = f4_processrec(geno, 0, root);
+	if (res) return NULL;
+	//DB( printf("test f4  "); )
+	DB(
+		if (root->child) {
+			char * buf = (char*)malloc(300);
+			DB(printf("(%d) ", root->child->count());)
+				buf[0] = 0;
+			root->child->sprintAdj(buf);
+			DB(printf("%s\n", buf);)
+				free(buf);
+		}
+	)
+		return root->child;
+}
Index: cpp/frams/genetics/f4/f4_general.h
===================================================================
--- cpp/frams/genetics/f4/f4_general.h	(revision 196)
+++ cpp/frams/genetics/f4/f4_general.h	(revision 196)
@@ -0,0 +1,232 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+
+#ifndef _F4_GENERAL_H_
+#define _F4_GENERAL_H_
+
+//#include "f4_orientmat.h"
+#include <frams/util/3d.h>
+#include <frams/util/sstring.h>
+#include <frams/util/multirange.h>
+
+#ifdef DMALLOC
+#include <dmalloc.h>
+#endif
+
+
+class f4_Props
+{
+public:
+	// fill with default values
+	f4_Props();
+	// must sum to 1
+	void normalizeBiol4();
+	void executeModifier(char modif);
+	void adjust();
+
+	double len;      // length (dlug)
+	double curv;     // curvedness (skr)
+	double mass;
+	double friction;
+	double ruch;
+	double assim;
+	double odpor;
+	double ingest;  // ingestion (wchl)
+	double twist;
+	double energ;
+};
+
+extern f4_Props stdProps;
+
+
+// rolling (one-time)
+void rolling_dec(double * v);
+void rolling_inc(double * v);
+
+
+class f4_node;   // later
+class f4_Cell;   // later
+class f4_Cells;  // later
+
+
+// cell types
+#define T_UNDIFF4 40
+#define T_STICK4  41
+#define T_NEURON4 42
+
+int scanrec(const char * s, unsigned int slen, char stopchar);
+
+
+class f4_CellLink;
+#define MAXINPUTS 100
+
+// an abstract cell type, extension of part/stick -- for developmental encoding
+class f4_Cell
+{
+public:
+	class repeat_ptr
+	{
+	public:
+		repeat_ptr() : node(NULL), count(-1) { };
+		repeat_ptr(f4_node * a, int b) : node(a), count(b) { };
+		inline void null() { node = NULL; count = -1; };
+		inline bool isNull() const { return ((node == NULL) || (count <= 0)); };
+		inline void dec() { count--; };
+		f4_node *  node;  // ptr to repetition code
+		char       count; // repetition counter
+	};
+
+	class repeat_stack  // a stack of repet_ptr's
+	{
+	public:
+		repeat_stack() { top = 0; };
+		inline void null() { top = 0; };
+		inline void push(repeat_ptr A) { if (top >= stackSize) return; ptr[top] = A; top++; };
+		inline void pop() { if (top > 0) top--; };
+		inline repeat_ptr * first() { return &(ptr[top - (top > 0)]); };
+		static const int stackSize = 4;  // max 4 nested levels
+		repeat_ptr ptr[stackSize];
+		short int top;  // top of the stack
+	};
+
+	f4_Cell(int nname,
+		f4_Cell * ndad, int nangle, f4_Props newP);
+	f4_Cell(f4_Cells * nO, int nname, f4_node * ngeno, f4_node * ngcur,
+		f4_Cell * ndad, int nangle, f4_Props newP);
+	~f4_Cell();
+
+	int onestep();	// execute one simulation step (till a division)
+
+	int   addlink(f4_Cell * nfrom, double nw, long nt);
+	void  adjustRec();
+
+	int        name;     // name (number)
+	int        type;     // type
+	f4_Cell *  dadlink;
+	f4_Cells * org;	// uplink to organism
+
+	f4_node *  genot;	  // genotype
+	f4_node *  gcur;        // current genotype execution pointer
+	int        active;      // whether development is still active
+	repeat_stack repeat;
+	int        recProcessedFlag;  // used during recursive traverse
+	// remember the genotype codes affecting this cell so far
+	MultiRange genoRange;
+
+	f4_Props     P;             // properties
+	int          anglepos;      // number of position within dad's children (,)
+	int          childcount;    // number of children
+	int          commacount;    // number of postitions at lastend (>=childcount)
+	double       rolling;       // rolling angle ('R') (around x)
+	double       xrot;
+	double       zrot;          // horizontal rotation angle due to
+	// branching (around z)
+	//Pt3D         firstend;      // coord.s of first end (connects to parent)
+	//Pt3D         lastend;       // last end
+	//f4_OrientMat OM;
+	double       mz;            // freedom in z
+	long         p2_refno;   // number of last end part object, used in f0
+	long         joint_refno;   // number of the joint object, used in f0
+	long         neuro_refno;   // number of the neuro object, used in f0
+
+	long         ctrl;  // neuron type
+	double       state;
+	double       inertia;
+	double       force;
+	double       sigmo;
+	f4_CellLink* links[MAXINPUTS];
+	int          nolink;
+};
+
+
+// an input link to a neuron
+class f4_CellLink
+{
+public:
+	f4_CellLink(f4_Cell * nfrom, double nw, long nt);
+	f4_Cell *    from;
+	// type: 0: input, 1 '*', 2 'G', 3 'T', 4 'S'
+	long         t;
+	double       w;
+};
+
+
+// a collection of cells, like Organism, for developmental encoding
+#define MAX4CELLS 100
+class f4_Cells
+{
+public:
+	f4_Cells(f4_node * genome, int nrepair);
+	f4_Cells(SString &genome, int nrepair);
+	~f4_Cells();
+	void addCell(f4_Cell * newcell);
+	void toF1Geno(SString &out);       // output to f1 format, approximation
+	int  onestep();       // simulate all parts for one step
+	int  simulate();      // simulate development, return error (0 for ok)
+	// for error reporting / genotype fixing
+	int  geterror() { return error; };
+	int  geterrorpos() { return errorpos; };
+	void setError(int nerrpos);
+	void setRepairRemove(int nerrpos, f4_node * rem);
+	int  setRepairInsert(int nerrpos, f4_node * parent, f4_node * insert);
+	void repairGeno(f4_node * geno, int whichchild);
+
+	// the cells
+	f4_Cell * C[MAX4CELLS];
+	int       nc;
+
+private:
+	// for error reporting / genotype fixing
+	int repair;
+	int error;
+	int errorpos;
+	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
+};
+
+
+/**
+ * Class to organize a f4 genotype in a tree structure.
+ */
+class f4_node
+{
+public:
+	char      name;	// one-letter 'name'
+	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 string
+	int       i1;		// internal int  parameter1
+	long      l1;		// internal long parameter1
+	double    f1;		// internal double parameter1
+
+	f4_node();
+	f4_node(char nname, f4_node * nparent, int npos);
+	~f4_node();
+	int       addChild(f4_node * nchi);
+	int       removeChild(f4_node * nchi);
+	int       childCount();	// return no of children, 0, 1, or 2
+	int       count();	// return no of nodes (recursive)
+	f4_node * ordNode(int n);	// returns the nth subnode (0-)
+	f4_node * randomNode();	// returns a random subnode
+	f4_node * randomNodeWithSize(int min, int max);	// returns a random subnode with given size
+	void      sprintAdj(char *& buf);	// print recursively
+	f4_node * duplicate();         // create duplicate copy. recursive.
+	void      destroy();	// release memory. recursive.
+private:
+	void     sprint(SString & out);	// print recursively
+};
+
+// convert f4 geno string to tree structure (internal)
+f4_node * f4_processtree(const char * geno);
+int f4_processrec(const char * genot, unsigned pos0, f4_node * parent);
+
+
+#endif
Index: cpp/frams/genetics/f4/oper_f4.cpp
===================================================================
--- cpp/frams/genetics/f4/oper_f4.cpp	(revision 196)
+++ cpp/frams/genetics/f4/oper_f4.cpp	(revision 196)
@@ -0,0 +1,627 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+// Copyright (C) since 2001 Maciej Komosinski
+
+#include "oper_f4.h"
+#include <frams/util/sstring.h>
+#include <common/framsg.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "common/nonstd_math.h"
+#include <string.h>
+
+
+#define FIELDSTRUCT Geno_f4
+
+static ParamEntry GENO4param_tab[] =
+{
+	{ "Genetics: f4", 1, F4_COUNT + F4_ADD_COUNT, },
+	{ "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", },
+	{ 0, },
+};
+
+#undef FIELDSTRUCT
+
+
+Geno_f4::Geno_f4()
+{
+	supported_format = '4';
+	par.setParamTab(GENO4param_tab);
+	par.select(this);
+	par.setDefault();
+
+	mutation_method_names = new char*[F4_COUNT + F4_ADD_COUNT - 1];
+	int index = 0;
+	mutation_method_names[index++] = "added division";
+	mutation_method_names[index++] = "added neural connection";
+	mutation_method_names[index++] = "added neuron property";
+	mutation_method_names[index++] = "added repetition gene";
+	mutation_method_names[index++] = "added a simple node";
+	mutation_method_names[index++] = "deleted a node";
+	mutation_method_names[index++] = "modified a node";
+	if (index != F4_COUNT + F4_ADD_COUNT - 1) FramMessage("Geno_f4", "Constructor", "Mutation names init error", 3);
+}
+
+int Geno_f4::ValidateRec(f4_node * geno, int retrycount) const
+{
+	// ! the genotype is geno->child (not geno) !
+	// build from it with repair on
+
+	f4_Cells cells(geno->child, 1);
+	cells.simulate();  //we should simulate?!
+
+	// errors not fixed:
+	if (GENOPER_OPFAIL == cells.geterror())
+	{
+		if (cells.geterrorpos() >= 0) return 1 + cells.geterrorpos();
+		return GENOPER_OPFAIL;
+	}
+	// errors can be fixed
+	if (GENOPER_REPAIR == cells.geterror())
+	{
+		cells.repairGeno(geno, 1);
+		// note: geno might have been fixed
+		// check again
+		int res2 = GENOPER_OK;
+		if (retrycount > 0)
+			res2 = ValidateRec(geno, retrycount - 1);
+
+		if (res2 == GENOPER_OK) return GENOPER_REPAIR;
+		return res2;
+	}
+	// no errors:
+	return GENOPER_OK;
+}
+
+
+int Geno_f4::validate(char *& geno)
+{
+	// convert geno to tree, then try to validate 20 times
+	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
+	{
+		geno[0] = 0;
+		root.child->sprintAdj(geno);
+	}
+	return GENOPER_OK;
+}
+
+
+int Geno_f4::checkValidity(const char * geno)
+{
+	f4_node root;
+	int res = f4_processrec(geno, 0, &root);
+	if (res) return res;  // errorpos, >0
+	if (root.childCount() != 1) return 1; //earlier: GENOPER_OPFAIL
+	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();
+		else return 1; //earlier: GENOPER_OPFAIL;
+	}
+	else return GENOPER_OK;
+}
+
+
+int Geno_f4::MutateOne(f4_node *& g, int &method) const
+{
+	// ! the genotype is g->child (not g) !
+
+	// codes that can be changed (apart being added/deleted)
+#define MUT_CHAN_CODES "<[#"
+#define ADD_SIMPLE_CODES ",XlLcCrRaAiIsSmMfFwWeEN@|"
+#define REP_MAXCOUNT 19
+
+	f4_node * n1, *n2, *n3, *n4, *n5;
+
+	// do the mutation
+	// pick a random node
+	n1 = g->child->randomNode();
+	//DB( printf("%c\n", n1->name); )
+
+	switch (roulette(prob, F4_COUNT))
+	{
+	case F4_ADD:
+	{
+		// add a node
+		switch (method = roulette(probadd, F4_ADD_COUNT))
+		{
+		case F4_ADD_DIV:
+		{
+			// add division ('<')
+			n3 = n1->parent;
+			n3->removeChild(n1);
+			n2 = new f4_node('<', n3, n3->pos);
+			n2->addChild(n1);
+			// new cell is stick or neuron
+			// "X>" or "N>"
+			double pr = rnd01;
+			pr -= 0.5;
+			if (pr < 0) n3 = new f4_node('X', n2, n2->pos);
+			else
+			{
+				pr -= 0.5;
+				if (pr < 0)
+				{
+					// if neuron, make muscle and add a link
+					n3 = new f4_node('N', n2, n2->pos);
+					if (randomN(2) == 0)
+						n4 = new f4_node('|', n3, n2->pos);
+					else
+						n4 = new f4_node('@', n3, n2->pos);
+					n5 = new f4_node('[', n4, n2->pos);
+					linkNodeMakeRandom(n5);
+				}
+			}
+			new f4_node('>', n3, n3->pos);
+			n1->parent = n2;
+			// now with 50% chance swap children
+			if (randomN(2) == 0)
+			{
+				n3 = n2->child;
+				n2->child = n2->child2;
+				n2->child2 = n3;
+			}
+		}
+			break;
+		case F4_ADD_CONN:
+		{
+			// add link
+			n1->parent->removeChild(n1);
+			n2 = new f4_node('[', n1->parent, n1->parent->pos);
+			linkNodeMakeRandom(n2);
+			n2->addChild(n1);
+			n1->parent = n2;
+		}
+			break;
+		case F4_ADD_NEUPAR:
+		{
+			// add neuron modifier
+			n1->parent->removeChild(n1);
+			n2 = new f4_node(':', n1->parent, n1->parent->pos);
+			nparNodeMakeRandom(n2);
+			n2->addChild(n1);
+			n1->parent = n2;
+		}
+			break;
+		case F4_ADD_REP:
+		{
+			// add repetition ('#')
+			// repeated code (left child) is the original, right child is empty, count is 2
+			n3 = n1->parent;
+			n3->removeChild(n1);
+			n2 = new f4_node('#', n3, n3->pos);
+			n2->i1 = 2;
+			n2->addChild(n1);
+			new f4_node('>', n2, n2->pos);
+			n1->parent = n2;
+		}
+			break;
+		case F4_ADD_SIMP:
+		{
+			// add simple node
+			// choose a simple node from ADD_SIMPLE_CODES
+			n1->parent->removeChild(n1);
+			n2 = new f4_node(ADD_SIMPLE_CODES[randomN(strlen(ADD_SIMPLE_CODES))], n1->parent, n1->parent->pos);
+			n2->addChild(n1);
+			n1->parent = n2;
+		}
+			break;
+		}
+	}
+		break;
+
+	case F4_DEL:
+	{
+		method = F4_ADD_COUNT - 1 + F4_DEL;
+		// delete a node
+		// must pick a node with parent, and at least one child
+		// already picked a node, but repeat may be needed
+		for (int i = 0; i < 10; i++) {
+			if ((NULL != n1->parent) && (g != n1->parent))
+				if (NULL != n1->child)
+					break;
+			// try a new one
+			n1 = g->child->randomNode();
+		}
+		if ((NULL != n1->parent) && (g != n1->parent))
+		{
+			switch (n1->childCount())
+			{
+			case 0: break;
+			case 1:  // one child
+			{
+				n2 = n1->parent;
+				n2->removeChild(n1);
+				if (NULL != n1->child) {
+					n1->child->parent = n2;
+					n2->addChild(n1->child);
+					n1->child = NULL;
+				}
+				if (NULL != n1->child2) {
+					n1->child2->parent = n2;
+					n2->addChild(n1->child2);
+					n1->child2 = NULL;
+				}
+				// destroy n1
+				n1->parent = NULL;
+				delete n1;
+			}
+				break;
+
+			case 2:  // two children
+			{
+				// two children
+				n2 = n1->parent;
+				n2->removeChild(n1);
+				// n1 has two children. pick one randomly 50-50, destroy other
+				if (randomN(2) == 0)
+				{
+					n1->child->parent = n2;
+					n2->addChild(n1->child);
+					n1->child = NULL;
+					n1->child2->parent = NULL;
+				}
+				else
+				{
+					n1->child2->parent = n2;
+					n2->addChild(n1->child2);
+					n1->child2 = NULL;
+					n1->child->parent = NULL;
+				}
+				// destroy n1
+				n1->parent = NULL;
+				delete n1;
+			}
+				break;
+			}
+		}
+		else return GENOPER_OPFAIL;
+	}
+		break;
+	case F4_MOD:
+	{
+		method = F4_ADD_COUNT - 1 + F4_MOD;
+		// change a node
+		// the only nodes that are modifiable are MUT_CHAN_CODES
+		// try to get a modifiable node
+		// already picked a node, but repeat may be needed
+		int i = 0;
+		while (1)
+		{
+			if (strchr(MUT_CHAN_CODES, n1->name)) break;
+			// try a new one
+			n1 = g->child->randomNode();
+			i++;
+			if (i >= 20) return GENOPER_OPFAIL;
+		}
+		switch (n1->name) {
+		case '<':
+			// swap children
+			n2 = n1->child; n1->child = n1->child2; n1->child2 = n2;
+			break;
+		case '[':
+			linkNodeChangeRandom(n1);
+			break;
+		case '#':
+			repeatNodeChangeRandom(n1);
+			break;
+		}
+	}
+		break;
+
+	default: //no mutations allowed?
+		return GENOPER_OPFAIL;
+	}
+
+	return GENOPER_OK;
+}
+
+// make a random [ node
+void Geno_f4::linkNodeMakeRandom(f4_node * nn) const
+{
+	int i;
+	float prob1;
+
+	i = 0;
+	// 35% chance one of *GTS
+	prob1 = rnd01;
+	prob1 -= 0.35f;
+	if (prob1 < 0)
+	{
+		// '*', 'G', 'T', or 'S', 1/4 chance each
+		i = 1 + (int)(3.999f * rnd01);
+	}
+	nn->i1 = i;
+	nn->l1 = 0;
+	if (0 == i) {
+		// relative input link
+		nn->l1 = (int)(4.0f * (rnd01 - 0.5f));
+	}
+	// weight
+	nn->f1 = 10.0f * (rnd01 - 0.5f);
+}
+
+// change a [ node
+void Geno_f4::linkNodeChangeRandom(f4_node * nn) const      //rewritten by M.K. - should work as before (not tested)
+{
+	int i;
+	float prob2;
+
+	double probs[3] = { 0.1, 0.3, 0.6 };
+	// 10% change type
+	// 30% change link
+	// 60% change weight
+
+	switch (roulette(probs, 3))
+	{
+	case 0: // change type
+		i = 0;
+		// * G, 10% chance each
+		prob2 = rnd01 - 0.10f;
+		if (prob2 < 0) i = 1; else { prob2 -= 0.10f; if (prob2 < 0) i = 2; }
+		nn->i1 = i;
+		break;
+	case 1: // change link
+		if (0 == nn->i1) // relative input link
+			nn->l1 += (int)(2.0f * (rnd01 - 0.5f));
+		break;
+	case 2: // change weight
+		nn->f1 += 1.0f * (rnd01 - 0.5f);
+		break;
+	}
+}
+
+// make a random : node
+void Geno_f4::nparNodeMakeRandom(f4_node * nn) const
+{
+	int sign = (int)(2.0f * rnd01);
+	int param = (int)(3.0f * rnd01);
+	if (param > 2) param = 2;
+	nn->l1 = sign;
+	nn->i1 = "!=/"[param];
+}
+
+// change a repeat # node
+void Geno_f4::repeatNodeChangeRandom(f4_node * nn) const
+{
+	int count;
+	float prob1;
+
+	// change count
+	count = nn->i1;
+	prob1 = rnd01;
+	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
+// 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++)
+	{
+		gcopy = g->duplicate();
+
+		res = MutateOne(gcopy, method);
+
+		if (GENOPER_OK != res)
+		{
+			// mutation failed, try again
+			delete gcopy;
+			continue;  // for
+		}
+		// try to validate it
+		res = ValidateRec(gcopy, 10);
+		// accept if it is OK, or was repaired
+		if (GENOPER_OK == res)
+			//(GENOPER_REPAIR == res)
+		{
+			// destroy the original one
+			g->destroy();
+			// make it the new one
+			*g = *gcopy;
+			gcopy->child = NULL;
+			gcopy->child2 = NULL;
+			delete gcopy;
+			res = GENOPER_OK;
+			goto retm1v;
+		}
+		delete gcopy;
+	}
+	// attempts failed
+	res = GENOPER_OPFAIL;
+retm1v:
+	return res;
+}
+
+
+int Geno_f4::mutate(char *& g, float & chg, int &method)
+{
+	f4_node *root = new f4_node;
+	if (f4_processrec(g, 0, root) || root->childCount() != 1)
+	{
+		delete root; return GENOPER_OPFAIL;
+	} // could not convert or bad: fail
+	// mutate one node, set chg as this percent
+	chg = 1.0 / float(root->child->count());
+	if (MutateOneValid(root, method) != GENOPER_OK)
+	{
+		delete root; return GENOPER_OPFAIL;
+	}
+	// OK, convert back to string
+	g[0] = 0;
+	root->child->sprintAdj(g);
+	delete root;
+	return GENOPER_OK;
+}
+
+
+/*
+int Geno_f4::MutateMany(char *& g, float & chg)
+// check if original is valid, then
+// make a number of mutations
+{
+int res, n, i;
+int totNodes = 0;
+int maxToMut = 0;
+
+// convert to tree
+f4_node * root;
+root = new f4_node();
+res = f4_processrec(g, 0, root);
+if (res) {
+// could not convert, fail
+goto retm;
+}
+if (1 != root->childCount()) {
+res = GENOPER_OPFAIL;
+goto retm;
+}
+
+// check if original is valid
+res = ValidateRec( root, 20 );
+// might have been repaired!
+if (GENOPER_REPAIR==res) {
+res = GENOPER_OK;
+}
+if (GENOPER_OK != res) {
+goto retm;
+}
+
+// decide number of nodes to mutate
+// decide maximum number of nodes to mutate: 0.25*nodes, min 2
+totNodes = root->child->count();
+maxToMut = (int)( 0.25f * totNodes);
+if (maxToMut<2) maxToMut=2;
+if (maxToMut>totNodes) maxToMut=totNodes;
+
+// decide number of nodes to mutate
+n = (int)( 0.5f + rnd01 * maxToMut );
+if (n<1) n=1;
+if (n>totNodes) n=totNodes;
+// set chg as this percent
+chg = ((float)n) / ((float)totNodes);
+for (i=0; i<n; i++)
+{
+res = MutateOneValid(root);
+if (GENOPER_OK != res)
+{
+res = GENOPER_OPFAIL;
+goto retm;
+}
+}
+// OK, convert back to string
+g[0]=0;
+root->child->sprintAdj(g);
+retm:
+delete root;
+return res;
+}
+*/
+
+
+int Geno_f4::CrossOverOne(f4_node * g1, f4_node * g2, float chg) const
+{
+	// ! the genotypes are g1->child and g2->child (not g1 g2) !
+	// single offspring in g1
+	int smin, smax;
+	float size;
+	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);
+	// get a random node with desired size
+	n1 = g1->child->randomNodeWithSize(smin, smax);
+
+	// determine desired size
+	size = (1 - chg) * (float)g2->count();
+	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);
+
+	// exchange the two nodes:
+	n1p = n1->parent;
+	n2p = n2->parent;
+	n1p->removeChild(n1);
+	n1p->addChild(n2);
+	n2p->removeChild(n2);
+	n2p->addChild(n1);
+	n1->parent = n2p;
+	n2->parent = n1p;
+
+	return GENOPER_OK;
+}
+
+int Geno_f4::crossOver(char *&g1, char *&g2, float &chg1, float &chg2)
+{
+	f4_node root1, root2, *copy1, *copy2;
+
+	// convert genotype strings into tree structures
+	if (f4_processrec(g1, 0, &root1) || (root1.childCount() != 1)) return GENOPER_OPFAIL;
+	if (f4_processrec(g2, 0, &root2) || (root2.childCount() != 1)) return GENOPER_OPFAIL;
+
+	// decide amounts of crossover, 0.25-0.75
+	// adam: seems 0.1-0.9 -- MacKo
+	chg1 = 0.1f + 0.8f*rnd01;
+	chg2 = 0.1f + 0.8f*rnd01;
+
+	copy1 = root1.duplicate();
+	if (CrossOverOne(copy1, &root2, chg1) != GENOPER_OK) { delete copy1; copy1 = NULL; }
+	copy2 = root2.duplicate();
+	if (CrossOverOne(copy2, &root1, chg2) != GENOPER_OK) { delete copy2; copy2 = NULL; }
+
+	g1[0] = 0;
+	g2[0] = 0;
+	if (copy1) { copy1->child->sprintAdj(g1); delete copy1; }
+	if (copy2) { copy2->child->sprintAdj(g2); delete copy2; }
+	if (g1[0] || g2[0]) return GENOPER_OK; else return GENOPER_OPFAIL;
+}
+
+
+
+unsigned long Geno_f4::style(const char *g, int pos)
+{
+	char ch = g[pos];
+	// style categories
+#define STYL4CAT_MODIFIC "LlRrCcQqAaIiSsMmFfWwEe,"
+#define STYL4CAT_NEUMOD "[]|@*GTS:+-/!="
+#define STYL4CAT_DIGIT "0123456789."
+#define STYL4CAT_REST "XN<># "
+	if (!strchr(STYL4CAT_MODIFIC STYL4CAT_NEUMOD STYL4CAT_DIGIT STYL4CAT_REST, ch))
+		return GENSTYLE_CS(0, GENSTYLE_INVALID);
+	unsigned long style = GENSTYLE_CS(0, GENSTYLE_STRIKEOUT); //default, should be changed below
+	if (strchr("X ", ch))              style = GENSTYLE_CS(0, GENSTYLE_NONE);
+	if (strchr("N", ch))               style = GENSTYLE_RGBS(0, 200, 0, GENSTYLE_NONE);
+	if (strchr("<", ch))               style = GENSTYLE_RGBS(0, 0, 200, GENSTYLE_BOLD);
+	if (strchr(">", ch))               style = GENSTYLE_RGBS(0, 0, 100, GENSTYLE_NONE);
+	if (strchr(STYL4CAT_DIGIT, ch))     style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
+	if (strchr(STYL4CAT_MODIFIC, ch))   style = GENSTYLE_RGBS(100, 100, 100, GENSTYLE_NONE);
+	if (strchr(STYL4CAT_NEUMOD, ch))    style = GENSTYLE_RGBS(0, 150, 0, GENSTYLE_NONE);
+	return style;
+}
Index: cpp/frams/genetics/f4/oper_f4.h
===================================================================
--- cpp/frams/genetics/f4/oper_f4.h	(revision 196)
+++ cpp/frams/genetics/f4/oper_f4.h	(revision 196)
@@ -0,0 +1,61 @@
+// This file is a part of the Framsticks GDK.
+// Copyright (C) 2002-2014  Maciej Komosinski and Szymon Ulatowski.  See LICENSE.txt for details.
+// Refer to http://www.framsticks.com/ for further information.
+
+// Copyright (C) 1999,2000  Adam Rotaru-Varga (adam_rotaru@yahoo.com), GNU LGPL
+// Copyright (C) since 2001 Maciej Komosinski
+
+#ifndef _GENO_F4_H_
+#define _GENO_F4_H_
+
+#include <stdio.h>
+#include "f4_general.h"
+#include "common/nonstd.h"
+#include "../oper_fx.h"
+#include <frams/param/param.h>
+
+
+#define F4_ADD           0
+#define F4_DEL           1
+#define F4_MOD           2
+#define F4_COUNT         3
+
+#define F4_ADD_DIV       0
+#define F4_ADD_CONN      1
+#define F4_ADD_NEUPAR    2
+#define F4_ADD_REP       3
+#define F4_ADD_SIMP      4
+#define F4_ADD_COUNT     5
+
+
+class Geno_f4 : public GenoOperators
+{
+public:
+	Geno_f4();
+	int validate(char *&);
+	int checkValidity(const char *);
+	int mutate(char *& g, float & chg, int &method);
+	int crossOver(char *&g1, char *&g2, float& chg1, float& chg2);
+	const char* getSimplest() { return "X"; }
+	unsigned long style(const char *g, int pos);
+
+	// mutation probabilities
+	double prob[F4_COUNT], probadd[F4_ADD_COUNT];
+
+protected:
+	/* int MutateMany(char *& g, float & chg); // not used anymore */
+	int  ValidateRec(f4_node * geno, int retrycount) const;
+	int  MutateOne(f4_node *& g, int &method) const;
+	void linkNodeMakeRandom(f4_node * nn) const;
+	void linkNodeChangeRandom(f4_node * nn) const;
+	void nparNodeMakeRandom(f4_node * nn) const;
+	void repeatNodeChangeRandom(f4_node * nn) const;
+	int  MutateOneValid(f4_node * &g, int &method) const;
+	int  CrossOverOne(f4_node *g1, f4_node *g2, float chg) const;
+	// returns GENOPER_OK or GENOPER_OPFAIL
+	// chg: fraction of parent1 genes in child (in g1) (parent2 has the rest)
+};
+
+
+#endif
+
Index: cpp/frams/genetics/gen-config-GDK.h
===================================================================
--- cpp/frams/genetics/gen-config-GDK.h	(revision 194)
+++ cpp/frams/genetics/gen-config-GDK.h	(revision 196)
@@ -15,5 +15,5 @@
 //#define USE_GENCONV_f21
 //#define USE_GENCONV_f30
-//#define USE_GENCONV_f40
+#define USE_GENCONV_f40
 //#define USE_GENCONV_f41_TEST
 //#define USE_GENCONV_f50
@@ -25,5 +25,5 @@
 //#define USE_GENMAN_f2
 //#define USE_GENMAN_f3
-//#define USE_GENMAN_f4
+#define USE_GENMAN_f4
 //#define USE_GENMAN_f5
 #define USE_GENMAN_f9
