Index: cpp/frams/genetics/geneprops.cpp
===================================================================
--- cpp/frams/genetics/geneprops.cpp	(revision 1241)
+++ cpp/frams/genetics/geneprops.cpp	(revision 1242)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2021  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -49,44 +49,44 @@
 }
 
-int GeneProps::executeModifier(char modif)
+int GeneProps::executeModifier_Legacy(char modif)
 {
 	switch (modif)
 	{
 #ifdef v1f1COMPATIBLE
-	case 'L': length += (3.0 - length)*0.3;
+	case 'L': length += (3.0 - length) * 0.3;
 		length = std::min(length, Model::getMaxJoint().d.x); break;
 #else
-	case 'L': length += (2.0 - length)*0.3; //2.0 is currently Model::getMaxJoint().d.x so min() does not limit the range
+	case 'L': length += (2.0 - length) * 0.3; //2.0 is currently Model::getMaxJoint().d.x so min() does not limit the range
 		length = std::min(length, Model::getMaxJoint().d.x); break;
 #endif
-	case 'l': length += (0.33 - length)*0.3;
+	case 'l': length += (0.33 - length) * 0.3;
 		length = std::max(length, Model::getMinJoint().d.x); break;
 
-	case 'W': weight += (2.0 - weight)*0.3;  break;
-	case 'w': weight += (0.5 - weight)*0.3;  break;
-	case 'F': friction += (4 - friction)*0.2;  break;
-	case 'f': friction -= friction*0.2;  break;
+	case 'W': weight += (2.0 - weight) * 0.3; break;
+	case 'w': weight += (0.5 - weight) * 0.3; break;
+	case 'F': friction += (4 - friction) * 0.2; break;
+	case 'f': friction -= friction * 0.2; break;
 	case 'C': curvedness += (2.0 - curvedness) * 0.25; break;
 	case 'c': curvedness += (-2.0 - curvedness) * 0.25; break;
-	case 'Q': twist += (M_PI_2 - twist)*0.3; break;
-	case 'q': twist += (-M_PI_2 - twist)*0.3; break;
-	case 'E': energy += (10.0 - energy)*0.1;  break;
-	case 'e': energy -= energy*0.1;	 break;
-
-	case 'A': assimilation += (1 - assimilation)*0.8;	normalizeBiol4(); break;
-	case 'a': assimilation -= assimilation*0.4;	normalizeBiol4(); break;
-	case 'I': ingestion += (1 - ingestion)*0.8;	normalizeBiol4(); break;
-	case 'i': ingestion -= ingestion * 0.4;	normalizeBiol4(); break;
-	case 'S': stamina += (1 - stamina)*0.8;	normalizeBiol4(); break;
-	case 's': stamina -= stamina*0.4;	normalizeBiol4(); break;
-	case 'M': muscle_power += (1 - muscle_power)*0.8;	normalizeBiol4(); break;
-	case 'm': muscle_power -= muscle_power*0.4;	normalizeBiol4(); break;
-
-	case 'D': cred += (1.0 - cred)*0.25;  break;
-	case 'd': cred += (0.0 - cred)*0.25;  break;
-	case 'G': cgreen += (1.0 - cgreen)*0.25;  break;
-	case 'g': cgreen += (0.0 - cgreen)*0.25;  break;
-	case 'B': cblue += (1.0 - cblue)*0.25;  break;
-	case 'b': cblue += (0.0 - cblue)*0.25;  break;
+	case 'Q': twist += (M_PI_2 - twist) * 0.3; break;
+	case 'q': twist += (-M_PI_2 - twist) * 0.3; break;
+	case 'E': energy += (10.0 - energy) * 0.1; break;
+	case 'e': energy -= energy * 0.1;  break;
+
+	case 'A': assimilation += (1 - assimilation) * 0.8; normalizeBiol4(); break;
+	case 'a': assimilation -= assimilation * 0.4; normalizeBiol4(); break;
+	case 'I': ingestion += (1 - ingestion) * 0.8; normalizeBiol4(); break;
+	case 'i': ingestion -= ingestion * 0.4; normalizeBiol4(); break;
+	case 'S': stamina += (1 - stamina) * 0.8; normalizeBiol4(); break;
+	case 's': stamina -= stamina * 0.4; normalizeBiol4(); break;
+	case 'M': muscle_power += (1 - muscle_power) * 0.8; normalizeBiol4(); break;
+	case 'm': muscle_power -= muscle_power * 0.4; normalizeBiol4(); break;
+
+	case 'D': cred += (1.0 - cred) * 0.25; break;
+	case 'd': cred += (0.0 - cred) * 0.25; break;
+	case 'G': cgreen += (1.0 - cgreen) * 0.25; break;
+	case 'g': cgreen += (0.0 - cgreen) * 0.25; break;
+	case 'B': cblue += (1.0 - cblue) * 0.25; break;
+	case 'b': cblue += (0.0 - cblue) * 0.25; break;
 
 	default: return -1;
@@ -95,7 +95,48 @@
 }
 
+int GeneProps::executeModifier(char modif, GenePropsOps* ops)
+{
+	if (ops == NULL)
+		ops = getStandardOps();
+
+#define APPLY(name) ops->name->apply(name, modif)
+#define APPLY_AND_MAYBE_NORMALIZEBIOL4(name) {APPLY(name); if (ops->use_normalizebiol4) normalizeBiol4();}
+
+	switch (toupper(modif))
+	{
+	case 'L': APPLY(length);
+		length = std::min(length, Model::getMaxJoint().d.x); break;
+		break;
+
+	case 'l': APPLY(length);
+		length = std::max(length, Model::getMinJoint().d.x); break;
+		break;
+
+	case 'W': APPLY(weight); break;
+	case 'F': APPLY(friction); break;
+	case 'C': APPLY(curvedness); break;
+	case 'Q': APPLY(twist); break;
+	case 'E': APPLY(energy); break;
+
+	case 'A': APPLY_AND_MAYBE_NORMALIZEBIOL4(assimilation); break;
+	case 'I': APPLY_AND_MAYBE_NORMALIZEBIOL4(ingestion); break;
+	case 'S': APPLY_AND_MAYBE_NORMALIZEBIOL4(stamina); break;
+	case 'M': APPLY_AND_MAYBE_NORMALIZEBIOL4(muscle_power); break;
+
+	case 'D': APPLY(cred); break;
+	case 'G': APPLY(cgreen); break;
+	case 'B': APPLY(cblue); break;
+
+	default: return -1;
+	}
+	return 0;
+
+#undef APPLY
+#undef APPLY_AND_MAYBE_NORMALIZEBIOL4
+}
+
 void GeneProps::propagateAlong(bool use_f1_muscle_reset_range)
 {
-	length = 0.5*length + 0.5 * standard_values.length;
+	length = 0.5 * length + 0.5 * standard_values.length;
 	weight += (standard_values.weight - weight) * 0.5;
 	friction = 0.8 * friction + 0.2 * standard_values.friction;
@@ -115,2 +156,148 @@
 	}
 }
+
+////////////////////////////////////////////////////
+
+void GenePropsOp::apply(double &value, char modif) const
+{
+	if (isupper(modif))
+		value = increase(value);
+	else
+		value = decrease(value);
+}
+
+double GenePropsOp_Old::increase(double value) const
+{
+	return value + (maxvalue - value) * change;
+}
+
+double GenePropsOp_Old::decrease(double value) const
+{
+	return value + (minvalue - value) * revchange;
+}
+
+GenePropsOp_Old::GenePropsOp_Old(double minvalue, double maxvalue, double defvalue, double change, double revchange)
+{
+	this->minvalue = minvalue;
+	this->maxvalue = maxvalue;
+	this->defvalue = defvalue;
+	this->change = change;
+	this->revchange = (revchange < 0) ? change : revchange;
+}
+
+GenePropsOp_Exponential::GenePropsOp_Exponential(double minvalue, double maxvalue, double defvalue, double change)
+	:GenePropsOp_NormalizedAndScaled(change)
+{
+	double mid = (maxvalue + minvalue) / 2;
+	if (fabs(mid - defvalue) < 0.01)
+	{
+		linear = true;
+		a = (maxvalue - minvalue) / 2;
+		b = defvalue;
+	}
+	else
+	{
+		linear = false;
+		a = -(maxvalue - defvalue) / (minvalue - defvalue);
+		b = (minvalue * minvalue - 2 * defvalue * minvalue + defvalue * defvalue) / (minvalue + maxvalue - 2 * defvalue);
+		c = (maxvalue * minvalue - defvalue * defvalue) / (minvalue + maxvalue - 2 * defvalue);
+		log_a = log(a);
+	}
+}
+
+double GenePropsOp_Exponential::scale(double value) const
+{
+	if (linear)
+		return a * value + b;
+	else
+		return pow(a, (value + 1)) * b + c;
+}
+
+double GenePropsOp_Exponential::scaleInv(double value) const
+{
+	if (linear)
+		return (value - b) / a;
+	else
+		return -(log_a - log(value / b - c / b)) / log_a;
+}
+
+////////////////////////////////////////////////////////////////////
+
+
+GenePropsOps::~GenePropsOps()
+{
+	delete length;
+	delete curvedness;
+	delete weight;
+	delete friction;
+	delete muscle_power;
+	delete assimilation;
+	delete stamina;
+	delete ingestion;
+	delete twist;
+	delete energy;
+	delete cred;
+	delete cgreen;
+	delete cblue;
+}
+
+GenePropsOps_Old::GenePropsOps_Old()
+{
+	use_normalizebiol4 = true;
+
+	length = new GenePropsOp_Old(0.33, 2.0, GeneProps::standard_values.length, 0.3);
+	weight = new GenePropsOp_Old(0.5, 2.0, GeneProps::standard_values.weight, 0.3);
+	friction = new GenePropsOp_Old(0, 4.0, GeneProps::standard_values.friction, 0.2);
+	curvedness = new GenePropsOp_Old(-2, 2, GeneProps::standard_values.curvedness, 0.25);
+	twist = new GenePropsOp_Old(-M_PI_2, M_PI_2, GeneProps::standard_values.twist, 0.3);
+	energy = new GenePropsOp_Old(0, 10, GeneProps::standard_values.energy, 0.1);
+
+	assimilation = new GenePropsOp_Old(0, 1, GeneProps::standard_values.assimilation, 0.8, 0.4);
+	ingestion = new GenePropsOp_Old(0, 1, GeneProps::standard_values.ingestion, 0.8, 0.4);
+	stamina = new GenePropsOp_Old(0, 1, GeneProps::standard_values.stamina, 0.8, 0.4);
+	muscle_power = new GenePropsOp_Old(0, 1, GeneProps::standard_values.muscle_power, 0.8, 0.4);
+
+	cred = new GenePropsOp_Old(0, 1, GeneProps::standard_values.cred, 0.25);
+	cgreen = new GenePropsOp_Old(0, 1, GeneProps::standard_values.cgreen, 0.25);
+	cblue = new GenePropsOp_Old(0, 1, GeneProps::standard_values.cblue, 0.25);
+}
+
+GenePropsOps_New05::GenePropsOps_New05()
+{
+	use_normalizebiol4 = false;
+	auto fields = { length,curvedness,weight,friction,muscle_power,assimilation,stamina,ingestion,twist,energy,cred,cgreen,cblue };
+	for (auto x : fields)
+	{
+		auto xx = dynamic_cast<GenePropsOp_Old*>(x);
+		if (xx)
+			xx->change = xx->revchange = 0.5;
+	}
+}
+
+GenePropsOps_Exponential::GenePropsOps_Exponential()
+{
+	length = new GenePropsOp_Exponential(0.33, 2.0, GeneProps::standard_values.length);
+	weight = new GenePropsOp_Exponential(0.5, 2.0, GeneProps::standard_values.weight);
+	friction = new GenePropsOp_Exponential(0, 4.0, GeneProps::standard_values.friction);
+	curvedness = new GenePropsOp_Exponential(-2, 2, GeneProps::standard_values.curvedness);
+	twist = new GenePropsOp_Exponential(-M_PI_2, M_PI_2, GeneProps::standard_values.twist);
+	energy = new GenePropsOp_Exponential(0, 10, GeneProps::standard_values.energy);
+
+	assimilation = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.assimilation);
+	ingestion = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.ingestion);
+	stamina = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.stamina);
+	muscle_power = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.muscle_power);
+
+	cred = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.cred);
+	cgreen = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.cgreen);
+	cblue = new GenePropsOp_Exponential(0, 1, GeneProps::standard_values.cblue);
+}
+
+
+GenePropsOps* GeneProps::standard_ops = NULL;
+GenePropsOps* GeneProps::getStandardOps()
+{
+	if (!standard_ops)
+		standard_ops = new GenePropsOps_New05();
+	return standard_ops;
+}
Index: cpp/frams/genetics/geneprops.h
===================================================================
--- cpp/frams/genetics/geneprops.h	(revision 1241)
+++ cpp/frams/genetics/geneprops.h	(revision 1242)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -19,5 +19,81 @@
 
 
+class GenePropsOp
+{
+public:
+	virtual ~GenePropsOp() {}
+	virtual double increase(double value) const = 0;
+	virtual double decrease(double value) const = 0;
+	void apply(double &value, char modif) const;
+};
 
+class GenePropsOp_Old : public GenePropsOp
+{
+	double minvalue, maxvalue, defvalue, change, revchange;
+public:
+	GenePropsOp_Old(double minvalue, double maxvalue, double defvalue, double change, double revchange = -1);
+	double increase(double value) const;
+	double decrease(double value) const;
+	friend class GenePropsOps_New05;
+};
+
+class GenePropsOp_NormalizedAndScaled : public GenePropsOp
+{
+	GenePropsOp_Old normalized;
+public:
+	GenePropsOp_NormalizedAndScaled(double change) :normalized(-1, 1, 0, change) {}
+	virtual double scale(double value) const { return value; }
+	virtual double scaleInv(double value) const { return value; }
+	double increase(double value) const { return scale(normalized.increase(scaleInv(value))); }
+	double decrease(double value) const { return scale(normalized.decrease(scaleInv(value))); }
+};
+
+class GenePropsOp_Exponential : public GenePropsOp_NormalizedAndScaled
+{
+	double a, b, c;
+	double log_a;
+	bool linear;
+public:
+	GenePropsOp_Exponential(double minvalue, double maxvalue, double defvalue, double change = 0.5);
+	double scale(double) const;
+	double scaleInv(double) const;
+};
+
+class GenePropsOps
+{
+public:
+	~GenePropsOps();
+
+	GenePropsOp* length;
+	GenePropsOp* curvedness;
+	GenePropsOp* weight;
+	GenePropsOp* friction;
+	GenePropsOp* muscle_power;
+	GenePropsOp* assimilation;
+	GenePropsOp* stamina;
+	GenePropsOp* ingestion;
+	GenePropsOp* twist;
+	GenePropsOp* energy;
+	GenePropsOp* cred, *cgreen, *cblue;
+	bool use_normalizebiol4;
+};
+
+class GenePropsOps_Old : public GenePropsOps
+{
+public:
+	GenePropsOps_Old();
+};
+
+class GenePropsOps_New05 : public GenePropsOps_Old
+{
+public:
+	GenePropsOps_New05();
+};
+
+class GenePropsOps_Exponential : public GenePropsOps
+{
+public:
+	GenePropsOps_Exponential();
+};
 
 /**
@@ -29,5 +105,5 @@
  * parents (prop) modified with the prop.propagateAlong() method.
  * "Biological" properties (assimilation, stamina, muscle strength and
- * ingestion) should be normalized after modification with normalizeBiol4().
+ * ingestion) can be normalized after modification with normalizeBiol4().
  */
 struct GeneProps
@@ -55,4 +131,6 @@
 
 	static GeneProps standard_values;
+	static GenePropsOps* standard_ops;
+	static GenePropsOps* getStandardOps();
 
 	/**
@@ -62,8 +140,7 @@
 
 	/**
-	 * Normalizes biological properties (muscle_power,
-	 * assimilation, stamina, and ingestion). This method is called in
-	 * executeModifier() when any of the biological properties is modified. All values
-	 * of those properties sum up to 1.
+	 * Normalizes biological properties (muscle_power, assimilation, stamina, and ingestion).
+	 * This method is called in executeModifier() when any of the biological properties is modified
+	 * and \a use_normalizebiol4 is true. All values of those properties sum up to 1.
 	 */
 	void normalizeBiol4();
@@ -75,5 +152,6 @@
 	 * @return 0 if the provided character was property modifier, -1 otherwise
 	 */
-	int executeModifier(char modif);
+	int executeModifier(char modif, GenePropsOps* ops = NULL);
+	int executeModifier_Legacy(char modif);
 
 	/**
