Index: /cpp/frams/Makefile-SDK
===================================================================
--- /cpp/frams/Makefile-SDK	(revision 1241)
+++ /cpp/frams/Makefile-SDK	(revision 1242)
@@ -5,5 +5,5 @@
 include frams/Makefile-SDK-files
 
-SDK_TESTS=genoconv_test genomanipulation geno_test genooper_test genooper_test_fTest neuro_test loader_test_geno loader_test_param saver_test_geno serial_test multiline_f0_test f0_variants_test full_props resize_f0 shapeconvert part_shapes neuro_layout_test geometry_apices_test geometry_info_test geometry_surface_test geometry_volume_test simil_test simil_unittests paramtree_stdin_test paramtree_paramlist_test mutableparam_test evol_test
+SDK_TESTS=genoconv_test genomanipulation geno_test genooper_test genooper_test_fTest neuro_test loader_test_geno loader_test_param saver_test_geno serial_test multiline_f0_test f0_variants_test full_props resize_f0 shapeconvert part_shapes neuro_layout_test geometry_apices_test geometry_info_test geometry_surface_test geometry_volume_test simil_test simil_unittests paramtree_stdin_test paramtree_paramlist_test mutableparam_test evol_test geneprops_test
 
 sdk_tests: $(SDK_TESTS)
@@ -107,7 +107,10 @@
 	$(CXX) $(EVOL_TEST_OBJS) $(LDFLAGS) -o $@
 
+geneprops_test: $(GENEPROPS_TEST_OBJS)
+	$(CXX) $(GENEPROPS_TEST_OBJS) $(LDFLAGS) -o $@
+
 #############################################
 
-ALL_OBJS=$(GENOMANIPULATION_OBJS) $(GENOCONV_TEST_OBJS) $(GENO_TEST_OBJS) $(NEURO_TEST_OBJS) $(LOADER_TEST_OBJS) $(SERIAL_TEST_OBJS) $(MULTILINE_F0_OBJS) $(F0_VARIANTS_OBJS) $(FULL_PROPS_OBJS) $(PART_SHAPES_OBJS) $(NEURO_LAYOUT_TEST_OBJS) $(GENOOPER_TEST_OBJS) $(GEOMETRY_APICES_TEST_OBJS) $(GEOMETRY_INFO_TEST_OBJS) $(GEOMETRY_SURFACE_TEST_OBJS) $(GEOMETRY_VOLUME_TEST_OBJS) $(SIMIL_TEST_OBJS) $(SIMIL_UNITTESTS_OBJS) $(PARAMTREE_STDIN_TEST_OBJS) $(PARAMTREE_PARAMLIST_TEST_OBJS) $(MUTABLEPARAM_TEST_OBJS) $(EVOL_TEST_OBJS)
+ALL_OBJS=$(GENOMANIPULATION_OBJS) $(GENOCONV_TEST_OBJS) $(GENO_TEST_OBJS) $(NEURO_TEST_OBJS) $(LOADER_TEST_OBJS) $(SERIAL_TEST_OBJS) $(MULTILINE_F0_OBJS) $(F0_VARIANTS_OBJS) $(FULL_PROPS_OBJS) $(PART_SHAPES_OBJS) $(NEURO_LAYOUT_TEST_OBJS) $(GENOOPER_TEST_OBJS) $(GEOMETRY_APICES_TEST_OBJS) $(GEOMETRY_INFO_TEST_OBJS) $(GEOMETRY_SURFACE_TEST_OBJS) $(GEOMETRY_VOLUME_TEST_OBJS) $(SIMIL_TEST_OBJS) $(SIMIL_UNITTESTS_OBJS) $(PARAMTREE_STDIN_TEST_OBJS) $(PARAMTREE_PARAMLIST_TEST_OBJS) $(MUTABLEPARAM_TEST_OBJS) $(EVOL_TEST_OBJS) $(GENEPROPS_TEST_OBJS)
 
 include common/Makefile-maintain
Index: /cpp/frams/Makefile-SDK-files
===================================================================
--- /cpp/frams/Makefile-SDK-files	(revision 1241)
+++ /cpp/frams/Makefile-SDK-files	(revision 1242)
@@ -105,2 +105,4 @@
 
 EVOL_TEST_OBJS=frams/_demos/evol_test.o $(SDK_OBJS) $(GENOCONV_AND_GENMAN_SDK_OBJS) $(STDOUT_LOGGER_OBJS)
+
+GENEPROPS_TEST_OBJS=frams/_demos/geneprops_test.o $(SDK_OBJS) $(STDOUT_LOGGER_OBJS) frams/genetics/geneprops.o
Index: /cpp/frams/_demos/geneprops_graph.py
===================================================================
--- /cpp/frams/_demos/geneprops_graph.py	(revision 1242)
+++ /cpp/frams/_demos/geneprops_graph.py	(revision 1242)
@@ -0,0 +1,57 @@
+# This file is a part of Framsticks SDK.  http://www.framsticks.com/
+# Copyright (C) 2023  Maciej Komosinski and Szymon Ulatowski.
+# See LICENSE.txt for details.
+
+import matplotlib.pyplot as plt
+from matplotlib.collections import LineCollection
+import sys
+import csv
+
+# Visualizing the influence of Framsticks f1/f4 modifier sequences. See also geneprops_test.cpp
+# usage:
+# ./geneprops_test f 3 -e | python3 frams/_demos/geneprops_graph.py
+#
+# See also https://colab.research.google.com/drive/1x9FTAUQKFhvxX_IyEcglWRWa4ylZeY-7?usp=sharing
+
+#################################################
+# loading
+
+nodes_list = [] # ['modifiers', value]
+
+for line in csv.reader(sys.stdin.readlines(),delimiter='\t'):
+    if line[0]=='-':
+        line[0]=''
+    nodes_list.append([line[0],float(line[1])])
+
+nodes_dict = {} # ['modifiers', value, [children,...]]
+    
+for n in sorted(nodes_list, key=lambda e: len(e[0])):
+    tree_node = [n[0],n[1],[]]
+    if n[0]!='':
+        nodes_dict[n[0][:-1]][2].append(tree_node)
+    nodes_dict[n[0]] = tree_node
+    
+#################################################
+# plotting
+
+fig, ax = plt.subplots(figsize=(4, 4))
+ax.set(title="Tree of modifier values")
+
+segs=[]
+for n in nodes_dict.values():
+    for child in n[2]:
+        segs.append( ((len(n[0]),n[1]),(len(n[0])+1,child[1])) )
+
+linecol = LineCollection(segs, linewidths=2, linestyle='solid')
+ax.add_collection(linecol)
+
+node_x = [len(n[0]) for n in nodes_dict.values()]
+node_y = [n[1] for n in nodes_dict.values()]
+
+ax.plot(node_x, node_y, "o", color="w", markerfacecolor="r")
+
+for n in nodes_dict.values():
+    ax.annotate(n[0], (len(n[0]),n[1]), textcoords="offset points", horizontalalignment="right", verticalalignment='center', xytext=(-4,0))
+
+ax.xaxis.set_visible(False)
+plt.show()
Index: /cpp/frams/_demos/geneprops_test.cpp
===================================================================
--- /cpp/frams/_demos/geneprops_test.cpp	(revision 1242)
+++ /cpp/frams/_demos/geneprops_test.cpp	(revision 1242)
@@ -0,0 +1,157 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
+#include <frams/genetics/geneprops.h>
+
+/*
+Testing the influence of Framsticks f1/f4 modifier sequences. See also geneprops_graph.py
+
+Examples:
+
+- new experimental friction:
+$ ./geneprops_test f 3 -e | sort -n -k 2
+
+fff	0.0158037
+ff	0.0366025
+Fff	0.0639754
+f	0.1
+fFf	0.147411
+Ff	0.209808
+FFf	0.291926
+-	0.4
+ffF	0.542233
+fF	0.729423
+FfF	0.975778
+F	1.3
+fFF	1.7267
+FF	2.28827
+FFF	3.02733
+
+
+- legacy friction:
+$ ./geneprops_test f 3 -l | sort -n -k 2
+
+fff	0.2048
+ff	0.256
+f	0.32
+-	0.4
+Fff	0.7168
+fFf	0.8448
+Ff	0.896
+ffF	1.0048
+fF	1.056
+F	1.12
+FFf	1.3568
+FfF	1.5168
+fFF	1.6448
+FF	1.696
+FFF	2.1568
+
+
+*/
+
+double genePropsValueForModifier(GeneProps& gp, char modifier)
+{
+	switch (toupper(modifier))
+	{
+	case 'L': return gp.length; break;
+	case 'W': return gp.weight; break;
+	case 'F': return gp.friction; break;
+	case 'C': return gp.curvedness; break;
+	case 'Q': return gp.twist; break;
+	case 'E': return gp.energy; break;
+
+	case 'A': return gp.assimilation; break;
+	case 'I': return gp.ingestion; break;
+	case 'S': return gp.stamina; break;
+	case 'M': return gp.muscle_power; break;
+
+	case 'D': return gp.cred; break;
+	case 'G': return gp.cgreen; break;
+	case 'B': return gp.cblue;  break;
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	char modifier = 'L';
+	int maxcount = 4;
+
+	enum Function { Legacy, RecreatedLegacy, New05, NewExponential };
+	Function fun = Legacy;
+
+	int argpos = 0;
+	for (int i = 1; i < argc; i++)
+	{
+		const char* arg = argv[i];
+		if (arg[0] == '-')
+			switch (arg[1])
+			{
+			case 'l': fun = Legacy; break;
+			case 'r': fun = RecreatedLegacy; break;
+			case 'n': fun = New05; break;
+			case 'e': fun = NewExponential; break;
+			case 'h': printf("%s args: [modifier [max_count]] (one of " F14_MODIFIERS ")\n"
+				"\t-l = legacy function\n"
+				"\t-r = recreated legacy function\n"
+				"\t-n = new, change=0.5, normalizeBiol4 disabled\n"
+				"\t-e = new exponential function\n", argv[0]);
+				return 0;
+			default: fprintf(stderr, "%s: invalid option: %s\n", argv[0], arg); return 2;
+			}
+		else
+			switch (argpos++)
+			{
+			case 0: modifier = arg[0]; break;
+			case 1: maxcount = atoi(arg); break;
+			default: fprintf(stderr, "%s: too many arguments: %s\n", argv[0], arg); return 2;
+			}
+	}
+
+	char modifier_u = toupper(modifier);
+	char modifier_l = tolower(modifier);
+
+	GenePropsOps_Exponential exponential_ops;
+	GenePropsOps_Old recreated_ops;
+	GenePropsOps_New05 new05_ops;
+	GenePropsOps *ops;
+
+	switch (fun)
+	{
+	case RecreatedLegacy: ops = &recreated_ops; break;
+	case NewExponential:  ops = &exponential_ops; break;
+	case New05:  ops = &new05_ops; break;
+	default: ops = NULL;
+	}
+
+	char *tmp = new char[maxcount + 1];
+	for (int count = 0; count <= maxcount; count++)
+	{
+		int N = 1 << count;
+		for (int i = 0; i < N; i++)
+		{
+			GeneProps props;
+			for (int c = 0; c < count; c++)
+			{
+				char m = ((1 << c) & i) ? modifier_u : modifier_l;
+				tmp[c] = m;
+				if (fun == Legacy)
+					props.executeModifier_Legacy(m);
+				else
+					props.executeModifier(m, ops);
+			}
+			if (count > 0)
+				tmp[count] = 0;
+			else
+			{
+				tmp[0] = '-'; tmp[1] = 0;
+			}
+			double value = genePropsValueForModifier(props, modifier);
+			printf("%s\t%g\n", tmp, value);
+		}
+	}
+
+	return 0;
+}
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);
 
 	/**
