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;
+}
