Index: /cpp/common/threads.h
===================================================================
--- /cpp/common/threads.h	(revision 1157)
+++ /cpp/common/threads.h	(revision 1158)
@@ -27,5 +27,5 @@
 	pthread_key_t mt_key;
 	bool destroyed;
-	
+
 public:
 
@@ -33,5 +33,5 @@
 	{
 		pthread_key_create(&mt_key, &destructor);
-		destroyed=false;
+		destroyed = false;
 	}
 
@@ -40,5 +40,5 @@
 		T* o = set(NULL);
 		if (o) delete o;
-		destroyed=true;
+		destroyed = true;
 	}
 
@@ -114,7 +114,7 @@
 	T *object;
 public:
-	ThreadSingletonPtr():object(NULL) {}
-	T* get() {return object;}
-	T* setptr(T* o) {T* previous=object; object=o; return previous;}
+	ThreadSingletonPtr() :object(NULL) {}
+	T* get() { return object; }
+	T* setptr(T* o) { T* previous = object; object = o; return previous; }
 };
 
@@ -139,5 +139,5 @@
 {
 	if (!var)
-		var=new T();
+		var = new T();
 	return var;
 }
@@ -151,8 +151,8 @@
 #define tlsGetRef(var) (*_tlsGet(ThreadLocal_ ## var))
 
-template<class T> T* _tlsSet(T*& var,T* new_o)
+template<class T> T* _tlsSet(T*& var, T* new_o)
 {
-	T* o=var;
-	var=new_o;
+	T* o = var;
+	var = new_o;
 	return o;
 }
Index: /cpp/frams/Makefile-SDK
===================================================================
--- /cpp/frams/Makefile-SDK	(revision 1157)
+++ /cpp/frams/Makefile-SDK	(revision 1158)
@@ -11,5 +11,5 @@
 CXXWARNINGS=-Wall -Wno-parentheses -Wno-overloaded-virtual -Wno-format -Werror=return-type
 
-SDK_BUILD_CONFIG= -include frams/config/sdk_build_config.h
+SDK_BUILD_CONFIG= -include frams/config/sdk-build-config.h
 CXXFLAGS= -I$(CURDIR) -std=gnu++17 $(SDK_BUILD_CONFIG) $(CXXWARNINGS) -g
 
Index: /cpp/frams/config/sdk-build-config.h
===================================================================
--- /cpp/frams/config/sdk-build-config.h	(revision 1158)
+++ /cpp/frams/config/sdk-build-config.h	(revision 1158)
@@ -0,0 +1,34 @@
+// This file is a part of Framsticks SDK.  http://www.framsticks.com/
+// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
+// See LICENSE.txt for details.
+
+#ifndef _SDK_BUILD_CONFIG_H_
+#define _SDK_BUILD_CONFIG_H_
+
+#include "version.h"
+
+#ifdef __APPLE__
+  #define MACOS
+#elif defined __linux || defined __CYGWIN__ || defined __MINGW32__
+  #define LINUX
+#endif
+
+#ifdef __MINGW32__
+  #undef _WIN32 //assume that someone using MinGW to compile this SDK wants to pretend their system is Linux, not Windows
+#endif
+
+
+#define USE_PRINTFLOAT_DRAGON4
+#define NOCREATUREOBJECT
+#define SDK_WITHOUT_FRAMS
+#define NO_BARRIER
+#define NO_VMACHINE
+
+#define NEURO_IMPL_FILES "neuroimplfiles-SDK.h"
+#define NEURO_CLS_FACTORY "neurocls-f0-SDK-factory.h"
+#define NEURO_CLS_LIBRARY "neurocls-f0-SDK-library.h"
+#define F0_DEFASSIGN_FILE "defassign-f0-SDK.h"
+#define F0_CLASSES_FILE "f0-SDK-classes.h"
+#define GEN_CONFIG_FILE "gen-config-SDK.h"
+
+#endif
Index: p/frams/config/sdk_build_config.h
===================================================================
--- /cpp/frams/config/sdk_build_config.h	(revision 1157)
+++ 	(revision )
@@ -1,34 +1,0 @@
-// This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
-// See LICENSE.txt for details.
-
-#ifndef _SDK_BUILD_CONFIG_H_
-#define _SDK_BUILD_CONFIG_H_
-
-#include "version.h"
-
-#ifdef __APPLE__
-  #define MACOS
-#elif defined __linux || defined __CYGWIN__ || defined __MINGW32__
-  #define LINUX
-#endif
-
-#ifdef __MINGW32__
-  #undef _WIN32 //assume that someone using MinGW to compile this SDK wants to pretend their system is Linux, not Windows
-#endif
-
-
-#define USE_PRINTFLOAT_DRAGON4
-#define NOCREATUREOBJECT
-#define SDK_WITHOUT_FRAMS
-#define NO_BARRIER
-#define NO_VMACHINE
-
-#define NEURO_IMPL_FILES "neuroimplfiles-SDK.h"
-#define NEURO_CLS_FACTORY "neurocls-f0-SDK-factory.h"
-#define NEURO_CLS_LIBRARY "neurocls-f0-SDK-library.h"
-#define F0_DEFASSIGN_FILE "defassign-f0-SDK.h"
-#define F0_CLASSES_FILE "f0-SDK-classes.h"
-#define GEN_CONFIG_FILE "gen-config-SDK.h"
-
-#endif
Index: /cpp/frams/frams-objects.h
===================================================================
--- /cpp/frams/frams-objects.h	(revision 1157)
+++ /cpp/frams/frams-objects.h	(revision 1158)
@@ -25,8 +25,16 @@
 EXPORTED char* extPropId(void *v, int i);
 EXPORTED char* extPropType(void *v, int i);
+EXPORTED char* extPropName(void *v, int i);
+EXPORTED int extPropFlags(void *v, int i);
+EXPORTED int extPropGroup(void *v, int i);
+EXPORTED char* extPropHelp(void *v, int i);
 EXPORTED int extPropFind(void *v, char* id);
 EXPORTED void* extPropGet(void *v, int i);
 EXPORTED int extPropSet(void *v, int i, void* e);
 EXPORTED void* extPropCall(void *v, int i, int argc, void* argv);
+EXPORTED int extGroupCount(void *v);
+EXPORTED char* extGroupName(void *v, int i);
+EXPORTED int extGroupMember(void *v, int g, int i);
+EXPORTED int extMemberCount(void *v, int g);
 EXPORTED void* rootObject();
 
Index: /cpp/frams/genetics/fS/fS_general.h
===================================================================
--- /cpp/frams/genetics/fS/fS_general.h	(revision 1157)
+++ /cpp/frams/genetics/fS/fS_general.h	(revision 1158)
@@ -1,4 +1,4 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 2019-2020  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 2019-2021  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
@@ -11,4 +11,5 @@
 #include <unordered_map>
 #include <exception>
+#include <algorithm>
 #include "frams/model/model.h"
 #include "frams/util/multirange.h"
Index: /cpp/frams/model/geometry/modelgeoclass.cpp
===================================================================
--- /cpp/frams/model/geometry/modelgeoclass.cpp	(revision 1157)
+++ /cpp/frams/model/geometry/modelgeoclass.cpp	(revision 1158)
@@ -18,5 +18,5 @@
 	"To set geom_density for individual ModelGeometry objects:\n"
 	"var mg=ModelGeometry.forModel(GenePools[0][0].getModel()); mg.geom_density=2; GenePools[0][0].data->area=mg.area();\n" },
-	{ "geom_density", 0, 0, "Density", "f 0.01 100.0 1.0", FIELD(density), "The number of samples (per unit length in one dimension) that affects the precision of estimation of geometrical properties." }, //note: we used 'geom_density' instead of 'density' to make the name more unique - because sim_params merges all configuration fields in a single namespace.
+	{ "geom_density", 0, 0, "Density", "f 0.01 100.0 3.0", FIELD(density), "The number of samples (per unit length in one dimension) that affects the precision of estimation of geometrical properties." }, //Note #1: we used 'geom_density' instead of 'density' to make the name more unique - because sim_params merges all configuration fields in a single namespace. Note #2: "density" needs better, more interpretable and more reliable parametrization for surface sampling...
 	{ "forModel", 0, PARAM_USERHIDDEN, "", "p oModelGeometry(oModel)", PROCEDURE(p_formodel), "The returned ModelGeometry object can be used to calculate geometric properties (volume, area, sizes) of the associated model. The density is copied from the current global ModelGeometry.geom_density on object creation." },
 	{ "volume", 0, PARAM_NOSTATIC | PARAM_USERHIDDEN, "volume", "p f()", PROCEDURE(p_volume), },
Index: /cpp/frams/model/modelobj.cpp
===================================================================
--- /cpp/frams/model/modelobj.cpp	(revision 1157)
+++ /cpp/frams/model/modelobj.cpp	(revision 1158)
@@ -21,7 +21,7 @@
 	{ "numconnections", 0, PARAM_DONTSAVE | PARAM_NOSTATIC | PARAM_READONLY, "Number of neuron connections", "d", GETONLY(numconnections), },
 
-	{ "getPart", 0, PARAM_USERHIDDEN | PARAM_NOSTATIC, "getPart (static model information)", "p oPart(d index)", PROCEDURE(p_getpart), },
-	{ "getJoint", 0, PARAM_USERHIDDEN | PARAM_NOSTATIC, "getJoint (static model information)", "p oJoint(d index)", PROCEDURE(p_getjoint), },
-	{ "getNeuroDef", 0, PARAM_USERHIDDEN | PARAM_NOSTATIC, "getNeuroDef", "p oNeuroDef(d index)", PROCEDURE(p_getneuro), },
+	{ "getPart", 0, PARAM_READONLY | PARAM_USERHIDDEN | PARAM_NOSTATIC, "getPart (static model information)", "p oPart(d index)", PROCEDURE(p_getpart), },
+	{ "getJoint", 0, PARAM_READONLY | PARAM_USERHIDDEN | PARAM_NOSTATIC, "getJoint (static model information)", "p oJoint(d index)", PROCEDURE(p_getjoint), },
+	{ "getNeuroDef", 0, PARAM_READONLY | PARAM_USERHIDDEN | PARAM_NOSTATIC, "getNeuroDef", "p oNeuroDef(d index)", PROCEDURE(p_getneuro), },
 
 	{ "size_x", 0, PARAM_READONLY | PARAM_NOSTATIC | PARAM_DEPRECATED, "Bounding box x size", "f", FIELD(size.x), "(size_x,size_y,size_z) are dimensions of the axis-aligned bounding box of the creature, including imaginary Part sizes (Part.s, usually 1.0). A creature consisting of a single default part has the size of (2.0,2.0,2.0) - twice the Part.s value (like a sphere diameter is twice its radius).\nSee also: Creature.moveAbs" },
@@ -30,5 +30,5 @@
 	{ "bboxSize", 0, PARAM_READONLY | PARAM_NOSTATIC, "Bounding box size", "oXYZ", GETONLY(bboxsize) },
 	{ "numcheckpoints", 0, PARAM_DONTSAVE | PARAM_READONLY | PARAM_NOSTATIC, "Number of checkpoints", "d", GETONLY(numcheckpoints) },
-	{ "getCheckpoint", 0, PARAM_USERHIDDEN | PARAM_NOSTATIC, "getCheckpoint", "p oModel(d index)", PROCEDURE(p_getcheckpoint),
+	{ "getCheckpoint", 0, PARAM_READONLY | PARAM_USERHIDDEN | PARAM_NOSTATIC, "getCheckpoint", "p oModel(d index)", PROCEDURE(p_getcheckpoint),
 	"Checkpoint Model objects are only valid as long as the parent Model object exists.\n"
 	"See also: Model.newWithCheckpoints()\n\n"
Index: /cpp/frams/model/similarity/measure-greedy.cpp
===================================================================
--- /cpp/frams/model/similarity/measure-greedy.cpp	(revision 1157)
+++ /cpp/frams/model/similarity/measure-greedy.cpp	(revision 1158)
@@ -1,7 +1,8 @@
 // This file is a part of Framsticks SDK.  http://www.framsticks.com/
-// Copyright (C) 1999-2020  Maciej Komosinski and Szymon Ulatowski.
+// Copyright (C) 1999-2021  Maciej Komosinski and Szymon Ulatowski.
 // See LICENSE.txt for details.
 
 #include "measure-greedy.h"
+#include <cstdlib> //std::qsort()
 #include <assert.h>
 
Index: /cpp/frams/neuro/impl/neuroimpl-fuzzy.cpp
===================================================================
--- /cpp/frams/neuro/impl/neuroimpl-fuzzy.cpp	(revision 1157)
+++ /cpp/frams/neuro/impl/neuroimpl-fuzzy.cpp	(revision 1158)
@@ -5,4 +5,5 @@
 #include "neuroimpl-fuzzy.h"
 #include "neuroimpl-fuzzy-f0.h"
+#include <algorithm>
 
 int NI_FuzzyNeuro::countOuts(const Model *m, const Neuro *fuzzy)
Index: /cpp/frams/util/extvalue.h
===================================================================
--- /cpp/frams/util/extvalue.h	(revision 1157)
+++ /cpp/frams/util/extvalue.h	(revision 1158)
@@ -43,5 +43,5 @@
 class ExtObject
 {
-	int subtype;                    //< 0/1=Generic/DPC Object,  0/2=Standalone/Shared Param
+	int subtype;                    //< 0/1=Generic/DPC Object,  0/2=Standalone/Shared Param,  0/4=RW/Readonly
 	void incref() const;
 	void decref() const;
@@ -73,4 +73,6 @@
 	void setEmpty() { decref(); subtype = 0; param = NULL; object = NULL; }
 	int isEmpty() const { return !param; }
+	bool isReadonly() const {return (subtype&4)!=0;}
+	void setReadonly(bool ro) {subtype=(subtype&~4)|(ro?4:0);}
 	static const ExtObject& empty() { static const ExtObject e((ParamInterface*)NULL); return e; }
 	ExtObject(const ExtObject& src) { DEBUG_EXTOBJECT("(const&)"); src.incref(); copyFrom(src); }
@@ -188,4 +190,6 @@
 	void setInvalid() { setEmpty(); type = TInvalid; }
 	void setError(const SString& msg);
+	bool isReadonly() const {return (type==TObj) && odata().isReadonly();}
+	void setReadonly(bool ro) {if (type==TObj) odata().setReadonly(ro);}
 	bool makeUnique() { return (type == TObj) && odata().makeUnique(); } //< @return false if nothing has changed
 	ExtPType getType() const { return type; }
Index: /cpp/frams/vm/classes/3dobject.cpp
===================================================================
--- /cpp/frams/vm/classes/3dobject.cpp	(revision 1157)
+++ /cpp/frams/vm/classes/3dobject.cpp	(revision 1158)
@@ -20,5 +20,5 @@
 		{ "new", 0, 0, "create new XYZ object", "p oXYZ(f x,f y,f z)", PROCEDURE(p_new), "3D vectors objects can be also created using the (x,y,z) notation, i.e. var v=(1,2,3) is the same as var v=XYZ.new(1,2,3);", },
 		{ "newFromVector", 0, 0, "create new XYZ object", "p oXYZ(oVector)", PROCEDURE(p_newFromVector), "used for deserialization" },
-		{ "clone", 0, PARAM_NOSTATIC, "create new XYZ object copying the coordinates", "p oXYZ()", PROCEDURE(p_clone), "Note: copying object references does not create new objects. Use clone() if a new object is needed.\n\nExample:\nvar o1=(1,2,3), o2=o1, o3=o1.clone();\no1.y=9999;\n//o2 is now (1,9999,3) but o3 is still (1,2,3)", },
+		{ "clone", 0, PARAM_READONLY | PARAM_NOSTATIC, "create new XYZ object copying the coordinates", "p oXYZ()", PROCEDURE(p_clone), "Note: copying object references does not create new objects. Use clone() if a new object is needed.\n\nExample:\nvar o1=(1,2,3), o2=o1, o3=o1.clone();\no1.y=9999;\n//o2 is now (1,9999,3) but o3 is still (1,2,3)", },
 		{ "set", 0, PARAM_NOSTATIC, "set (copy) coordinates from another XYZ object", "p(oXYZ)", PROCEDURE(p_set), },
 		{ "set3", 0, PARAM_NOSTATIC, "set individual 3 coordinates", "p(f x,f y,f z)", PROCEDURE(p_set3), },
@@ -32,5 +32,5 @@
 		{ "rotate", 0, PARAM_NOSTATIC, "rotate using Orient object", "p(oOrient)", PROCEDURE(p_rotate), },
 		{ "revRotate", 0, PARAM_NOSTATIC, "reverse rotate using Orient object", "p(oOrient)", PROCEDURE(p_revrotate), },
-		{ "get", 0, PARAM_NOSTATIC, "get one of coordinates", "p f(d index)", PROCEDURE(p_get), "this function makes the XYZ objects \"indexable\" (so you can use [] for accessing subsequent fields, like in Vector)", },
+		{ "get", 0, PARAM_READONLY | PARAM_NOSTATIC, "get one of coordinates", "p f(d index)", PROCEDURE(p_get), "this function makes the XYZ objects \"indexable\" (so you can use [] for accessing subsequent fields, like in Vector)", },
 		{ 0, 0, 0, },
 	};
Index: /cpp/frams/vm/classes/collectionobj.cpp
===================================================================
--- /cpp/frams/vm/classes/collectionobj.cpp	(revision 1157)
+++ /cpp/frams/vm/classes/collectionobj.cpp	(revision 1158)
@@ -31,9 +31,9 @@
 	{ "size", 0, PARAM_READONLY | PARAM_NOSTATIC, "Element count", "d", GETONLY(size), },
 	{ "remove", 0, PARAM_NOSTATIC, "Remove at position", "p(d position)", PROCEDURE(p_remove), },
-	{ "get", 0, PARAM_NOSTATIC, "Get value at position", "p x(d position)", PROCEDURE(p_get), "object[position] can be always used instead of object.get(position)" },
+	{ "get", 0, PARAM_READONLY | PARAM_NOSTATIC, "Get value at position", "p x(d position)", PROCEDURE(p_get), "object[position] can be always used instead of object.get(position)" },
 	{ "set", 0, PARAM_NOSTATIC, "Set value at position", "p(d position,x value)", PROCEDURE(p_set), "object[position]=value can be always used instead of object.set(position,value)" },
 	{ "insert", 0, PARAM_NOSTATIC, "Insert value at position", "p(d position,x value)", PROCEDURE(p_insert), },
 	{ "add", 0, PARAM_NOSTATIC, "Append at the end", "p(x value)", PROCEDURE(p_add), },
-	{ "find", 0, PARAM_NOSTATIC, "Find", "p d(x value)", PROCEDURE(p_find), "returns the element index or -1 if not found" },
+	{ "find", 0, PARAM_READONLY | PARAM_NOSTATIC, "Find", "p d(x value)", PROCEDURE(p_find), "returns the element index or -1 if not found" },
 	{ "avg", 0, PARAM_READONLY | PARAM_NOSTATIC, "Average", "x", GETONLY(avg) },
 	{ "stdev", 0, PARAM_READONLY | PARAM_NOSTATIC, "Standard deviation", "x", GETONLY(stdev), "=sqrt(sum((element[i]-avg)^2)/(size-1)) which is estimated population std.dev. from sample std.dev." },
@@ -42,5 +42,5 @@
 	{ "sort", 0, PARAM_NOSTATIC, "Sort elements (in place)", "p(oFunctionReference comparator)", PROCEDURE(p_sort), "comparator can be null, giving the \"natural\" sorting order (depending on element type), otherwise it must be a function reference obtained from the 'function' operator.\n\nExample:\nfunction compareLastDigit(a,b) {return (a%10)<(b%10);}\nvar v=[16,23,35,42,54,61];\nv.sort(function compareLastDigit);" },
 	{ "iterator", 0, PARAM_NOSTATIC | PARAM_READONLY, "Iterator", "o", GETONLY(iterator), },
-	{ "clone", 0, PARAM_NOSTATIC, "Create a clone", "p oVector()", PROCEDURE(p_clone), "The resulting clone is a shallow copy (contains the same object references as the original). A deep copy can be obtained through serialization: String.deserialize(String.serialize(object));" },
+	{ "clone", 0, PARAM_READONLY | PARAM_NOSTATIC, "Create a clone", "p oVector()", PROCEDURE(p_clone), "The resulting clone is a shallow copy (contains the same object references as the original). A deep copy can be obtained through serialization: String.deserialize(String.serialize(object));" },
 	{ 0, 0, 0, },
 };
@@ -72,7 +72,7 @@
 	{ "size", 0, PARAM_NOSTATIC | PARAM_READONLY, "Element count", "d", GETONLY(size), },
 	{ "remove", 0, PARAM_NOSTATIC, "Remove", "p(x key)", PROCEDURE(p_remove), "Removes the named or indexed element (depending on the argument type: string or int)." },
-	{ "get", 0, PARAM_NOSTATIC, "Get element", "p x(x key)", PROCEDURE(p_get), "Retrieves the named or indexed element (depending on the argument type: string or int). Accessing nonexistent keys is an error (use hasKey() if necessary).\nobject.get(key) can be shortened to object[key]." },
-	{ "getKey", 0, PARAM_NOSTATIC, "Get a key", "p s(d index)", PROCEDURE(p_getKey), "Returns the key of the indexed element (0 <= index < size)." },
-	{ "hasKey", 0, PARAM_NOSTATIC, "Check if key exists", "p d(s key)", PROCEDURE(p_hasKey), "Returns 1 (interpreted as true) if dictionary contains the supplied key, or 0 (false) otherwise.\nExample:\n   if (obj.hasKey(\"a\"))\n      x = obj->a;" },
+	{ "get", 0, PARAM_READONLY | PARAM_NOSTATIC, "Get element", "p x(x key)", PROCEDURE(p_get), "Retrieves the named or indexed element (depending on the argument type: string or int). Accessing nonexistent keys is an error (use hasKey() if necessary).\nobject.get(key) can be shortened to object[key]." },
+	{ "getKey", 0, PARAM_READONLY | PARAM_NOSTATIC, "Get a key", "p s(d index)", PROCEDURE(p_getKey), "Returns the key of the indexed element (0 <= index < size)." },
+	{ "hasKey", 0, PARAM_READONLY | PARAM_NOSTATIC, "Check if key exists", "p d(s key)", PROCEDURE(p_hasKey), "Returns 1 (interpreted as true) if dictionary contains the supplied key, or 0 (false) otherwise.\nExample:\n   if (obj.hasKey(\"a\"))\n      x = obj->a;" },
 	{ "set", 0, PARAM_NOSTATIC, "Set element", "p x(x key,x value)", PROCEDURE(p_set), "Set element value for the specified key or index (depending on the argument type: string or int).\n"
 	"Returns the value previously associated with the given key (or index).\n"
@@ -81,8 +81,8 @@
 	"  var old_value=object.set(\"key\",new_value); //'old_value' gets the value previously associated with \"key\"\n"
 	"  var x=object[\"key\"]=new_value; //'x' becomes 'new_value', consistently with the semantics of the assignment operator. The value previously associated with \"key\" is lost." },
-	{ "find", 0, PARAM_NOSTATIC, "Find", "p x(x value)", PROCEDURE(p_find), "Returns the element key or null if not found." },
+	{ "find", 0, PARAM_READONLY | PARAM_NOSTATIC, "Find", "p x(x value)", PROCEDURE(p_find), "Returns the element key or null if not found." },
 	{ "new", 0, 0, "Create a Dictionary", "p oDictionary()", STATICPROCEDURE(p_new), "Empty directory can be also created using the {} expression." },
 	{ "toString", 0, PARAM_READONLY | PARAM_NOSTATIC, "Textual form", "s", GETONLY(toString), },
-	{ "clone", 0, PARAM_NOSTATIC, "Create a clone", "p oDictionary()", PROCEDURE(p_clone), "The resulting clone is a shallow copy (contains the same object references as the original). A deep copy can be obtained through serialization: String.deserialize(String.serialize(object));" },
+	{ "clone", 0, PARAM_READONLY | PARAM_NOSTATIC, "Create a clone", "p oDictionary()", PROCEDURE(p_clone), "The resulting clone is a shallow copy (contains the same object references as the original). A deep copy can be obtained through serialization: String.deserialize(String.serialize(object));" },
 	{ "assign", 0, PARAM_NOSTATIC, "Assign from another object", "p(x)", PROCEDURE(p_assign), "Replaces current dictionary with dictionary contents from another object." },
 	{ "iterator", 0, PARAM_NOSTATIC | PARAM_READONLY, "Iterator", "o", GETONLY(iterator), },
@@ -99,5 +99,5 @@
 
 VectorObject::VectorObject(Pt3D &pt)
-	:readonly(0), owndata(1)
+	:owndata(1)
 {
 	set_or_insert(0, ExtValue(pt.x), false);
@@ -119,5 +119,4 @@
 void VectorObject::p_remove(PARAMPROCARGS)
 {
-	if (readonly) return;
 	int i = args->getInt();
 	if (!listIndexCheck(&data, i, "VectorObject", "remove")) return;
@@ -270,5 +269,5 @@
 	VMachine *vm;
 	VMVEComparator(VMachine::JumpTargetObject *_jto) :jto(_jto), vm(jto->vm) {}
-#ifdef QSORT_R_USING_QSORT_S
+#ifdef QSORT_R_THIS_FIRST
 	static int compare(void* _this, const void *a, const void *b);
 	bool operator()(const ExtValue *a, const ExtValue *b) { return compare(this,&a,&b) == ExtValue::ResultLower; }
@@ -279,5 +278,5 @@
 };
 
-#ifdef QSORT_R_USING_QSORT_S
+#ifdef QSORT_R_THIS_FIRST
 int VMVEComparator::compare(void* _this, const void *a, const void *b)
 #else
@@ -324,5 +323,5 @@
 		std::sort(first, first + data.size(), cmp); //no qsort_r() or equivalent on Android (yet)
 #else
-		qsort_r(first, data.size(), sizeof(ExtValue*), cmp.compare, &cmp);
+		CALL_QSORT_R(first, data.size(), sizeof(ExtValue*), cmp.compare, &cmp);
 #endif
 	}
Index: /cpp/frams/vm/classes/collectionobj.h
===================================================================
--- /cpp/frams/vm/classes/collectionobj.h	(revision 1157)
+++ /cpp/frams/vm/classes/collectionobj.h	(revision 1158)
@@ -16,5 +16,5 @@
 public:
 	SList data;
-	unsigned int readonly : 1, owndata : 1;
+	unsigned int owndata : 1;
 	void clear();
 	ExtValue *get(int i) { return (ExtValue*)data.get(i); }
@@ -24,9 +24,9 @@
 	static Param par;
 	VectorObject(Pt3D& pt);
-	VectorObject() :readonly(0), owndata(1) {}
+	VectorObject() :owndata(1) {}
 	~VectorObject() { clear(); }
 	static Param& getStaticParam() { return par; }
 #define STATRICKCLASS VectorObject
-	PARAMPROCDEF(p_clear) { if (readonly) return; clear(); }
+	PARAMPROCDEF(p_clear) { clear(); }
 	PARAMGETDEF(size) { arg1->setInt(data.size()); }
 	PARAMGETDEF(avg);
@@ -36,7 +36,7 @@
 	PARAMPROCDEF(p_get);
 	PARAMPROCDEF(p_find);
-	PARAMPROCDEF(p_set) { if (!readonly) set_or_insert(arg1[1].getInt(), arg1[0], false); }
-	PARAMPROCDEF(p_add) { if (readonly) return; /*ExtValue tmp; get_toString(&tmp); printf("%s += %s",(const char*)tmp.getString(),(const char*)arg1[0].getString());*/ data += new ExtValue(arg1[0]); /*get_toString(&tmp); printf(" -> %s\n",(const char*)tmp.getString());*/ arg2->setInt(data.size() - 1); }
-	PARAMPROCDEF(p_insert) { if (!readonly) set_or_insert(arg1[1].getInt(), arg1[0], true); }
+	PARAMPROCDEF(p_set) { set_or_insert(arg1[1].getInt(), arg1[0], false); }
+	PARAMPROCDEF(p_add) { data += new ExtValue(arg1[0]); arg2->setInt(data.size() - 1); }
+	PARAMPROCDEF(p_insert) { set_or_insert(arg1[1].getInt(), arg1[0], true); }
 	PARAMGETDEF(toString);
 	PARAMPROCDEF(p_sort);
Index: /cpp/frams/vm/classes/genoobj.cpp
===================================================================
--- /cpp/frams/vm/classes/genoobj.cpp	(revision 1157)
+++ /cpp/frams/vm/classes/genoobj.cpp	(revision 1158)
@@ -23,6 +23,6 @@
 	"-1 = validity is not known. This is a transient state. The value of \"is_valid\" will never be -1 when read. It is safe to treat is_valid as boolean in statements like \"if (g.is_valid) ...\". Setting \"is_valid=-1\" will make it 0 or 1 again. This third state (-1) is only needed for loading Genotype objects from files where the \"is_valid\" field might not be present."
 	},
-	{ "getConverted", 0, PARAM_NOSTATIC, "Get converted genotype", "p oGeno(s format)", PROCEDURE(p_getconvert), },
-	{ "getConvertedWithCheckpoints", 0, PARAM_NOSTATIC, "Get converted genotype", "p oGeno(s format)", PROCEDURE(p_getconvert_ch), "See also Model.newWithCheckpoints()" },
+	{ "getConverted", 0, PARAM_READONLY | PARAM_NOSTATIC, "Get converted genotype", "p oGeno(s format)", PROCEDURE(p_getconvert), },
+	{ "getConvertedWithCheckpoints", 0, PARAM_READONLY | PARAM_NOSTATIC, "Get converted genotype", "p oGeno(s format)", PROCEDURE(p_getconvert_ch), "See also Model.newWithCheckpoints()" },
 	{ "f0genotype", 0, PARAM_NOSTATIC | PARAM_READONLY, "f0 genotype", "s 1", GETONLY(f0genotype), "converted to f0 genotype", },
 	{ "new", 0, 0, "create new empty object", "p oGeno()", PROCEDURE(p_new), },
Index: /framspy/deterministic.sim
===================================================================
--- /framspy/deterministic.sim	(revision 1157)
+++ /framspy/deterministic.sim	(revision 1158)
@@ -10,8 +10,8 @@
 
 sim_params:
+# no need to evaluate a creature many times:
+evalcount:1
 # always central location in the world:
 placement:1
-# no need to evaluate many times:
-evalcount:1
 # no random initialization of state in neurons:
 randinit:0.0
Index: /framspy/frams-test-props.py
===================================================================
--- /framspy/frams-test-props.py	(revision 1157)
+++ /framspy/frams-test-props.py	(revision 1158)
@@ -1,2 +1,4 @@
+"""An example of iterating through the properties of an ExtValue object and printing their characteristics."""
+
 import frams
 import sys
@@ -4,43 +6,36 @@
 frams.init(*(sys.argv[1:]))
 
-def printSingleProperty(v,p):
-	print('  %s "%s" type="%s" flags=%d group=%d help="%s"' % (v._propId(p),v._propName(p),v._propType(p),v._propFlags(p),v._propGroup(p),v._propHelp(p)))
+
+def printSingleProperty(v, p):
+	print(' * %s "%s" type="%s" flags=%d group=%d help="%s"' % (v._propId(p), v._propName(p), v._propType(p), v._propFlags(p), v._propGroup(p), v._propHelp(p)))
 
 
 def printFramsProperties(v):
-	N=v._propCount()
-	G=v._groupCount()
-	if G<2:
-		
-		#no groups, simply iterate all props
-		
-		print("'%s' has %d properties" % (v._class(),v._propCount()))
+	N = v._propCount()
+	G = v._groupCount()
+	print("======================= '%s' has %d properties in %d group(s). =======================" % (v._class(), N, G))
+	if G < 2:
+		# No groups, simply iterate all properties
 		for p in range(v._propCount()):
-			printSingleProperty(v,p)
-		
+			printSingleProperty(v, p)
 	else:
-		
-		#iterate through groups and iterate all props in a group.
-		#why the distinction?
-		#first, just to show there are two ways. there is always at least one
-		#group so you can always get all props by iterating the group.
-		#second, groups actually do not exist as collections. iterating in
-		#groups works by checking all properties on each iteration and
-		#testing which one is the m-th property of the group.
-		#these inefficient _memberCount() and _groupMember() are provided
-		#for the sake of completeness but don't use them without good reason ;-)
-		
-		print("'%s' has %d properties in %d group(s)" % (v._class(),v._propCount(),v._groupCount()))
+		# Iterate through groups and iterate all props in a group.
+		# Why the distinction?
+		# First, just to show there are two ways. There is always at least one
+		# group so you can always get all properties by iterating the group.
+		# Second, groups actually do not exist as collections. Iterating in
+		# groups works by checking all properties on each iteration and
+		# testing which one is the m-th property of the group!
+		# So these inefficient _memberCount() and _groupMember() are provided
+		# for the sake of completeness, but don't use them without a good reason ;-)
 		for g in range(G):
-			print('\n=========== Group #%d: %s ==========' % (g,v._groupName(g)))
+			print('\n------------------- Group #%d: %s -------------------' % (g, v._groupName(g)))
 			for m in range(v._memberCount(g)):
-				p=v._groupMember(g,m)
-				printSingleProperty(v,p)
-			print('========================\n')
-
-
+				p = v._groupMember(g, m)
+				printSingleProperty(v, p)
+	print('\n\n')
 
 
 printFramsProperties(frams.World)
-		
-printFramsProperties(frams.GenePools[0].add('X'))
+
+printFramsProperties(frams.GenePools[0].add('X'))  # add('X') returns a Genotype object
Index: /framspy/run-evolalg-examples.cmd
===================================================================
--- /framspy/run-evolalg-examples.cmd	(revision 1157)
+++ /framspy/run-evolalg-examples.cmd	(revision 1158)
@@ -7,13 +7,14 @@
 
 
-rem simple one-criterion evolution
+
+rem simple one-criterion evolution from minimalistic example source (examples.standard)
 python -m evolalg.examples.standard          -path %DIR_WITH_FRAMS_LIBRARY%   -opt numneurons
 
 
-rem "chaining" .sim files, subsequent files overwrite selected parameters
+rem as above but "chaining" .sim files, subsequent files overwrite selected parameters
 python -m evolalg.examples.standard          -path %DIR_WITH_FRAMS_LIBRARY%   -sim eval-allcriteria.sim;deterministic.sim;sample-period-longest.sim    -opt velocity
 
 
-rem hard limit on the number of parts
+rem simple one-criterion evolution but more options available in examples.niching_novelty, here: hard limit on the number of parts and debugging messages
 python -m evolalg.examples.niching_novelty   -path %DIR_WITH_FRAMS_LIBRARY%   -opt velocity   -max_numparts 6   -debug
 
Index: /framspy/sample-period-longest.sim
===================================================================
--- /framspy/sample-period-longest.sim	(revision 1157)
+++ /framspy/sample-period-longest.sim	(revision 1158)
@@ -1,4 +1,4 @@
-# Extremely high "performance sampling period" (likely longer than lifespan) in the first population
-# to measure distance and velocity rectilinearly. Such a long period results in two sampling events: at birth and at death.
+# Extremely high "performance sampling period" in the first population to measure distance and velocity rectilinearly.
+# Such a long period (likely longer than lifespan) results in two sampling events: at birth and at death.
 #
 # Before loading this file, load base settings, e.g. "eval-allcriteria.sim".
