Index: cpp/frams/genetics/f1/f1_conv.cpp
===================================================================
--- cpp/frams/genetics/f1/f1_conv.cpp	(revision 1257)
+++ cpp/frams/genetics/f1/f1_conv.cpp	(revision 1258)
@@ -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.
 
@@ -14,9 +14,34 @@
 //#define v1f1COMPATIBLE //as in ancient Framsticks 1.x
 
+#define FIELDSTRUCT GenoConv_f1
+static ParamEntry f1_conv_paramtab[] =
+{
+	{ "Genetics: Converters: f1 to f0", 1, 3, },
+	{ "conv_f1_f0_modcompat", 1, 0, "Modifier compatibility", "d 0 1 1 ~Before June 2023~Modern", FIELD(settings.modifier_compatibility), "The modern implementation makes the influence of modifiers more consistent and uniform, and the extreme property values are easier to reach with a lower number of characters, which improves the topology for evolutionary search.\nPrevious implementation can be enabled for compatibility, for example when you want to test old genotypes." },
+	{ "conv_f1_f0_cq_influence", 1, 0, "'C' and 'Q' modifier influence", "d 0 1 1 ~Before June 2023~Modern", FIELD(settings.cq_mod_influence), "'C' and 'Q' modifier semantics was changed in June 2023. Previously they did not affect the stick immediately following the current sequence of modifiers. In the modern implementation, all modifiers consistently start their influence at the very next stick that is being created in the current branch.\nExample:\nIn the old interpretation of 'XcXX', only the last stick is rotated, because 'c' starts its influence at the stick that occurs after the current stick. In the modern implementation, the same effect is achieved with 'XXcX', where 'c' immediately bends the first 'X' that appears after it.\nPrevious implementation can be enabled for compatibility, for example when you want to test old genotypes." },
+	{ "conv_f1_f0_branch_muscle_range", 1, 0, "Bending muscle default range", "d 0 2 0 ~Full/NumberOfBranches~Full/(NumberOfBranches+1)~Full", FIELD(settings.branch_muscle_range), "Determines how the bending muscle default turning range is limited when the muscle is controlling a stick growing from a branching point that has 'NumberOfBranches' sticks separated by commas. The motivation of the limited range is to keep the neighboring sticks from intersecting when they are bent by muscles. This constraint may degrade the performance (e.g. velocity) of creatures, but this default value can be overridden by providing a specific range property value for the '|' muscle neuron in the genotype.\n"
+	  "- Full/NumberOfBranches - a compromise between the two other settings.\n"
+	  "- Full/(NumberOfBranches+1) - because the originating stick also counts as a branch. This setting guarantees that in the worst case, when at least two neighboring branches have sticks controlled by bending muscles and their controlling signals are at extreme values, the sticks can touch and overlap, but will not intersect. This setting is in most cases too strict because (1) all branches are very rarely controlled by muscles, (2) there are often 'empty' branches - multiple commas with no sticks in-between, and (3) the share of the originating stick is effectively wasted because this stick itself has no muscle at the branching point so it will not bend; the muscle bending range is symmetrical and the default range is equal for all muscles in a branching, but the sticks equipped with muscles in a branching are rarely evenly spaced.\n"
+	  "- Full: Always 1 (the complete angle) - because we do not have to care about the physical plausibility and avoid intersecting sticks, and other genetic representations do not impose such constraints, so this full angle setting can be useful as the default bending range when comparing the performance of various genetic encodings."},
+
+	{ 0, },
+};
+#undef FIELDSTRUCT
+
 class Builder
 {
 public:
-	Builder(const char*g, int mapping = 0) :invalid(0), genbegin(g), usemapping(mapping), first_part_mapping(NULL), own_first_part_mapping(true), model_energy(0), model_energy_count(0) {}
+	Builder(GenoConv_f1_Settings& _settings, const char*g, int mapping = 0) :settings(_settings), invalid(0), genbegin(g), usemapping(mapping), first_part_mapping(NULL), own_first_part_mapping(true), model_energy(0), model_energy_count(0)
+	{
+		switch (settings.modifier_compatibility)
+		{
+		case GenoConv_f1_Settings::MODIF_COMPAT_LEGACY: gp_ops = GeneProps::getLegacyOps(); break;
+		case GenoConv_f1_Settings::MODIF_COMPAT_ALL_CHANGE_05: gp_ops = GeneProps::getAllChange05Ops(); break;
+		}
+	}
 	~Builder() { if (own_first_part_mapping) SAFEDELETE(first_part_mapping); }
+
+	GenoConv_f1_Settings& settings;
+	GenePropsOps* gp_ops;
 	char tmp[222];
 	bool invalid;
@@ -43,5 +68,5 @@
 	void grow(int part1, const char*g, Pt3D k, GeneProps c, int branching_part);
 	void setPartMapping(int p, const char* g);
-	int growJoint(int part1, int part2, Pt3D &angle, GeneProps &c, const char *g);
+	int growJoint(int part1, int part2, const Pt3D &angle, const GeneProps &c, const char *g);
 	int growPart(GeneProps &c, const char *g);
 	const char *skipNeuro(const char *z);
@@ -94,9 +119,20 @@
 }
 
+GenoConv_f1::GenoConv_f1()
+{
+	name = "Recursive encoding";
+	in_format = '1';
+	mapsupport = 1;
+
+	param.setParamTab(f1_conv_paramtab);
+	param.select(this);
+	param.setDefault();
+}
+
 /** main conversion function - with conversion map support */
 SString GenoConv_f1::convert(SString &i, MultiMap *map, bool using_checkpoints)
 {
 	const char* g = i.c_str();
-	Builder builder(g, map ? 1 : 0);
+	Builder builder(settings, g, map ? 1 : 0);
 	builder.model.open(using_checkpoints);
 	builder.grow(-1, g, Pt3D_0, GeneProps::standard_values, -1); // uses Model::addFromString() to create model elements
@@ -125,8 +161,9 @@
 {
 	int hasmuscles = 0;
-	k += Pt3D(c.twist, 0, c.curvedness);
+	if (settings.cq_mod_influence == settings.CQ_INFLUENCE_OLD)
+		k += Pt3D(c.twist, 0, c.curvedness);
 	while (1)
 	{
-		if (c.executeModifier(*g) == 0)
+		if (c.executeModifier(*g, gp_ops) == 0)
 		{
 			setPartMapping(part1, g);
@@ -177,13 +214,23 @@
 				{
 					Part *part = model.getPart(part1);
-					part->density = ((part->mass*part->density) + 1.0 / c.weight) / (part->mass + 1.0); // v=m*d
+					part->density = ((part->mass * part->density) + 1.0 / c.weight) / (part->mass + 1.0); // v=m*d
 					//			part->volume+=1.0/c.weight;
 					part->mass += 1.0;
 				}
-				model_energy += c.energy;
+
+				if (settings.modifier_compatibility == settings.MODIF_COMPAT_LEGACY)
+					model_energy += 0.9 * c.energy + 0.1;
+				else
+					model_energy += c.energy;
 				model_energy_count++;
 
 				int part2 = growPart(c, g);
-				growJoint(part1, part2, k, c, g);
+				Pt3D new_k;
+				switch (settings.cq_mod_influence)
+				{
+				case GenoConv_f1_Settings::CQ_INFLUENCE_OLD: new_k = k; break;
+				case GenoConv_f1_Settings::CQ_INFLUENCE_NEW: new_k = k + Pt3D(c.twist, 0, c.curvedness); break;
+				}
+				growJoint(part1, part2, new_k, c, g);
 				//		est* e = new est(*s,*s2,k,c,zz,this);
 
@@ -201,7 +248,18 @@
 				int count = countBranches(g + 1, ga);
 				c.muscle_reset_range = false;
-				c.muscle_bend_range = 1.0 / count;
+				switch (settings.branch_muscle_range)
+				{
+				case GenoConv_f1_Settings::MUSCLE_RANGE_ORIGINAL:
+					c.muscle_bend_range = 1.0 / count;
+					break;
+				case GenoConv_f1_Settings::MUSCLE_RANGE_STRICT:
+					c.muscle_bend_range = 1.0 / (count + 1);
+					break;
+				case GenoConv_f1_Settings::MUSCLE_RANGE_UNLIMITED:
+					c.muscle_bend_range = 1.0;
+					break;
+				}
 				for (int i = 0; i < count; i++)
-					grow(part1, (char*)ga(i), k + Pt3D(0, 0, -M_PI + (i + 1)*(2 * M_PI / (count + 1))), c, part1);
+					grow(part1, (char*)ga(i), k + Pt3D(0, 0, -M_PI + (i + 1) * (2 * M_PI / (count + 1))), c, part1);
 				return;
 			}
@@ -270,5 +328,5 @@
 }
 
-int Builder::growJoint(int part1, int part2, Pt3D &angle, GeneProps &c, const char *g)
+int Builder::growJoint(int part1, int part2, const Pt3D &angle, const GeneProps &c, const char *g)
 {
 	double len = std::min(2.0, c.length);
@@ -309,5 +367,5 @@
 	if (*t && *next != ',' && *next != ']') // old style muscles [|rest] or [@rest]
 		switch (*t)
-	{
+		{
 		case '@': if (t[1] == ':') break;
 			haveclass = 1;
@@ -338,5 +396,5 @@
 			t++;
 			break;
-	}
+		}
 	while (*t && *t <= ' ') t++;
 	bool finished = 0;
Index: cpp/frams/genetics/f1/f1_conv.h
===================================================================
--- cpp/frams/genetics/f1/f1_conv.h	(revision 1257)
+++ cpp/frams/genetics/f1/f1_conv.h	(revision 1258)
@@ -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.
 
@@ -51,15 +51,31 @@
 
  */
+
+struct GenoConv_f1_Settings
+{
+	paInt modifier_compatibility, cq_mod_influence, branch_muscle_range;
+
+	static constexpr int MODIF_COMPAT_LEGACY = 0;
+	static constexpr int MODIF_COMPAT_ALL_CHANGE_05 = 1;
+
+	static constexpr int CQ_INFLUENCE_OLD = 0;
+	static constexpr int CQ_INFLUENCE_NEW = 1;
+
+	static constexpr int MUSCLE_RANGE_ORIGINAL = 0;
+	static constexpr int MUSCLE_RANGE_STRICT = 1;
+	static constexpr int MUSCLE_RANGE_UNLIMITED = 2;
+};
+
 class GenoConv_f1 : public GenoConverter
 {
 public:
-	GenoConv_f1()
-	{
-		name = "Recursive encoding";
-		in_format = '1';
-		mapsupport = 1;
-	}
+	GenoConv_f1();
 	SString convert(SString &i, MultiMap *map, bool using_checkpoints);
 	~GenoConv_f1() {}
+
+	GenoConv_f1_Settings settings;
+
+	Param param;
+	ParamInterface* getParam() { return &param; }
 };
 
