diff --git a/db.h b/db.h
index 22cd193..00adccc 100644
--- a/db.h
+++ b/db.h
@@ -18,10 +18,32 @@
 #ifndef DB_h
 #define DB_h 1
 
+#include <stdbool.h>
+
 #include "config.h"
 #include "program.h"
 #include "structures.h"
 
+static inline bool
+is_list_of_objs(Var v)
+{
+    int i;
+
+    if (TYPE_LIST != v.type)
+	return false;
+
+    for (i = 1; i <= v.v.list[0].v.num; i++)
+	if (TYPE_OBJ != v.v.list[i].type)
+	    return false;
+
+    return true;
+}
+
+static inline bool
+is_obj_or_list_of_objs(Var v)
+{
+    return is_obj(v) || is_list_of_objs(v);
+}
 
 /**** file system ****/
 
@@ -84,18 +106,22 @@ extern Objid db_create_object(void);
 
 extern Objid db_last_used_objid(void);
 extern void db_reset_last_used_objid(void);
+extern void db_set_last_used_objid(Objid);
 				/* The former returns the highest object number
 				 * ever returned by db_create_object().  The
-				 * latter resets that high-water mark to the
+				 * second resets that high-water mark to the
 				 * highest object number referring to a
-				 * currently-valid object.
+				 * currently-valid object.  The third sets that
+				 * high-water mark to the specified object
+				 * number, or to the highest object number
+				 * referring to a currently-valid object.
 				 */
 
 extern void db_destroy_object(Objid);
 				/* Destroys object, freeing all associated
-				 * storage.  The object's parent and location
-				 * must == #-1 and it must not have any
-				 * children or contents.
+				 * storage.  The object must not have parents,
+				 * children or contents, and the location
+				 * must == #-1.
 				 */
 
 extern Objid db_renumber_object(Objid);
@@ -112,6 +138,38 @@ extern int db_object_bytes(Objid);
 				 * properties.
 				 */
 
+extern Var db_ancestors(Objid, bool);
+				/* Returns a list of the ancestors of the
+				 * given object.  db_ancestors() does not/
+				 * can not free the returned list.  The caller
+				 * should therefore free it once it has finished
+				 * operating on it.
+				 */
+
+extern Var db_descendants(Objid, bool);
+				/* Returns a list of the descendants of the
+				 * given object.  db_descendants() does not/
+				 * can not free the returned list.  The caller
+				 * should therefore free it once it has finished
+				 * operating on it.
+				 */
+
+extern Var db_all_locations(Objid, bool);
+				/* Returns a list of all objects that
+				 * contain the given object.
+				 * db_all_locations() does not/can not free the
+				 * returned list.  The caller should therefore
+				 * free it once it has finished operating on it.
+				 */
+
+extern Var db_all_contents(Objid, bool);
+				/* Returns a list of all objects that are
+				 * contained by the given object.
+				 * db_all_contents() does not/can not free the
+				 * returned list.  The caller should therefore
+				 * free it once it has finished operating on it.
+				 */
+
 /**** object attributes ****/
 
 extern Objid db_object_owner(Objid);
@@ -125,7 +183,7 @@ extern void db_set_object_name(Objid oid, const char *name);
 				 * reference is to be persistent.
 				 */
 
-extern Objid db_object_parent(Objid);
+extern Var db_object_parent(Objid);
 extern int db_count_children(Objid);
 extern int db_for_all_children(Objid,
 			       int (*)(void *, Objid),
@@ -138,7 +196,7 @@ extern int db_for_all_children(Objid,
 				 *      db_renumber_object()
 				 *      db_change_parent()
 				 */
-extern int db_change_parent(Objid oid, Objid parent);
+extern int db_change_parent(Objid oid, Var parents);
 				/* db_change_parent() returns true (and
 				 * actually changes the parent of OID) iff
 				 * neither OID nor any of its descendents
diff --git a/db_file.c b/db_file.c
index 87ce5d6..0e3beff 100644
--- a/db_file.c
+++ b/db_file.c
@@ -37,16 +37,163 @@
 #include "str_intern.h"
 #include "tasks.h"
 #include "timers.h"
+#include "utils.h"
 #include "version.h"
 
 static char *input_db_name, *dump_db_name;
 static int dump_generation = 0;
 static const char *header_format_string
-= "** LambdaMOO Database, Format Version %u **\n";
+  = "** LambdaMOO Database, Format Version %u **\n";
 
 DB_Version dbio_input_version;
 
 
+/*********** Format version 4 support ***********/
+
+/*
+ * This structure is compatible with the popular database format
+ * version 4.
+ */
+typedef struct Object4 {
+    Objid id;
+    Objid owner;
+    Objid location;
+    Objid contents;
+    Objid next;
+
+    Objid parent;
+    Objid child;
+    Objid sibling;
+
+    const char *name;
+    int flags;
+
+    Verbdef *verbdefs;
+    Proplist propdefs;
+    Pval *propval;
+} Object4;
+
+static Object4 **objects;
+static int num_objects = 0;
+static int max_objects = 0;
+
+static void
+ensure_new_object(void)
+{
+    if (max_objects == 0) {
+	max_objects = 100;
+	objects = mymalloc(max_objects * sizeof(Object4 *), M_OBJECT_TABLE);
+    }
+    if (num_objects >= max_objects) {
+	int i;
+	Object4 **new;
+
+	new = mymalloc(max_objects * 2 * sizeof(Object4 *), M_OBJECT_TABLE);
+	for (i = 0; i < max_objects; i++)
+	    new[i] = objects[i];
+	myfree(objects, M_OBJECT_TABLE);
+	objects = new;
+	max_objects *= 2;
+    }
+}
+
+static Object4 *
+dbv4_new_object(void)
+{
+    Object4 *o;
+
+    ensure_new_object();
+    o = objects[num_objects] = mymalloc(sizeof(Object4), M_OBJECT);
+    o->id = num_objects;
+    num_objects++;
+
+    return o;
+}
+
+static void
+dbv4_new_recycled_object(void)
+{
+    ensure_new_object();
+    objects[num_objects++] = 0;
+}
+
+static Object4 *
+dbv4_find_object(Objid oid)
+{
+    if (oid < 0 || oid >= num_objects)
+	return 0;
+    else
+	return objects[oid];
+}
+
+static int
+dbv4_valid(Objid oid)
+{
+    return dbv4_find_object(oid) != 0;
+}
+
+static Objid
+dbv4_last_used_objid(void)
+{
+    return num_objects - 1;
+}
+
+static int
+dbv4_count_properties(Objid oid)
+{
+    Object4 *o;
+    int nprops = 0;
+
+    for (o = dbv4_find_object(oid); o; o = dbv4_find_object(o->parent))
+	nprops += o->propdefs.cur_length;
+
+    return nprops;
+}
+
+typedef struct {
+    Objid definer;
+    Verbdef *verbdef;
+} handle;
+
+static db_verb_handle
+dbv4_find_indexed_verb(Objid oid, unsigned index)
+{
+    Object4 *o = dbv4_find_object(oid);
+    Verbdef *v;
+    unsigned i;
+    static handle h;
+    db_verb_handle vh;
+
+    for (v = o->verbdefs, i = 0; v; v = v->next)
+	if (++i == index) {
+	    h.definer = o->id;
+	    h.verbdef = v;
+	    vh.ptr = &h;
+
+	    return vh;
+	}
+    vh.ptr = 0;
+
+    return vh;
+}
+
+/*
+ * The following functions work with both the version 4 database and
+ * the latest database version.  If that changes they will need to be
+ * replaced.
+ *
+ * dbpriv_new_propdef
+ * dbpriv_build_prep_table
+ * db_verb_definer
+ * db_verb_names
+ * db_set_verb_program
+ * dbpriv_set_all_users
+ * db_all_users
+ * dbpriv_dbio_failed
+ * dbpriv_set_dbio_output
+ * dbpriv_set_dbio_input
+ */
+
 /*********** Verb and property I/O ***********/
 
 static void
@@ -102,26 +249,26 @@ write_propval(Pval * p)
 /*********** Object I/O ***********/
 
 static int
-read_object(void)
+v4_read_object(void)
 {
     Objid oid;
-    Object *o;
+    Object4 *o;
     char s[20];
     int i;
     Verbdef *v, **prevv;
     int nprops;
 
-    if (dbio_scanf("#%d", &oid) != 1 || oid != db_last_used_objid() + 1)
+    if (dbio_scanf("#%d", &oid) != 1 || oid != dbv4_last_used_objid() + 1)
 	return 0;
     dbio_read_line(s, sizeof(s));
 
     if (strcmp(s, " recycled\n") == 0) {
-	dbpriv_new_recycled_object();
+	dbv4_new_recycled_object();
 	return 1;
     } else if (strcmp(s, "\n") != 0)
 	return 0;
 
-    o = dbpriv_new_object();
+    o = dbv4_new_object();
     o->name = dbio_read_string_intern();
     (void) dbio_read_string();	/* discard old handles string */
     o->flags = dbio_read_num();
@@ -168,19 +315,83 @@ read_object(void)
     return 1;
 }
 
-static void
-write_object(Objid oid)
+static int
+ng_read_object(void)
 {
+    Objid oid;
     Object *o;
+    char s[20];
+    int i;
+    Verbdef *v, **prevv;
+    int nprops;
+
+    if (dbio_scanf("#%d", &oid) != 1 || oid != db_last_used_objid() + 1)
+	return 0;
+    dbio_read_line(s, sizeof(s));
+
+    if (strcmp(s, " recycled\n") == 0) {
+	dbpriv_new_recycled_object();
+	return 1;
+    } else if (strcmp(s, "\n") != 0)
+	return 0;
+
+    o = dbpriv_new_object();
+    o->name = dbio_read_string_intern();
+    o->flags = dbio_read_num();
+
+    o->owner = dbio_read_objid();
+
+    o->location = dbio_read_var();
+    o->contents = dbio_read_var();
+
+    o->parents = dbio_read_var();
+    o->children = dbio_read_var();
+
+    o->verbdefs = 0;
+    prevv = &(o->verbdefs);
+    for (i = dbio_read_num(); i > 0; i--) {
+	v = mymalloc(sizeof(Verbdef), M_VERBDEF);
+	read_verbdef(v);
+	*prevv = v;
+	prevv = &(v->next);
+    }
+
+    o->propdefs.cur_length = 0;
+    o->propdefs.max_length = 0;
+    o->propdefs.l = 0;
+    if ((i = dbio_read_num()) != 0) {
+	o->propdefs.l = mymalloc(i * sizeof(Propdef), M_PROPDEF);
+	o->propdefs.cur_length = i;
+	o->propdefs.max_length = i;
+	for (i = 0; i < o->propdefs.cur_length; i++)
+	    o->propdefs.l[i] = read_propdef();
+    }
+    nprops = dbio_read_num();
+    if (nprops)
+	o->propval = mymalloc(nprops * sizeof(Pval), M_PVAL);
+    else
+	o->propval = 0;
+
+    for (i = 0; i < nprops; i++) {
+	read_propval(o->propval + i);
+    }
+
+    return 1;
+}
+
+static void
+v4_write_object(Objid oid)
+{
+    Object4 *o;
     Verbdef *v;
     int i;
     int nverbdefs, nprops;
 
-    if (!valid(oid)) {
+    if (!dbv4_valid(oid)) {
 	dbio_printf("#%d recycled\n", oid);
 	return;
     }
-    o = dbpriv_find_object(oid);
+    o = dbv4_find_object(oid);
 
     dbio_printf("#%d\n", oid);
     dbio_write_string(o->name);
@@ -208,6 +419,50 @@ write_object(Objid oid)
     for (i = 0; i < o->propdefs.cur_length; i++)
 	write_propdef(&o->propdefs.l[i]);
 
+    nprops = dbv4_count_properties(oid);
+
+    dbio_write_num(nprops);
+    for (i = 0; i < nprops; i++)
+	write_propval(o->propval + i);
+}
+
+static void
+ng_write_object(Objid oid)
+{
+    Object *o;
+    Verbdef *v;
+    int i;
+    int nverbdefs, nprops;
+
+    if (!valid(oid)) {
+	dbio_printf("#%d recycled\n", oid);
+	return;
+    }
+    o = dbpriv_find_object(oid);
+
+    dbio_printf("#%d\n", oid);
+    dbio_write_string(o->name);
+    dbio_write_num(o->flags);
+
+    dbio_write_objid(o->owner);
+
+    dbio_write_var(o->location);
+    dbio_write_var(o->contents);
+
+    dbio_write_var(o->parents);
+    dbio_write_var(o->children);
+
+    for (v = o->verbdefs, nverbdefs = 0; v; v = v->next)
+	nverbdefs++;
+
+    dbio_write_num(nverbdefs);
+    for (v = o->verbdefs; v; v = v->next)
+	write_verbdef(v);
+
+    dbio_write_num(o->propdefs.cur_length);
+    for (i = 0; i < o->propdefs.cur_length; i++)
+	write_propdef(&o->propdefs.l[i]);
+
     nprops = dbpriv_count_properties(oid);
 
     dbio_write_num(nprops);
@@ -219,10 +474,10 @@ write_object(Objid oid)
 /*********** File-level Input ***********/
 
 static int
-validate_hierarchies()
+v4_validate_hierarchies()
 {
     Objid oid, log_oid;
-    Objid size = db_last_used_objid() + 1;
+    Objid size = dbv4_last_used_objid() + 1;
     int broken = 0;
     int fixed_nexts = 0;
 
@@ -239,7 +494,7 @@ validate_hierarchies()
 
     oklog("VALIDATE: Phase 1: Check for invalid objects ...\n");
     for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
-	Object *o = dbpriv_find_object(oid);
+	Object4 *o = dbv4_find_object(oid);
 
 	MAYBE_LOG_PROGRESS;
 	if (o) {
@@ -250,7 +505,7 @@ validate_hierarchies()
 #	    define CHECK(field, name) 					\
 	    {								\
 	        if (o->field != NOTHING					\
-		    && !dbpriv_find_object(o->field)) {			\
+		    && !dbv4_find_object(o->field)) {			\
 		    errlog("VALIDATE: #%d.%s = #%d <invalid> ... fixed.\n", \
 			   oid, name, o->field);			\
 		    o->field = NOTHING;				  	\
@@ -274,7 +529,7 @@ validate_hierarchies()
 
     oklog("VALIDATE: Phase 2: Check for cycles ...\n");
     for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
-	Object *o = dbpriv_find_object(oid);
+	Object4 *o = dbv4_find_object(oid);
 
 	MAYBE_LOG_PROGRESS;
 	if (o) {
@@ -283,7 +538,7 @@ validate_hierarchies()
 	        Objid	oid2 = start;					\
 		int	count = 0;					\
 	        for (; oid2 != NOTHING					\
-		     ; oid2 = dbpriv_find_object(oid2)->field) {	\
+		     ; oid2 = dbv4_find_object(oid2)->field) {	\
 		    if (++count > size)	{				\
 			errlog("VALIDATE: Cycle in `%s' chain of #%d\n",\
 			       name, oid);				\
@@ -307,7 +562,7 @@ validate_hierarchies()
 
     oklog("VALIDATE: Phase 3: Check for inconsistencies ...\n");
     for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
-	Object *o = dbpriv_find_object(oid);
+	Object4 *o = dbv4_find_object(oid);
 
 	MAYBE_LOG_PROGRESS;
 	if (o) {
@@ -318,9 +573,9 @@ validate_hierarchies()
 									\
 		/* Is oid in its up's down list? */			\
 		if (up != NOTHING) {					\
-		    for (oid2 = dbpriv_find_object(up)->down;		\
+		    for (oid2 = dbv4_find_object(up)->down;		\
 			 oid2 != NOTHING;				\
-			 oid2 = dbpriv_find_object(oid2)->across) {	\
+			 oid2 = dbv4_find_object(oid2)->across) {	\
 			if (oid2 == oid) /* found it */			\
 			    break;					\
 		    }							\
@@ -343,8 +598,8 @@ validate_hierarchies()
 									\
 		for (oid2 = o->down;					\
 		     oid2 != NOTHING;					\
-		     oid2 = dbpriv_find_object(oid2)->across) {		\
-		    if (dbpriv_find_object(oid2)->up != oid) {		\
+		     oid2 = dbv4_find_object(oid2)->across) {		\
+		    if (dbv4_find_object(oid2)->up != oid) {		\
 			errlog(						\
 			    "VALIDATE: #%d erroneously on #%d's %s list.\n", \
 			    oid2, oid, down_name);			\
@@ -360,10 +615,239 @@ validate_hierarchies()
 	}
     }
 
+#   undef PROGRESS_INTERVAL
+#   undef MAYBE_LOG_PROGRESS
+
+    oklog("VALIDATING the object hierarchies ... finished.\n");
+    return !broken;
+}
+
+static int
+ng_validate_hierarchies()
+{
+    Objid oid, log_oid;
+    Objid size = db_last_used_objid() + 1;
+    int i, c;
+    int broken = 0;
+
+    oklog("VALIDATING the object hierarchies ...\n");
+
+#   define PROGRESS_INTERVAL 10000
+#   define MAYBE_LOG_PROGRESS					\
+    {								\
+        if (oid == log_oid) {					\
+	    log_oid += PROGRESS_INTERVAL;			\
+	    oklog("VALIDATE: Done through #%d ...\n", oid);	\
+	}							\
+    }
+
+    oklog("VALIDATE: Phase 1: Check for invalid objects ...\n");
+    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
+	Object *o = dbpriv_find_object(oid);
+	MAYBE_LOG_PROGRESS;
+	if (o) {
+	    if (!is_obj_or_list_of_objs(o->parents)) {
+		errlog("VALIDATE: #%d.parents is not an object or list of objects.\n",
+		       oid);
+		broken = 1;
+	    }
+	    if (!is_list_of_objs(o->children)) {
+		errlog("VALIDATE: #%d.children is not a list of objects.\n",
+		       oid);
+		broken = 1;
+	    }
+	    if (!is_obj(o->location)) {
+		errlog("VALIDATE: #%d.location is not an object.\n",
+		       oid);
+		broken = 1;
+	    }
+	    if (!is_list_of_objs(o->contents)) {
+		errlog("VALIDATE: #%d.contents is not a list of objects.\n",
+		       oid);
+		broken = 1;
+	    }
+#	    define CHECK(field, name)					\
+	    {								\
+		if (TYPE_LIST == o->field.type) {			\
+		    Var tmp;						\
+		    FOR_EACH(tmp, o->field, i, c) {			\
+			if (tmp.v.obj != NOTHING			\
+			    && !dbpriv_find_object(tmp.v.obj)) {	\
+			    errlog("VALIDATE: #%d.%s = #%d <invalid> ... removed.\n", \
+			           oid, name, tmp);			\
+			    o->field = setremove(o->field, tmp);	\
+			}						\
+		    }							\
+		}							\
+		else {							\
+		    if (o->field.v.obj != NOTHING			\
+		        && !dbpriv_find_object(o->field.v.obj)) {	\
+			errlog("VALIDATE: #%d.%s = #%d <invalid> ... fixed.\n", \
+			       oid, name, o->field.v.obj);		\
+			o->field.v.obj = NOTHING;			\
+		    }							\
+		}							\
+	    }
+
+	    if (!broken) {
+		CHECK(parents, "parent");
+		CHECK(children, "child");
+		CHECK(location, "location");
+		CHECK(contents, "content");
+	    }
+
+#	    undef CHECK
+	}
+    }
+
+    if (broken)		/* Can't continue if invalid objects found */
+	return 0;
+
+    oklog("VALIDATE: Phase 2: Check for cycles ...\n");
+    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
+	Object *o = dbpriv_find_object(oid);
+	MAYBE_LOG_PROGRESS;
+	if (o) {
+#           define CHECK(start, func, name)				\
+	    {								\
+		Var tmp;						\
+		tmp.type = TYPE_OBJ;					\
+		tmp.v.obj = start;					\
+		Var all = func(start, false);				\
+		if (ismember(tmp, all, 1)) {				\
+			errlog("VALIDATE: Cycle in %s chain of #%d.\n",	\
+			       name, oid);				\
+			broken = 1;					\
+		}							\
+		free_var(all);						\
+	    }
+
+	    CHECK(oid, db_ancestors, "parent");
+	    CHECK(oid, db_all_locations, "location");
+
+#	    undef CHECK
+	}
+    }
+
+    if (broken)		/* Can't continue if cycles found */
+	return 0;
+
+    oklog("VALIDATE: Phase 3: Check for inconsistencies ...\n");
+    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
+	Object *o = dbpriv_find_object(oid);
+	MAYBE_LOG_PROGRESS;
+	if (o) {
+#	    define CHECK(up, up_name, down, down_name)			\
+	    {								\
+		Var tmp, t1, t2, obj;					\
+		obj.type = TYPE_OBJ;					\
+		obj.v.obj = oid;					\
+		t1 = enlist_var(var_ref(o->up));			\
+		FOR_EACH(tmp, t1, i, c) {				\
+		    if (tmp.v.obj != NOTHING) {				\
+			Object *otmp = dbpriv_find_object(tmp.v.obj);	\
+			t2 = enlist_var(var_ref(otmp->down));		\
+			if (ismember(obj, t2, 1)) {			\
+			    free_var(t2);				\
+			    continue;					\
+			}						\
+			else {						\
+			    errlog("VALIDATE: #%d not in it's %s's (#%d) %s.\n", \
+			           oid, up_name, otmp->id, down_name); \
+			    free_var(t2);				\
+			    broken = 1;					\
+			    break;					\
+			}						\
+		    }							\
+		}							\
+		free_var(t1);						\
+	    }
+
+	    CHECK(location, "location", contents, "contents");
+	    CHECK(contents, "content", location, "location");
+	    CHECK(parents, "parent", children, "children");
+	    CHECK(children, "child", parents, "parents");
+
+#	    undef CHECK
+	}
+    }
+
+#   undef PROGRESS_INTERVAL
+#   undef MAYBE_LOG_PROGRESS
+
     oklog("VALIDATING the object hierarchies ... finished.\n");
     return !broken;
 }
 
+static int
+v4_upgrade_objects()
+{
+    Objid oid, log_oid;
+    Objid size = dbv4_last_used_objid() + 1;
+
+    oklog("UPGRADING objects to new structure ...\n");
+
+#   define PROGRESS_INTERVAL 10000
+#   define MAYBE_LOG_PROGRESS					\
+    {								\
+        if (oid == log_oid) {					\
+	    log_oid += PROGRESS_INTERVAL;			\
+	    oklog("UPGRADE: Done through #%d ...\n", oid);	\
+	}							\
+    }
+
+    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
+	Object4 *o = dbv4_find_object(oid);
+
+	MAYBE_LOG_PROGRESS;
+	if (o) {
+	    Object *new = dbpriv_new_object();
+	    new->name = o->name;
+	    new->flags = o->flags;
+
+	    new->owner = o->owner;
+
+	    Objid iter;
+
+	    new->parents = var_dup(new_obj(o->parent));
+
+	    new->children = new_list(0);
+	    for (iter = o->child; iter != NOTHING; iter = objects[iter]->sibling)
+		new->children = listappend(new->children, var_dup(new_obj(iter)));
+
+	    new->location = var_dup(new_obj(o->location));
+
+	    new->contents = new_list(0);
+	    for (iter = o->contents; iter != NOTHING; iter = objects[iter]->next)
+		new->contents = listappend(new->contents, var_dup(new_obj(iter)));
+
+	    new->verbdefs = o->verbdefs;
+	    new->propdefs = o->propdefs;
+	    new->propval = o->propval;
+	}
+	else {
+	    dbpriv_new_recycled_object();
+	}
+    }
+
+#   undef PROGRESS_INTERVAL
+#   undef MAYBE_LOG_PROGRESS
+
+    for (oid = 0; oid < size; oid++) {
+	if (objects[oid]) {
+	    myfree(objects[oid], M_OBJECT);
+	    objects[oid] = 0;
+	}
+    }
+
+    num_objects = 0;
+    max_objects = 0;
+    myfree(objects, M_OBJECT_TABLE);
+
+    oklog("UPGRADING objects to new structure ... finished.\n");
+    return 1;
+}
+
 static const char *
 fmt_verb_name(void *data)
 {
@@ -395,17 +879,19 @@ read_db_file(void)
 	       dbio_input_version);
 	return 0;
     }
+
     /* I use a `dummy' variable here and elsewhere instead of the `*'
      * assignment-suppression syntax of `scanf' because it allows more
-     * straightforward error checking; unfortunately, the standard says that
-     * suppressed assignments are not counted in determining the returned value
-     * of `scanf'...
+     * straightforward error checking; unfortunately, the standard
+     * says that suppressed assignments are not counted in determining
+     * the returned value of `scanf'...
      */
     if (dbio_scanf("%d\n%d\n%d\n%d\n",
 		   &nobjs, &nprogs, &dummy, &nusers) != 4) {
 	errlog("READ_DB_FILE: Bad header\n");
 	return 0;
     }
+
     user_list = new_list(nusers);
     for (i = 1; i <= nusers; i++) {
 	user_list.v.list[i].type = TYPE_OBJ;
@@ -413,35 +899,66 @@ read_db_file(void)
     }
     dbpriv_set_all_users(user_list);
 
-    oklog("LOADING: Reading %d objects...\n", nobjs);
+    oklog("LOADING: Reading %d objects ...\n", nobjs);
     for (i = 1; i <= nobjs; i++) {
-	if (!read_object()) {
-	    errlog("READ_DB_FILE: Bad object #%d.\n", i - 1);
-	    return 0;
+	if (DBV_NextGen > dbio_input_version) {
+	    if (!v4_read_object()) {
+		errlog("READ_DB_FILE: Bad object #%d.\n", i - 1);
+		return 0;
+	    }
+	}
+	else {
+	    if (!ng_read_object()) {
+		errlog("READ_DB_FILE: Bad object #%d.\n", i - 1);
+		return 0;
+	    }
 	}
 	if (i % 10000 == 0 || i == nobjs)
 	    oklog("LOADING: Done reading %d objects ...\n", i);
     }
 
-    if (!validate_hierarchies()) {
-	errlog("READ_DB_FILE: Errors in object hierarchies.\n");
-	return 0;
+    if (DBV_NextGen > dbio_input_version) {
+	if (!v4_validate_hierarchies()) {
+	    errlog("READ_DB_FILE: Errors in object hierarchies.\n");
+	    return 0;
+	}
+    }
+    else {
+	if (!ng_validate_hierarchies()) {
+	    errlog("READ_DB_FILE: Errors in object hierarchies.\n");
+	    return 0;
+	}
     }
-    oklog("LOADING: Reading %d MOO verb programs...\n", nprogs);
+
+    oklog("LOADING: Reading %d MOO verb programs ...\n", nprogs);
     for (i = 1; i <= nprogs; i++) {
 	if (dbio_scanf("#%d:%d\n", &oid, &vnum) != 2) {
 	    errlog("READ_DB_FILE: Bad program header, i = %d.\n", i);
 	    return 0;
 	}
-	if (!valid(oid)) {
-	    errlog("READ_DB_FILE: Verb for non-existant object: #%d:%d.\n",
-		   oid, vnum);
-	    return 0;
+	if (DBV_NextGen > dbio_input_version) {
+	    if (!dbv4_valid(oid)) {
+		errlog("READ_DB_FILE: Verb for non-existant object: #%d:%d.\n",
+		       oid, vnum);
+		return 0;
+	    }
+	    h = dbv4_find_indexed_verb(oid, vnum + 1);	/* DB file is 0-based. */
+	    if (!h.ptr) {
+		errlog("READ_DB_FILE: Unknown verb index: #%d:%d.\n", oid, vnum);
+		return 0;
+	    }
 	}
-	h = db_find_indexed_verb(oid, vnum + 1);	/* DB file is 0-based. */
-	if (!h.ptr) {
-	    errlog("READ_DB_FILE: Unknown verb index: #%d:%d.\n", oid, vnum);
-	    return 0;
+	else {
+	    if (!valid(oid)) {
+		errlog("READ_DB_FILE: Verb for non-existant object: #%d:%d.\n",
+		       oid, vnum);
+		return 0;
+	    }
+	    h = db_find_indexed_verb(oid, vnum + 1);	/* DB file is 0-based. */
+	    if (!h.ptr) {
+		errlog("READ_DB_FILE: Unknown verb index: #%d:%d.\n", oid, vnum);
+		return 0;
+	    }
 	}
 	program = dbio_read_program(dbio_input_version, fmt_verb_name, &h);
 	if (!program) {
@@ -450,19 +967,28 @@ read_db_file(void)
 	}
 	db_set_verb_program(h, program);
 	if (i % 5000 == 0 || i == nprogs)
-	    oklog("LOADING: Done reading %d verb programs...\n", i);
+	    oklog("LOADING: Done reading %d verb programs ...\n", i);
     }
 
-    oklog("LOADING: Reading forked and suspended tasks...\n");
+    oklog("LOADING: Reading forked and suspended tasks ...\n");
     if (!read_task_queue()) {
 	errlog("READ_DB_FILE: Can't read task queue.\n");
 	return 0;
     }
-    oklog("LOADING: Reading list of formerly active connections...\n");
+
+    oklog("LOADING: Reading list of formerly active connections ...\n");
     if (!read_active_connections()) {
 	errlog("DB_READ: Can't read active connections.\n");
 	return 0;
     }
+
+    if (DBV_NextGen > dbio_input_version) {
+	if (!v4_upgrade_objects()) {
+	    errlog("READ_DB_FILE: Errors upgrading objects.\n");
+	    return 0;
+	}
+    }
+
     return 1;
 }
 
@@ -489,41 +1015,48 @@ write_db_file(const char *reason)
 
     user_list = db_all_users();
 
-    TRY
+    TRY {
 	dbio_printf(header_format_string, current_version);
-    dbio_printf("%d\n%d\n%d\n%d\n",
-		max_oid + 1, nprogs, 0, user_list.v.list[0].v.num);
-    for (i = 1; i <= user_list.v.list[0].v.num; i++)
-	dbio_write_objid(user_list.v.list[i].v.obj);
-    oklog("%s: Writing %d objects...\n", reason, max_oid + 1);
-    for (oid = 0; oid <= max_oid; oid++) {
-	write_object(oid);
-	if ((oid + 1) % 10000 == 0 || oid == max_oid)
-	    oklog("%s: Done writing %d objects...\n", reason, oid + 1);
-    }
-    oklog("%s: Writing %d MOO verb programs...\n", reason, nprogs);
-    for (i = 0, oid = 0; oid <= max_oid; oid++)
-	if (valid(oid)) {
-	    int vcount = 0;
-
-	    for (v = dbpriv_find_object(oid)->verbdefs; v; v = v->next) {
-		if (v->program) {
-		    dbio_printf("#%d:%d\n", oid, vcount);
-		    dbio_write_program(v->program);
-		    if (++i % 5000 == 0 || i == nprogs)
-			oklog("%s: Done writing %d verb programs...\n",
-			      reason, i);
+
+	dbio_printf("%d\n%d\n%d\n%d\n",
+	            max_oid + 1, nprogs, 0, user_list.v.list[0].v.num);
+
+	for (i = 1; i <= user_list.v.list[0].v.num; i++)
+	    dbio_write_objid(user_list.v.list[i].v.obj);
+
+	oklog("%s: Writing %d objects ...\n", reason, max_oid + 1);
+	for (oid = 0; oid <= max_oid; oid++) {
+	    ng_write_object(oid);
+	    if ((oid + 1) % 10000 == 0 || oid == max_oid)
+		oklog("%s: Done writing %d objects ...\n", reason, oid + 1);
+	}
+
+	oklog("%s: Writing %d MOO verb programs ...\n", reason, nprogs);
+	for (i = 0, oid = 0; oid <= max_oid; oid++) {
+	    if (valid(oid)) {
+		int vcount = 0;
+		for (v = dbpriv_find_object(oid)->verbdefs; v; v = v->next) {
+		    if (v->program) {
+			dbio_printf("#%d:%d\n", oid, vcount);
+			dbio_write_program(v->program);
+			if (++i % 5000 == 0 || i == nprogs)
+			    oklog("%s: Done writing %d verb programs ...\n",
+			          reason, i);
+		    }
+		    vcount++;
 		}
-		vcount++;
 	    }
 	}
-    oklog("%s: Writing forked and suspended tasks...\n", reason);
-    write_task_queue();
-    oklog("%s: Writing list of formerly active connections...\n", reason);
-    write_active_connections();
-    EXCEPT(dbpriv_dbio_failed)
+
+	oklog("%s: Writing forked and suspended tasks ...\n", reason);
+	write_task_queue();
+
+	oklog("%s: Writing list of formerly active connections ...\n", reason);
+	write_active_connections();
+    }
+    EXCEPT(dbpriv_dbio_failed) {
 	success = 0;
-    ENDTRY;
+    } ENDTRY;
 
     return success;
 }
@@ -584,12 +1117,12 @@ dump_database(Dump_Reason reason)
 	    fclose(f);
 	    remove(temp_name);
 	    if (reason == DUMP_CHECKPOINT) {
-		errlog("Abandoning checkpoint attempt...\n");
+		errlog("Abandoning checkpoint attempt ...\n");
 		success = 0;
 	    } else {
 		int retry_interval = 60;
 
-		errlog("Waiting %d seconds and retrying dump...\n",
+		errlog("Waiting %d seconds and retrying dump ...\n",
 		       retry_interval);
 		timer_sleep(retry_interval);
 		goto retryDumping;
diff --git a/db_objects.c b/db_objects.c
index c8a670d..57642d9 100644
--- a/db_objects.c
+++ b/db_objects.c
@@ -66,6 +66,13 @@ db_reset_last_used_objid(void)
 	num_objects--;
 }
 
+void
+db_set_last_used_objid(Objid oid)
+{
+    while (!objects[num_objects - 1] && num_objects > oid)
+	num_objects--;
+}
+
 static void
 ensure_new_object(void)
 {
@@ -117,8 +124,12 @@ db_create_object(void)
 
     o->name = str_dup("");
     o->flags = 0;
-    o->parent = o->child = o->sibling = NOTHING;
-    o->location = o->contents = o->next = NOTHING;
+
+    o->parents = nothing;
+    o->children = new_list(0);
+
+    o->location = nothing;
+    o->contents = new_list(0);
 
     o->propval = 0;
 
@@ -143,10 +154,19 @@ db_destroy_object(Objid oid)
     if (!o)
 	panic("DB_DESTROY_OBJECT: Invalid object!");
 
-    if (o->location != NOTHING || o->contents != NOTHING
-	|| o->parent != NOTHING || o->child != NOTHING)
+    if (o->location.v.obj != NOTHING ||
+	o->contents.v.list[0].v.num != 0 ||
+	(o->parents.type == TYPE_OBJ && o->parents.v.obj != NOTHING) ||
+	(o->parents.type == TYPE_LIST && o->parents.v.list[0].v.num != 0) ||
+	o->children.v.list[0].v.num != 0)
 	panic("DB_DESTROY_OBJECT: Not a barren orphan!");
 
+    free_var(o->parents);
+    free_var(o->children);
+
+    free_var(o->location);
+    free_var(o->contents);
+
     if (is_user(oid)) {
 	Var t;
 
@@ -189,47 +209,49 @@ db_renumber_object(Objid old)
     db_priv_affected_callable_verb_lookup();
 
     for (new = 0; new < old; new++) {
-	if (objects[new] == 0) {
+	if (objects[new] == NULL) {
 	    /* Change the identity of the object. */
 	    o = objects[new] = objects[old];
 	    objects[old] = 0;
 	    objects[new]->id = new;
 
-	    /* Fix up the parent/children hierarchy */
-	    {
-		Objid oid, *oidp;
-
-		if (o->parent != NOTHING) {
-		    oidp = &objects[o->parent]->child;
-		    while (*oidp != old && *oidp != NOTHING)
-			oidp = &objects[*oidp]->sibling;
-		    if (*oidp == NOTHING)
-			panic("Object not in parent's children list");
-		    *oidp = new;
-		}
-		for (oid = o->child;
-		     oid != NOTHING;
-		     oid = objects[oid]->sibling)
-		    objects[oid]->parent = new;
+	    /* Fix up the parents/children hierarchy and the
+	     * location/contents hierarchy.
+	     */
+	    int i1, c1, i2, c2;
+	    Var obj1, obj2;
+
+#define	    FIX(up, down)							\
+	    if (TYPE_LIST == o->up.type) {					\
+		FOR_EACH(obj1, o->up, i1, c1) {					\
+		    FOR_EACH(obj2, objects[obj1.v.obj]->down, i2, c2)		\
+			if (obj2.v.obj == old)					\
+			    break;						\
+		    objects[obj1.v.obj]->down.v.list[i2].v.obj = new;		\
+		}								\
+	    }									\
+	    else if (TYPE_OBJ == o->up.type && NOTHING != o->up.v.obj) {	\
+		FOR_EACH(obj1, objects[o->up.v.obj]->down, i2, c2)		\
+		if (obj1.v.obj == old)						\
+		    break;							\
+		objects[o->up.v.obj]->down.v.list[i2].v.obj = new;		\
+	    }									\
+	    FOR_EACH(obj1, o->down, i1, c1) {					\
+		if (TYPE_LIST == objects[obj1.v.obj]->up.type) {		\
+		    FOR_EACH(obj2, objects[obj1.v.obj]->up, i2, c2)		\
+			if (obj2.v.obj == old)					\
+			    break;						\
+		    objects[obj1.v.obj]->up.v.list[i2].v.obj = new;		\
+		}								\
+		else {								\
+		    objects[obj1.v.obj]->up.v.obj = new;			\
+		}								\
 	    }
 
-	    /* Fix up the location/contents hierarchy */
-	    {
-		Objid oid, *oidp;
-
-		if (o->location != NOTHING) {
-		    oidp = &objects[o->location]->contents;
-		    while (*oidp != old && *oidp != NOTHING)
-			oidp = &objects[*oidp]->next;
-		    if (*oidp == NOTHING)
-			panic("Object not in location's contents list");
-		    *oidp = new;
-		}
-		for (oid = o->contents;
-		     oid != NOTHING;
-		     oid = objects[oid]->next)
-		    objects[oid]->location = new;
-	    }
+	    FIX(parents, children);
+	    FIX(location, contents);
+
+#undef	    FIX
 
 	    /* Fix up the list of users, if necessary */
 	    if (is_user(new)) {
@@ -313,6 +335,48 @@ db_object_bytes(Objid oid)
 }
 
 
+/* Define db_ancestors(), db_descendants(), db_all_locations(), and
+ * db_all_contents().  To simplify the construction of the macro, use
+ * enlist_var() to make everything look like a list.
+ */
+#define DEFUNC(name, field)						\
+Var db_##name(Objid oid, bool full)					\
+{									\
+    Var list = new_list(0);						\
+    if (oid == NOTHING)							\
+	return list;							\
+    if (full)								\
+	list = listappend(list, new_obj(oid));				\
+    Var tmp, stack, top, next;						\
+    tmp = dbpriv_find_object(oid)->field;				\
+    stack = enlist_var(var_ref(tmp));					\
+    while (listlength(stack) > 0) {					\
+	top = var_ref(stack.v.list[1]);					\
+	stack = listdelete(stack, 1);					\
+	if (valid(top.v.obj)) {						\
+	    if (top.v.obj != oid) {					\
+		tmp = dbpriv_find_object(top.v.obj)->field;		\
+		next = enlist_var(var_ref(tmp));			\
+		stack = listconcat(next, stack);			\
+	    }								\
+	    list = setadd(list, top);					\
+	}								\
+	else {								\
+	    free_var(top);						\
+	}								\
+    }									\
+    free_var(stack);							\
+    return list;							\
+}
+
+DEFUNC(ancestors, parents)
+DEFUNC(descendants, children);
+DEFUNC(all_locations, location);
+DEFUNC(all_contents, contents);
+
+#undef DEFUNC
+
+
 /*********** Object attributes ***********/
 
 Objid
@@ -343,97 +407,111 @@ db_set_object_name(Objid oid, const char *name)
     o->name = name;
 }
 
-Objid
+Var
 db_object_parent(Objid oid)
 {
-    return objects[oid]->parent;
+    return objects[oid]->parents;
 }
 
 int
 db_count_children(Objid oid)
 {
-    Objid c;
-    int i = 0;
-
-    for (c = objects[oid]->child; c != NOTHING; c = objects[c]->sibling)
-	i++;
-
-    return i;
+    return listlength(objects[oid]->children);
 }
 
 int
 db_for_all_children(Objid oid, int (*func) (void *, Objid), void *data)
 {
-    Objid c;
+    int i, c = db_count_children(oid);
 
-    for (c = objects[oid]->child; c != NOTHING; c = objects[c]->sibling)
-	if (func(data, c))
+    for (i = 1; i <= c; i++)
+	if (func(data, objects[oid]->children.v.list[i].v.obj))
 	    return 1;
 
     return 0;
 }
 
-#define LL_REMOVE(where, listname, what, nextname) { \
-    Objid lid; \
-    if (objects[where]->listname == what) \
-	objects[where]->listname = objects[what]->nextname; \
-    else { \
-	for (lid = objects[where]->listname; lid != NOTHING; \
-	      lid = objects[lid]->nextname) { \
-	    if (objects[lid]->nextname == what) { \
-		objects[lid]->nextname = objects[what]->nextname; \
-		break; \
-	    } \
-	} \
-    } \
-    objects[what]->nextname = NOTHING; \
-}
-
-#define LL_APPEND(where, listname, what, nextname) { \
-    Objid lid; \
-    if (objects[where]->listname == NOTHING) { \
-	objects[where]->listname = what; \
-    } else { \
-	for (lid = objects[where]->listname; \
-	     objects[lid]->nextname != NOTHING; \
-	     lid = objects[lid]->nextname) \
-	    ; \
-	objects[lid]->nextname = what; \
-    } \
-    objects[what]->nextname = NOTHING; \
+static int
+check_for_duplicate_parents(Var parents)
+{
+    if (TYPE_LIST != parents.type && TYPE_OBJ != parents.type)
+	return 0;
+
+    if (TYPE_LIST == parents.type && listlength(parents) > 1) {
+	int i, j, c = listlength(parents);
+	for (i = 1; i <= c; i++)
+	    for (j = i + i; j <= c; j++)
+		if (equality(parents.v.list[i], parents.v.list[j], 1))
+		    return 0;
+    }
+
+    return 1;
 }
 
 int
-db_change_parent(Objid oid, Objid parent)
+db_change_parent(Objid oid, Var new_parents)
 {
-    Objid old_parent;
+    if (!check_for_duplicate_parents(new_parents))
+	return 0;
 
-    if (!dbpriv_check_properties_for_chparent(oid, parent))
+    if (!dbpriv_check_properties_for_chparent(oid, new_parents))
 	return 0;
 
-    if (objects[oid]->child == NOTHING && objects[oid]->verbdefs == NULL) {
+    if (listlength(objects[oid]->children) == 0 && objects[oid]->verbdefs == NULL) {
 	/* Since this object has no children and no verbs, we know that it
 	   can't have had any part in affecting verb lookup, since we use first
 	   parent with verbs as a key in the verb lookup cache. */
-	/* The "no kids" rule is necessary because potentially one of the kids
-	   could have verbs on it--and that kid could have cache entries for
-	   THIS object's parentage. */
+	/* The "no kids" rule is necessary because potentially one of the
+	   children could have verbs on it--and that child could have cache
+	   entries for THIS object's parentage. */
 	/* In any case, don't clear the cache. */
 	;
     } else {
 	db_priv_affected_callable_verb_lookup();
     }
 
-    old_parent = objects[oid]->parent;
+    Var me = new_obj(oid);
+    Var old_parents = objects[oid]->parents;
+
+    /* save this; we need it later */
+    Var old_ancestors = db_ancestors(oid, true);
+
+    Var parent;
+    int i, c;
+
+    /* Remove me from my old parents' children. */
+    if (old_parents.type == TYPE_OBJ && old_parents.v.obj != NOTHING)
+	objects[old_parents.v.obj]->children = setremove(objects[old_parents.v.obj]->children, me);
+    else if (old_parents.type == TYPE_LIST)
+	FOR_EACH(parent, old_parents, i, c)
+	    objects[parent.v.obj]->children = setremove(objects[parent.v.obj]->children, me);
+
+    /* Add me to my new parents' children. */
+    if (new_parents.type == TYPE_OBJ && new_parents.v.obj != NOTHING)
+	objects[new_parents.v.obj]->children = setadd(objects[new_parents.v.obj]->children, me);
+    else if (new_parents.type == TYPE_LIST)
+	FOR_EACH(parent, new_parents, i, c)
+	    objects[parent.v.obj]->children = setadd(objects[parent.v.obj]->children, me);
+
+    free_var(objects[oid]->parents);
+
+    /* Clean up the representation.
+     *  {#X} becomes #X
+     *  {} becomes NOTHING
+     */
+    if (new_parents.type == TYPE_LIST && new_parents.v.list[0].v.num == 0)
+	objects[oid]->parents = nothing;
+    else if (new_parents.type == TYPE_LIST && new_parents.v.list[0].v.num == 1)
+	objects[oid]->parents = var_dup(new_parents.v.list[1]);
+    else
+	objects[oid]->parents = var_dup(new_parents);
 
-    if (old_parent != NOTHING)
-	LL_REMOVE(old_parent, child, oid, sibling);
+    Var new_ancestors = db_ancestors(oid, true);
 
-    if (parent != NOTHING)
-	LL_APPEND(parent, child, oid, sibling);
+    dbpriv_fix_properties_after_chparent(oid, old_ancestors, new_ancestors);
 
-    objects[oid]->parent = parent;
-    dbpriv_fix_properties_after_chparent(oid, old_parent);
+    free_var(old_ancestors);
+    free_var(new_ancestors);
 
     return 1;
 }
@@ -441,45 +519,43 @@ db_change_parent(Objid oid, Objid parent)
 Objid
 db_object_location(Objid oid)
 {
-    return objects[oid]->location;
+    return objects[oid]->location.v.obj;
 }
 
 int
 db_count_contents(Objid oid)
 {
-    Objid c;
-    int i = 0;
-
-    for (c = objects[oid]->contents; c != NOTHING; c = objects[c]->next)
-	i++;
-
-    return i;
+    return listlength(objects[oid]->contents);
 }
 
 int
 db_for_all_contents(Objid oid, int (*func) (void *, Objid), void *data)
 {
-    Objid c;
+    int i, c = db_count_contents(oid);
 
-    for (c = objects[oid]->contents; c != NOTHING; c = objects[c]->next)
-	if (func(data, c))
+    for (i = 1; i <= c; i++)
+	if (func(data, objects[oid]->contents.v.list[i].v.obj))
 	    return 1;
 
     return 0;
 }
 
 void
-db_change_location(Objid oid, Objid location)
+db_change_location(Objid oid, Objid new_location)
 {
-    Objid old_location = objects[oid]->location;
+    Var me = new_obj(oid);
+
+    Objid old_location = objects[oid]->location.v.obj;
 
     if (valid(old_location))
-	LL_REMOVE(old_location, contents, oid, next);
+	objects[old_location]->contents = setremove(objects[old_location]->contents, var_dup(me));
+
+    if (valid(new_location))
+	objects[new_location]->contents = setadd(objects[new_location]->contents, me);
 
-    if (valid(location))
-	LL_APPEND(location, contents, oid, next);
+    free_var(objects[oid]->location);
 
-    objects[oid]->location = location;
+    objects[oid]->location = new_obj(new_location);
 }
 
 int
@@ -554,7 +630,7 @@ dbpriv_set_all_users(Var v)
 
 char rcsid_db_objects[] = "$Id: db_objects.c,v 1.4 1998/12/14 13:17:36 nop Exp $";
 
-/* 
+/*
  * $Log: db_objects.c,v $
  * Revision 1.4  1998/12/14 13:17:36  nop
  * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
diff --git a/db_private.h b/db_private.h
index bc58df3..4180362 100644
--- a/db_private.h
+++ b/db_private.h
@@ -58,23 +58,27 @@ typedef struct Pval {
 typedef struct Object {
     Objid id;
     Objid owner;
-    Objid location;
-    Objid contents;
-    Objid next;
-
-    Objid parent;
-    Objid child;
-    Objid sibling;
-
 
     const char *name;
     int flags;
 
+    Var location;
+    Var contents;
+    Var parents;
+    Var children;
+
     Verbdef *verbdefs;
     Proplist propdefs;
     Pval *propval;
 } Object;
 
+/*
+ * `parents' can be #-1 (NOTHING), a valid object number, or a list of
+ * valid object numbers.  `location' can be #-1 or a valid object
+ * number.  `children' and `contents' must be a list of valid object
+ * numbers.
+ */
+
 /*********** Verb cache support ***********/
 
 #define VERB_CACHE 1
@@ -127,18 +131,21 @@ extern Propdef dbpriv_new_propdef(const char *name);
 extern int dbpriv_count_properties(Objid);
 
 extern int dbpriv_check_properties_for_chparent(Objid oid,
-						Objid new_parent);
-				/* Return true iff NEW_PARENT defines no
+						Var parents);
+				/* Return true iff PARENTS defines no
 				 * properties that are also defined by either
-				 * OID or any of OID's descendants.
+				 * OID or any of OID's descendants, or by
+				 * other parents in PARENTS and their
+				 * ancestors.
 				 */
 
 extern void dbpriv_fix_properties_after_chparent(Objid oid,
-						 Objid old_parent);
-				/* OID has just had its parent changed away
-				 * from OLD_PARENT.  Fix up the properties of
-				 * OID and its descendants, removing obsolete
-				 * ones and adding clear new ones, as
+						 Var old_ancestors,
+						 Var new_ancestors);
+				/* OID has just had its parent changed.
+				 * Fix up the properties of OID and its
+				 * descendants, removing obsolete ones
+				 * and adding clear new ones, as
 				 * appropriate for its new parent.
 				 */
 
diff --git a/db_properties.c b/db_properties.c
index eb2d88e..a57b489 100644
--- a/db_properties.c
+++ b/db_properties.c
@@ -39,21 +39,75 @@ dbpriv_new_propdef(const char *name)
 int
 dbpriv_count_properties(Objid oid)
 {
+    Var ancestor, ancestors;
+    int i, c, nprops = 0;
     Object *o;
-    int nprops = 0;
 
-    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent))
+    if (NOTHING == oid)
+	return 0;
+
+    ancestors = db_ancestors(oid, true);
+
+    FOR_EACH(ancestor, ancestors, i, c) {
+	o = dbpriv_find_object(ancestor.v.obj);
 	nprops += o->propdefs.cur_length;
+    }
+
+    free_var(ancestors);
 
     return nprops;
 }
 
+/*
+ * Finds the offset of the properties defined on `target' in `this'.
+ * Returns -1 if `target' is not an ancestor of `this'.
+ */
+static int
+properties_offset(Objid target, Objid this)
+{
+    Var ancestor, ancestors;
+    int i, c, offset = 0;
+    Object *o;
+
+    ancestors = db_ancestors(this, true);
+
+    FOR_EACH(ancestor, ancestors, i, c) {
+	if (target == ancestor.v.obj)
+	    break;
+	o = dbpriv_find_object(ancestor.v.obj);
+	offset += o->propdefs.cur_length;
+    }
+
+    free_var(ancestors);
+
+    return target == ancestor.v.obj ? offset : -1;
+}
+
+/*
+ * Returns true iff `oid' defines a property named `pname'.
+ */
+static int
+property_defined_at(const char *pname, int phash, Objid oid)
+{
+    Proplist *props = &dbpriv_find_object(oid)->propdefs;
+    int length = props->cur_length;
+    int i;
+
+    for (i = 0; i < length; i++)
+	if (props->l[i].hash == phash
+	    && !mystrcasecmp(props->l[i].name, pname))
+	    return 1;
+
+    return 0;
+}
+
+/*
+ * Return true iff some descendant of `oid' defines a property named
+ * `pname'.
+ */
 static int
 property_defined_at_or_below(const char *pname, int phash, Objid oid)
 {
-    /* Return true iff some descendant of OID defines a property named PNAME.
-     */
-    Objid c;
     Proplist *props = &dbpriv_find_object(oid)->propdefs;
     int length = props->cur_length;
     int i;
@@ -63,10 +117,9 @@ property_defined_at_or_below(const char *pname, int phash, Objid oid)
 	    && !mystrcasecmp(props->l[i].name, pname))
 	    return 1;
 
-    for (c = dbpriv_find_object(oid)->child;
-	 c != NOTHING;
-	 c = dbpriv_find_object(c)->sibling)
-	if (property_defined_at_or_below(pname, phash, c))
+    Var children = dbpriv_find_object(oid)->children;
+    for (i = 1; i <= children.v.list[0].v.num; i++)
+	if (property_defined_at_or_below(pname, phash, children.v.list[i].v.obj))
 	    return 1;
 
     return 0;
@@ -101,19 +154,19 @@ insert_prop(Objid oid, int pos, Pval pval)
 }
 
 static void
-insert_prop_recursively(Objid root, int root_pos, Pval pv)
+insert_prop_recursively(Objid root, int prop_pos, Pval pv)
 {
-    Objid c;
+    insert_prop(root, prop_pos, pv);
 
-    insert_prop(root, root_pos, pv);
     pv.var.type = TYPE_CLEAR;	/* do after initial insert_prop so only
 				   children will be TYPE_CLEAR */
-    for (c = dbpriv_find_object(root)->child;
-	 c != NOTHING;
-	 c = dbpriv_find_object(c)->sibling) {
-	int new_prop_count = dbpriv_find_object(c)->propdefs.cur_length;
 
-	insert_prop_recursively(c, new_prop_count + root_pos, pv);
+    Var child, children = dbpriv_find_object(root)->children;
+    int i, c;
+
+    FOR_EACH(child, children, i, c) {
+	int offset = properties_offset(root, child.v.obj);
+	insert_prop_recursively(child.v.obj, offset + prop_pos, pv);
     }
 }
 
@@ -214,17 +267,16 @@ remove_prop(Objid oid, int pos)
 }
 
 static void
-remove_prop_recursively(Objid root, int root_pos)
+remove_prop_recursively(Objid root, int prop_pos)
 {
-    Objid c;
+    remove_prop(root, prop_pos);
 
-    remove_prop(root, root_pos);
-    for (c = dbpriv_find_object(root)->child;
-	 c != NOTHING;
-	 c = dbpriv_find_object(c)->sibling) {
-	int new_prop_count = dbpriv_find_object(c)->propdefs.cur_length;
+    Var child, children = dbpriv_find_object(root)->children;
+    int i, c;
 
-	remove_prop_recursively(c, new_prop_count + root_pos);
+    FOR_EACH(child, children, i, c) {
+	int offset = properties_offset(root, child.v.obj);
+	remove_prop_recursively(child.v.obj, offset + prop_pos);
     }
 }
 
@@ -427,7 +479,15 @@ db_find_property(Objid oid, const char *name, Var * value)
 
     h.built_in = BP_NONE;
     n = 0;
-    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent)) {
+
+    Var ancestor, ancestors;
+    int i1, c1;
+
+    ancestors = db_ancestors(oid, true);
+
+    FOR_EACH(ancestor, ancestors, i1, c1) {
+	o = dbpriv_find_object(ancestor.v.obj);
+
 	Proplist *props = &(o->propdefs);
 	Propdef *defs = props->l;
 	int length = props->cur_length;
@@ -439,21 +499,39 @@ db_find_property(Objid oid, const char *name, Var * value)
 
 		h.definer = o->id;
 		o = dbpriv_find_object(oid);
-		prop = h.ptr = o->propval + n;
+		h.ptr = o->propval + n;
 
 		if (value) {
+		    prop = h.ptr;
+
 		    while (prop->var.type == TYPE_CLEAR) {
-			n -= o->propdefs.cur_length;
-			o = dbpriv_find_object(o->parent);
-			prop = o->propval + n;
+			if (TYPE_LIST == o->parents.type) {
+			    Var parent, parents = o->parents;
+			    int i2, c2, offset;
+			    FOR_EACH(parent, parents, i2, c2)
+				if ((offset = properties_offset(h.definer, parent.v.obj)) > -1)
+				    break;
+			    o = dbpriv_find_object(parent.v.obj);
+			    prop = o->propval + offset + i;
+			}
+			else if (TYPE_OBJ == o->parents.type && NOTHING != o->parents.v.obj) {
+			    int offset = properties_offset(h.definer, o->parents.v.obj);
+			    o = dbpriv_find_object(o->parents.v.obj);
+			    prop = o->propval + offset + i;
+			}
 		    }
 		    *value = prop->var;
 		}
+
+		free_var(ancestors);
+
 		return h;
 	    }
 	}
     }
 
+    free_var(ancestors);
+
     h.ptr = 0;
     return h;
 }
@@ -587,94 +665,271 @@ db_property_allows(db_prop_handle h, Objid progr, db_prop_flag flag)
 	    || is_wizard(progr));
 }
 
-static void
-fix_props(Objid oid, int parent_local, int old, int new, int common)
+/*
+ * `parents' is the proposed set of new parents.  Ensure that object
+ * `oid' or one of its descendants does not define a property with the
+ * same name as one defined on `parents' or any of their ancestors.
+ * Ensure that none of the `parents' not their ancestors define a
+ * property with the same name.
+ */
+int
+dbpriv_check_properties_for_chparent(Objid oid, Var parents)
 {
-    Object *me = dbpriv_find_object(oid);
-    Object *parent = dbpriv_find_object(me->parent);
-    Pval *new_propval;
-    int local = parent_local;
-    int i;
-    Objid c;
+    /* build a list of ancestors from the supplied parents */
+
+    Var ancestors = new_list(0);
+    Var stack = enlist_var(var_dup(parents));
+
+    while (listlength(stack) > 0) {
+	Var top;
+	POP_TOP(top, stack);
+	if (valid(top.v.obj)) {
+	    if (top.v.obj != oid) {
+		Var tmp = dbpriv_find_object(top.v.obj)->parents;
+		tmp = enlist_var(var_ref(tmp));
+		stack = listconcat(tmp, stack);
+	    }
+	    ancestors = setadd(ancestors, top);
+	}
+    }
 
-    local += me->propdefs.cur_length;
+    free_var(stack);
 
-    for (i = local; i < local + old; i++)
-	free_var(me->propval[i].var);
+    Object *o;
+    Var ancestor;
+    int i, c, x;
 
-    if (local + new + common != 0) {
-	new_propval = mymalloc((local + new + common) * sizeof(Pval), M_PVAL);
-	for (i = 0; i < local; i++)
-	    new_propval[i] = me->propval[i];
-	for (i = 0; i < new; i++) {
-	    Pval pv;
+    /* check props in descendants */
 
-	    pv = parent->propval[parent_local + i];
-	    new_propval[local + i] = pv;
-	    new_propval[local + i].var.type = TYPE_CLEAR;
-	    if (pv.perms & PF_CHOWN)
-		new_propval[local + i].owner = me->owner;
-	}
-	for (i = 0; i < common; i++)
-	    new_propval[local + new + i] = me->propval[local + old + i];
-    } else
-	new_propval = 0;
+    FOR_EACH(ancestor, ancestors, i, c) {
+	o = dbpriv_find_object(ancestor.v.obj);
+	Proplist *props = &o->propdefs;
 
-    if (me->propval)
-	myfree(me->propval, M_PVAL);
-    me->propval = new_propval;
+	for (x = 0; x < props->cur_length; x++)
+	    if (property_defined_at_or_below(props->l[x].name,
+					     props->l[x].hash,
+					     oid)) {
+		free_var(ancestors);
+		return 0;
+	    }
+    }
 
-    for (c = me->child; c != NOTHING; c = dbpriv_find_object(c)->sibling)
-	fix_props(c, local, old, new, common);
-}
+    /* check props in parents */
 
-int
-dbpriv_check_properties_for_chparent(Objid oid, Objid new_parent)
-{
-    Object *o;
-    int i;
+    while (listlength(ancestors) > 0) {
+	POP_TOP(ancestor, ancestors);
 
-    for (o = dbpriv_find_object(new_parent);
-	 o;
-	 o = dbpriv_find_object(o->parent)) {
+	o = dbpriv_find_object(ancestor.v.obj);
 	Proplist *props = &o->propdefs;
 
-	for (i = 0; i < props->cur_length; i++)
-	    if (property_defined_at_or_below(props->l[i].name,
-					     props->l[i].hash,
-					     oid))
-		return 0;
+	Var obj;
+
+	FOR_EACH(obj, ancestors, i, c) {
+	    if (obj.v.obj == ancestor.v.obj)
+		continue;
+	    for (x = 0; x < props->cur_length; x++)
+		if (property_defined_at(props->l[x].name,
+					props->l[x].hash,
+					obj.v.obj)) {
+		    free_var(ancestors);
+		    return 0;
+		}
+	    
+	}
     }
 
+    free_var(ancestors);
     return 1;
 }
 
+/*
+ * Given lists of `old_ancestors' and `new_ancestors', fix the
+ * properties of `oid' by 1) preserving properties whose definition is
+ * present in both `old_ancestors' and `new_ancestors', 2) removing
+ * all properties whose definition is not present in `new_ancestors',
+ * and 3) adding new clear properties for properties whose definition
+ * was added in `new_ancestors'.
+ *
+ * Consider the following graph.  The challenge is to figure out what
+ * properties we must preserve when we chparent `a' to `c' (bypassing
+ * `b' which is still a parent of `f').
+ *
+ *      x - m
+ *     /     \
+ *    z       a ---- b - c
+ *     \     /       /
+ *      y - n       /
+ *           \     /
+ *            e - f
+ *
+ * Ancestors Before
+ *  m - a, b, c
+ *  x - m, a, b, c
+ *  n - a, b, c, e, f
+ *  y - n, a, b, c, e, f
+ *  z - x, m, a, b, c, y, n, e, f
+ *
+ * Ancestors After
+ *  m - a, c
+ *  x - m, a, c
+ *  n - a, c, e, f, b
+ *  y - n, a, c, e, f, b
+ *  z - x, m, a, c, y, n, e, f, b
+ *
+ * Note that for `z', `b' is still an ancestor, however the path
+ * changed from _through `x'_ to _through `y'_.  If `z' changed the
+ * value of a property on `b', then that value should be preserved.
+ * If the value of a property on `b' was clear, then access through
+ * `x' will pick up the non-clear value from an ancestor based on the
+ * new in inheritance path.  This applies to `y' and `n', as well.
+ *
+ * In short, in the case of `z', `y' and `n', the properties of `b'
+ * should be preserved, even though `b' moved in the inheritance path.
+ * The only thing that should change is the apparent value from clear
+ * properties.
+ *
+ * Note!  Ownership of 'c' properties is a sticky issue -- however
+ * ickyness exists in the single-inheritance implementation, too --
+ * once ownership is established in a child via the `c' flag, it is
+ * never revoked, even if the parent changes or the parent changes the
+ * `c' flag!
+ *
+ * Implementation: for this object and all of its descendants,
+ * calculate the set of old ancestors, the set of new ancestors, and
+ * find their intersection.  The layout may change, but as we're
+ * laying out the properties in memory, consult the intersection, and
+ * preserve information about those properties.
+ */
 void
-dbpriv_fix_properties_after_chparent(Objid oid, Objid old_parent)
+dbpriv_fix_properties_after_chparent(Objid oid, Var old_ancestors, Var new_ancestors)
 {
-    Objid o1, o2, common, new_parent;
-    int common_props, old_props, new_props;
-
-    /* Find the nearest common ancestor between old & new parent */
-    new_parent = db_object_parent(oid);
-    common = NOTHING;
-    for (o1 = new_parent; o1 != NOTHING; o1 = db_object_parent(o1))
-	for (o2 = old_parent; o2 != NOTHING; o2 = db_object_parent(o2))
-	    if (o1 == o2) {
-		common = o1;
-		goto endouter;
+    Object *o;
+    Var ancestor;
+
+    /*
+     * Property values are laid corresponding to the order of
+     * ancestors in the inheritance hierarchy.  The offsets arrays
+     * hold the starting point of each sub-array.
+     */
+    int offset;
+    int old_count, *old_offsets = mymalloc((listlength(old_ancestors) + 1) * sizeof(int), M_INT);
+    int new_count, *new_offsets = mymalloc((listlength(new_ancestors) + 1) * sizeof(int), M_INT);
+    int i1, c1;
+
+    offset = 0;
+    FOR_EACH(ancestor, old_ancestors, i1, c1) {
+	o = dbpriv_find_object(ancestor.v.obj);
+	old_offsets[i1 - 1] = offset; /* C arrays start at index 0, MOO arrays start at index 1 */
+	offset += o->propdefs.cur_length;
+    }
+    old_count = old_offsets[i1 - 1] = offset;
+
+    offset = 0;
+    FOR_EACH(ancestor, new_ancestors, i1, c1) {
+	o = dbpriv_find_object(ancestor.v.obj);
+	new_offsets[i1 - 1] = offset;
+	offset += o->propdefs.cur_length;
+    }
+    new_count = new_offsets[i1 - 1] = offset;
+
+    /*
+     * Iterate through the new ancestors.  Copy property values that
+     * are in both the new ancestors and the old ancestors.
+     * Otherwise, add new clear property values and delete any
+     * remaining property values.
+     */
+    Object *me = dbpriv_find_object(oid);
+    Pval *new_propval = NULL;
+
+    if (new_count != 0) {
+	new_propval = mymalloc(new_count * sizeof(Pval), M_PVAL);
+	int i2, c2, i3, c3;
+	FOR_EACH(ancestor, new_ancestors, i2, c2) {
+	    int n1 = new_offsets[i2 - 1];
+	    int n2 = new_offsets[i2];
+	    int l, x;
+	    if ((l = ismember(ancestor, old_ancestors, 1)) > 0) {
+		int o1 = old_offsets[l - 1];
+		int o2 = old_offsets[l];
+		for (x = o1; x < o2; x++, n1++) {
+		    new_propval[n1].var = var_ref(me->propval[x].var);
+		    new_propval[n1].owner = me->propval[x].owner;
+		    new_propval[n1].perms = me->propval[x].perms;
+		}
 	    }
-  endouter:
+	    else {
+		Var parent, parents = enlist_var(var_ref(me->parents));
+		FOR_EACH(parent, parents, i3, c3)
+		    if (valid(parent.v.obj))
+			if ((offset = properties_offset(ancestor.v.obj, parent.v.obj)) > -1)
+			    break;
+		free_var(parents);
+		for (x = 0; n1 < n2; x++, n1++) {
+		    Pval pv = dbpriv_find_object(parent.v.obj)->propval[offset + x];
+		    new_propval[n1].var = clear;
+		    new_propval[n1].owner = pv.perms & PF_CHOWN ? me->owner : pv.owner;
+		    new_propval[n1].perms = pv.perms;
+		}
+	    }
+	}
+    }
 
-    if (common != NOTHING)
-	common_props = dbpriv_count_properties(common);
-    else
-	common_props = 0;
+    /*
+     * Clean up.
+     */
+    int c;
+    if (me->propval) {
+	for (c = 0; c < old_count; c++)
+	  free_var(me->propval[c].var);
+	myfree(me->propval, M_PVAL);
+    }
+    me->propval = new_propval;
 
-    old_props = dbpriv_count_properties(old_parent) - common_props;
-    new_props = dbpriv_count_properties(new_parent) - common_props;
+    myfree(old_offsets, M_INT);
+    myfree(new_offsets, M_INT);
 
-    fix_props(oid, 0, old_props, new_props, common_props);
+    /*
+     * Recursively call dbpriv_fix_properties_after_chparent for each
+     * child.  For each child's parents assemble the old ancestors.
+     * When we come to me (oid) as parent, use my old ancestors
+     * instead of my new ancestors.
+     */
+    Var child, parent;
+    int i4, c4, i5, c5, i6, c6;
+    FOR_EACH(child, dbpriv_find_object(oid)->children, i4, c4) {
+	Object *oc = dbpriv_find_object(child.v.obj);
+	Var new = new_list(1);
+	Var old = new_list(1);
+	new.v.list[1] = var_ref(child);
+	old.v.list[1] = var_ref(child);
+	if (TYPE_LIST == oc->parents.type) {
+	    FOR_EACH(parent, oc->parents, i5, c5) {
+		Object *op = dbpriv_find_object(parent.v.obj);
+		if (op->id == oid) {
+		    Var tmp;
+		    FOR_EACH(tmp, old_ancestors, i6, c6)
+			old = setadd(old, var_ref(tmp));
+		    FOR_EACH(tmp, new_ancestors, i6, c6)
+			new = setadd(new, var_ref(tmp));
+		}
+		else {
+		    Var tmp, all = db_ancestors(op->id, true);
+		    FOR_EACH(tmp, all, i6, c6) {
+			old = setadd(old, var_ref(tmp));
+			new = setadd(new, var_ref(tmp));
+		    }
+		    free_var(all);
+		}
+	    }
+	}
+	else {
+	    new = listconcat(new, var_ref(new_ancestors));
+	    old = listconcat(old, var_ref(old_ancestors));
+	}
+	dbpriv_fix_properties_after_chparent(child.v.obj, old, new);
+	free_var(new);
+	free_var(old);
+    }
 }
 
 char rcsid_db_properties[] = "$Id: db_properties.c,v 1.3 1998/12/14 13:17:38 nop Exp $";
diff --git a/db_verbs.c b/db_verbs.c
index fc8ab25..45d389e 100644
--- a/db_verbs.c
+++ b/db_verbs.c
@@ -299,7 +299,15 @@ db_find_command_verb(Objid oid, const char *verb,
     static handle h;
     db_verb_handle vh;
 
-    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent))
+    Var ancestors;
+    Var ancestor;
+    int i, c;
+
+    ancestors = db_ancestors(oid, false);
+    ancestors = listinsert(ancestors, new_obj(oid), 1);
+
+    FOR_EACH(ancestor, ancestors, i, c) {
+	o = dbpriv_find_object(ancestor.v.obj);
 	for (v = o->verbdefs; v; v = v->next) {
 	    db_arg_spec vdobj = (v->perms >> DOBJSHIFT) & OBJMASK;
 	    db_arg_spec viobj = (v->perms >> IOBJSHIFT) & OBJMASK;
@@ -312,9 +320,14 @@ db_find_command_verb(Objid oid, const char *verb,
 		h.verbdef = v;
 		vh.ptr = &h;
 
+		free_var(ancestors);
+
 		return vh;
 	    }
 	}
+    }
+
+    free_var(ancestors);
 
     vh.ptr = 0;
 
@@ -450,6 +463,41 @@ db_log_cache_stats(void)
 
 #endif
 
+/*
+ * Used by `db_find_callable_verb' once a suitable starting point
+ * is found.  The function iterates through all ancestors looking
+ * for a matching verbdef.
+ */
+struct verbdef_definer_data {
+    Object *o;
+    Verbdef *v;
+};
+
+static struct verbdef_definer_data
+find_callable_verbdef(Objid oid, const char *verb)
+{
+    Object *o;
+    Verbdef *v = NULL;
+    Var ancestor, ancestors;
+    int i, c;
+
+    ancestors = db_ancestors(oid, true);
+
+    FOR_EACH(ancestor, ancestors, i, c) {
+	o = dbpriv_find_object(ancestor.v.obj);
+	if ((v = find_verbdef_by_name(o, verb, 1)) != NULL) {
+	    break;
+	}
+    }
+
+    free_var(ancestors);
+
+    struct verbdef_definer_data data;
+    data.o = o;
+    data.v = v;
+    return data;
+}
+
 db_verb_handle
 db_find_callable_verb(Objid oid, const char *verb)
 {
@@ -463,78 +511,122 @@ db_find_callable_verb(Objid oid, const char *verb)
     db_verb_handle vh;
 
 #ifdef VERB_CACHE
-    unsigned int hash, bucket;
-    Objid first_parent_with_verbs = oid;
-    vc_entry *vc;
+    /*
+     * First, find the `first_parent_with_verbs'.  This is the first
+     * ancestor of a parent that actually defines a verb.  If I define
+     * verbs, then `first_parent_with_verbs' is me.  Otherwise,
+     * iterate through each parent in turn, find the first ancestor
+     * with verbs, and then try to find the verb starting at that
+     * point.
+     */
+    Var stack = new_list(0);
+    stack = listappend(stack, new_obj(oid));
 
-    if (vc_table == NULL)
-	make_vc_table(DEFAULT_VC_SIZE);
+    try_again:
+    while (listlength(stack) > 0) {
+	Var top;
 
-    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent)) {
-	if (o->verbdefs != NULL)
-	    break;
-    }
+	POP_TOP(top, stack);
 
-    if (o) {
-	first_parent_with_verbs = o->id;
-    } else {
-	first_parent_with_verbs = NOTHING;
-    }
+	if (!valid(top.v.obj)) {
+	    /* just consume it */
+	    free_var(top);
+	    continue;
+	}
+	else {
+	    o = dbpriv_find_object(top.v.obj);
+	    if (o->verbdefs == NULL) {
+		/* keep looking */
+		if (TYPE_OBJ == o->parents.type)
+		    stack = listinsert(stack, var_ref(o->parents), 1);
+		else
+		    stack = listconcat(var_ref(o->parents), stack);
+		free_var(top);
+		continue;
+	    }
+	}
+
+	free_var(top);
+
+	Objid first_parent_with_verbs = o ? o->id : NOTHING;
+
+	/* found something with verbdefs, now check the cache */
+	unsigned int hash, bucket;
+	vc_entry *vc;
 
-    hash = str_hash(verb) ^ (~first_parent_with_verbs);		/* ewww, but who cares */
-    bucket = hash % vc_size;
-
-    for (vc = vc_table[bucket]; vc; vc = vc->next) {
-	if (hash == vc->hash
-	    && first_parent_with_verbs == vc->oid_key
-	    && !mystrcasecmp(verb, vc->verbname)) {
-	    /* we haaave a winnaaah */
-	    if (vc->h.verbdef) {
-		verbcache_hit++;
-		vh.ptr = &vc->h;
-	    } else {
-		verbcache_neg_hit++;
-		vh.ptr = 0;
+	if (vc_table == NULL)
+	    make_vc_table(DEFAULT_VC_SIZE);
+
+	hash = str_hash(verb) ^ (~first_parent_with_verbs);	/* ewww, but who cares */
+	bucket = hash % vc_size;
+
+	for (vc = vc_table[bucket]; vc; vc = vc->next) {
+	    if (hash == vc->hash
+		&& first_parent_with_verbs == vc->oid_key
+		&& !mystrcasecmp(verb, vc->verbname)) {
+		/* we haaave a winnaaah */
+		if (vc->h.verbdef) {
+		    verbcache_hit++;
+		    vh.ptr = &vc->h;
+		} else {
+		    verbcache_neg_hit++;
+		    vh.ptr = 0;
+		}
+		if (vh.ptr) {
+		    free_var(stack);
+		    return vh;
+		}
+		goto try_again;
 	    }
-	    return vh;
 	}
-    }
 
-    /* A swing and a miss. */
-    verbcache_miss++;
+	/* a swing and a miss */
+	verbcache_miss++;
+
 #else
-    o = dbpriv_find_object(oid);
+	o = dbpriv_find_object(oid);
 #endif
 
 #ifdef VERB_CACHE
-    /*
-     * Add the entry to the verbcache whether we find it or not.  This means
-     * we do "negative caching", keeping track of failed lookups so that
-     * repeated failures hit the cache instead of going through a lookup.
-     */
-    new_vc = mymalloc(sizeof(vc_entry), M_VC_ENTRY);
-
-    new_vc->hash = hash;
-    new_vc->oid_key = first_parent_with_verbs;
-    new_vc->verbname = str_dup(verb);
-    new_vc->h.verbdef = NULL;
-    new_vc->next = vc_table[bucket];
-    vc_table[bucket] = new_vc;
+	/*
+	 * Add the entry to the verbcache whether we find it or not.  This
+	 * means we do "negative caching", keeping track of failed lookups
+	 * so that repeated failures hit the cache instead of going
+	 * through a lookup.
+	 */
+	new_vc = mymalloc(sizeof(vc_entry), M_VC_ENTRY);
+
+	new_vc->hash = hash;
+	new_vc->oid_key = first_parent_with_verbs;
+	new_vc->verbname = str_dup(verb);
+	new_vc->h.verbdef = NULL;
+	new_vc->next = vc_table[bucket];
+	vc_table[bucket] = new_vc;
 #endif
 
-    for ( /* from above */ ; o; o = dbpriv_find_object(o->parent))
-	if ((v = find_verbdef_by_name(o, verb, 1)) != 0) {
+	struct verbdef_definer_data data = find_callable_verbdef(o->id, verb);
+	if (data.o != NULL && data.v != NULL) {
+
 #ifdef VERB_CACHE
-	    new_vc->h.definer = o->id;
-	    new_vc->h.verbdef = v;
+	    free_var(stack);
+
+	    new_vc->h.definer = data.o->id;
+	    new_vc->h.verbdef = data.v;
 	    vh.ptr = &new_vc->h;
 #else
-	    h.definer = o->id;
-	    h.verbdef = v;
+	    h.definer = data.o->id;
+	    h.verbdef = data.v;
 	    vh.ptr = &h;
 #endif
 	    return vh;
 	}
+
+#ifdef VERB_CACHE
+    }
+
+    free_var(stack);
+#endif
+
     /*
      * note that the verbcache has cleared h.verbdef, so it defaults to a
      * "miss" cache if the for loop doesn't win
diff --git a/execute.c b/execute.c
index 3b7474d..698352a 100644
--- a/execute.c
+++ b/execute.c
@@ -551,17 +551,45 @@ call_verb(Objid this, const char *vname, Var args, int do_pass)
     Var *env;
     Var v;
 
-    if (do_pass)
+    if (do_pass) {
 	if (!valid(RUN_ACTIV.vloc))
 	    return E_INVIND;
-	else
-	    where = db_object_parent(RUN_ACTIV.vloc);
-    else
+
+	Var parents = db_object_parent(RUN_ACTIV.vloc);
+
+	if (TYPE_LIST == parents.type) {
+	    /* Loop over each parent, looking for the first parent
+	     * that defines a suitable verb that we can pass to.
+	     */
+	    Var parent;
+	    int i, c;
+	    FOR_EACH(parent, parents, i, c) {
+		where = parent.v.obj;
+		h = db_find_callable_verb(where, vname);
+		if (h.ptr)
+		    break;
+	    }
+	}
+	else if (TYPE_OBJ == parents.type) {
+	    /* Look for a suitable verb on the parent, if the parent
+	     * is valid.
+	     */
+	    where = parents.v.obj;
+	    if (!valid(where))
+		return E_INVIND;
+	    h = db_find_callable_verb(where, vname);
+	}
+	else {
+	    return E_VERBNF;
+	}
+    }
+    else {
 	where = this;
+	if (!valid(where))
+	    return E_INVIND;
+	h = db_find_callable_verb(where, vname);
+    }
 
-    if (!valid(where))
-	return E_INVIND;
-    h = db_find_callable_verb(where, vname);
     if (!h.ptr)
 	return E_VERBNF;
     else if (!push_activation())
diff --git a/list.h b/list.h
index 0929c9e..db656bf 100644
--- a/list.h
+++ b/list.h
@@ -34,7 +34,59 @@ extern Var new_list(int size);
 extern const char *value2str(Var);
 extern const char *value_to_literal(Var);
 
-/* 
+/*
+ * Returns the length of the given list `l'.  Does *not* check to
+ * ensure `l' is, in fact, a list.
+ */
+static inline int32
+listlength(Var l)
+{
+    return l.v.list[0].v.num;
+}
+
+/*
+ * Wraps `v' in a list if it is not already a list.  Consumes `v', so
+ * you may want to var_ref/var_dup `v'.  Currently, this function is
+ * called by functions that operate on an object's parents, which can
+ * be either an object reference (TYPE_OBJ) or a list of object
+ * references (TYPE_LIST).
+ */
+static inline Var
+enlist_var(Var v)
+{
+    if (TYPE_LIST == v.type)
+	return v;
+
+    Var r = new_list(1);
+    r.v.list[1] = v;
+    return r;
+}
+
+/*
+ * Iterate over the values in the list `lst'.  Sets `val' to each
+ * value, in turn.  `itr' and `cnt' must be int variables.  In the
+ * body of the statement, they hold the current index and total count,
+ * respectively.  Use the macro as follows (assuming you already have
+ * a list in `items'):
+ *   Var item;
+ *   int i, c;
+ *   FOR_EACH(item, items, i, c) {
+ *       printf("%d of %d, item = %s\n", i, c, value_to_literal(item));
+ *   }
+ */
+#define FOR_EACH(val, lst, idx, cnt)				\
+for (idx = 1, cnt = lst.v.list[0].v.num;			\
+     idx <= cnt && (val = lst.v.list[idx], 1);			\
+     idx++)
+
+/*
+ * Pop the first value off `stck' and put it in `tp'.
+ */
+#define POP_TOP(tp, stck)					\
+tp = var_ref(stck.v.list[1]);					\
+stck = listdelete(stck, 1);					\
+
+/*
  * $Log: list.h,v $
  * Revision 1.3  1998/12/14 13:17:58  nop
  * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
diff --git a/objects.c b/objects.c
index 9077b3b..b477bcc 100644
--- a/objects.c
+++ b/objects.c
@@ -46,7 +46,59 @@ make_arglist(Objid what)
     return r;
 }
 
+static bool
+all_valid(Var vars)
+{
+    Var var;
+    int i, c;
+    FOR_EACH(var, vars, i, c)
+	if (!valid(var.v.obj))
+	    return false;
+    return true;
+}
 
+static bool
+all_allowed(Var vars, Objid progr, db_object_flag f)
+{
+    Var var;
+    int i, c;
+    FOR_EACH(var, vars, i, c)
+	if (!db_object_allows(var.v.obj, progr, f))
+	    return false;
+    return true;
+}
+
+/*
+ * Returns true if `this' is a descendant of `obj'.
+ */
+static bool
+is_a_descendant(Var this, Var obj)
+{
+    Var descendants = db_descendants(obj.v.obj, true);
+    int ret = ismember(this, descendants, 1);
+    free_var(descendants);
+    return ret ? true : false;
+}
+
+/*
+ * Returns true if any of `these' are descendants of `obj'.
+ */
+static bool
+any_are_descendants(Var these, Var obj)
+{
+    Var this, descendants = db_descendants(obj.v.obj, true);
+    int i, c, ret;
+    FOR_EACH(this, these, i, c) {
+	ret = ismember(this, descendants, 1);
+	if (is_a_descendant(this, obj)) {
+	    free_var(descendants);
+	    return true;
+	}
+    }
+    free_var(descendants);
+    return false;
+}
+
 struct bf_move_data {
     Objid what, where;
 };
@@ -235,34 +287,59 @@ bf_max_object(Var arglist, Byte next, void *vdata, Objid progr)
 
 static package
 bf_create(Var arglist, Byte next, void *vdata, Objid progr)
-{				/* (parent [, owner]) */
+{				/* (OBJ|LIST parent [, OBJ owner]) */
     Objid *data = vdata;
     Var r;
 
     if (next == 1) {
-	Objid parent, owner;
+	if (!is_obj_or_list_of_objs(arglist.v.list[1])) {
+	    free_var(arglist);
+	    return make_error_pack(E_TYPE);
+	}
 
-	parent = arglist.v.list[1].v.obj;
-	owner = (arglist.v.list[0].v.num == 2
-		 ? arglist.v.list[2].v.obj
-		 : progr);
-	free_var(arglist);
+	Objid owner = (arglist.v.list[0].v.num == 2
+		       ? arglist.v.list[2].v.obj
+		       : progr);
 
-	if ((valid(parent) ? !db_object_allows(parent, progr, FLAG_FERTILE)
-	     : (parent != NOTHING))
-	    || (owner != progr && !is_wizard(progr)))
+	if ((arglist.v.list[1].type == TYPE_OBJ
+	     && !valid(arglist.v.list[1].v.obj)
+	     && arglist.v.list[1].v.obj != NOTHING)
+	    || (arglist.v.list[1].type == TYPE_LIST
+		&& !all_valid(arglist.v.list[1]))) {
+	    free_var(arglist);
+	    return make_error_pack(E_INVARG);
+	}
+	else if ((progr != owner && !is_wizard(progr))
+		 || (arglist.v.list[1].type == TYPE_OBJ
+		     && valid(arglist.v.list[1].v.obj)
+		     && !db_object_allows(arglist.v.list[1].v.obj, progr, FLAG_FERTILE))
+		 || (arglist.v.list[1].type == TYPE_LIST
+		     && !all_allowed(arglist.v.list[1], progr, FLAG_FERTILE))) {
+	    free_var(arglist);
 	    return make_error_pack(E_PERM);
+	}
 
-	if (valid(owner) && !decr_quota(owner))
+	if (valid(owner) && !decr_quota(owner)) {
+	    free_var(arglist);
 	    return make_error_pack(E_QUOTA);
+	}
 	else {
 	    enum error e;
+	    Objid last = db_last_used_objid();
 	    Objid oid = db_create_object();
 	    Var args;
 
 	    db_set_object_name(oid, str_dup(""));
-	    db_set_object_owner(oid, owner == NOTHING ? oid : owner);
-	    db_change_parent(oid, parent);
+	    db_set_object_owner(oid, !valid(owner) ? oid : owner);
+
+	    if (!db_change_parent(oid, arglist.v.list[1])) {
+		db_destroy_object(oid);
+		db_set_last_used_objid(last);
+		free_var(arglist);
+		return make_error_pack(E_INVARG);
+	    }
+
+	    free_var(arglist);
 
 	    data = alloc_data(sizeof(*data));
 	    *data = oid;
@@ -311,34 +388,54 @@ bf_create_read(void)
 
 static package
 bf_chparent(Var arglist, Byte next, void *vdata, Objid progr)
-{				/* (object, new_parent) */
+{				/* (OBJ what, OBJ|LIST new_parent) */
+    if (!is_obj_or_list_of_objs(arglist.v.list[2])) {
+	free_var(arglist);
+	return make_error_pack(E_TYPE);
+    }
+
     Objid what = arglist.v.list[1].v.obj;
-    Objid parent = arglist.v.list[2].v.obj;
-    Objid oid;
 
-    free_var(arglist);
     if (!valid(what)
-	|| (!valid(parent) && parent != NOTHING))
+	|| (arglist.v.list[2].type == TYPE_OBJ
+	    && !valid(arglist.v.list[2].v.obj)
+	    && arglist.v.list[2].v.obj != NOTHING)
+	|| (arglist.v.list[2].type == TYPE_LIST
+	    && !all_valid(arglist.v.list[2]))) {
+	free_var(arglist);
 	return make_error_pack(E_INVARG);
+    }
     else if (!controls(progr, what)
-	     || (valid(parent)
-		 && !db_object_allows(parent, progr, FLAG_FERTILE)))
+	     || (arglist.v.list[2].type == TYPE_OBJ
+		 && valid(arglist.v.list[2].v.obj)
+		 && !db_object_allows(arglist.v.list[2].v.obj, progr, FLAG_FERTILE))
+	     || (arglist.v.list[2].type == TYPE_LIST
+		 && !all_allowed(arglist.v.list[2], progr, FLAG_FERTILE))) {
+	free_var(arglist);
 	return make_error_pack(E_PERM);
+    }
+    else if ((arglist.v.list[2].type == TYPE_OBJ
+	      && is_a_descendant(arglist.v.list[2], arglist.v.list[1]))
+	     || (arglist.v.list[2].type == TYPE_LIST
+		 && any_are_descendants(arglist.v.list[2], arglist.v.list[1]))) {
+	free_var(arglist);
+	return make_error_pack(E_RECMOVE);
+    }
     else {
-	for (oid = parent; oid != NOTHING; oid = db_object_parent(oid))
-	    if (oid == what)
-		return make_error_pack(E_RECMOVE);
-
-	if (!db_change_parent(what, parent))
+	if (!db_change_parent(what, arglist.v.list[2])) {
+	    free_var(arglist);
 	    return make_error_pack(E_INVARG);
-	else
+	}
+	else {
+	    free_var(arglist);
 	    return no_var_pack();
+	}
     }
 }
 
 static package
 bf_parent(Var arglist, Byte next, void *vdata, Objid progr)
-{				/* (object) */
+{				/* (OBJ object) */
     Var r;
     Objid obj = arglist.v.list[1].v.obj;
 
@@ -347,9 +444,8 @@ bf_parent(Var arglist, Byte next, void *vdata, Objid progr)
     if (!valid(obj))
 	return make_error_pack(E_INVARG);
     else {
-	r.type = TYPE_OBJ;
-	r.v.obj = db_object_parent(obj);
-	return make_var_pack(r);
+	r = db_object_parent(obj);
+	return make_var_pack(var_dup(r));
     }
 }
 
@@ -389,6 +485,34 @@ bf_children(Var arglist, Byte next, void *vdata, Objid progr)
 	return make_var_pack(d.r);
     }
 }
+
+static package
+bf_ancestors(Var arglist, Byte next, void *vdata, Objid progr)
+{				/* (OBJ object) */
+    Objid oid = arglist.v.list[1].v.obj;
+
+    free_var(arglist);
+
+    if (!valid(oid))
+	return make_error_pack(E_INVARG);
+    else {
+	return make_var_pack(db_ancestors(oid, false));
+    }
+}
+
+static package
+bf_descendants(Var arglist, Byte next, void *vdata, Objid progr)
+{				/* (OBJ object) */
+    Objid oid = arglist.v.list[1].v.obj;
+
+    free_var(arglist);
+
+    if (!valid(oid))
+	return make_error_pack(E_INVARG);
+    else {
+	return make_var_pack(db_descendants(oid, false));
+    }
+}
 
 static int
 move_to_nothing(Objid oid)
@@ -431,7 +555,7 @@ get_first(Objid oid, int (*for_all) (Objid, int (*)(void *, Objid), void *))
 
 static package
 bf_recycle(Var arglist, Byte func_pc, void *vdata, Objid progr)
-{				/* (object) */
+{				/* (OBJ object) */
     Objid oid, c;
     Var args;
     enum error e;
@@ -483,7 +607,8 @@ bf_recycle(Var arglist, Byte func_pc, void *vdata, Objid progr)
 	/* Do the same thing for the inheritance hierarchy (much easier!) */
 	while ((c = get_first(oid, db_for_all_children)) != NOTHING)
 	    db_change_parent(c, db_object_parent(oid));
-	db_change_parent(oid, NOTHING);
+
+	db_change_parent(oid, nothing);
 
 	/* Finish the demolition. */
 	incr_quota(db_object_owner(oid));
@@ -524,7 +649,6 @@ bf_recycle_read(void)
 	return 0;
 }
 
-
 static package
 bf_players(Var arglist, Byte next, void *vdata, Objid progr)
 {				/* () */
@@ -552,10 +676,10 @@ static package
 bf_set_player_flag(Var arglist, Byte next, void *vdata, Objid progr)
 {				/* (object, yes/no) */
     Var obj;
-    char bool;
+    char flag;
 
     obj = arglist.v.list[1];
-    bool = is_true(arglist.v.list[2]);
+    flag = is_true(arglist.v.list[2]);
 
     free_var(arglist);
 
@@ -564,7 +688,7 @@ bf_set_player_flag(Var arglist, Byte next, void *vdata, Objid progr)
     else if (!is_wizard(progr))
 	return make_error_pack(E_PERM);
 
-    if (bool) {
+    if (flag) {
 	db_set_object_flag(obj.v.obj, FLAG_USER);
     } else {
 	boot_player(obj.v.obj);
@@ -592,22 +716,52 @@ bf_object_bytes(Var arglist, Byte next, void *vdata, Objid progr)
     return make_var_pack(v);
 }
 
+static package
+bf_isa(Var arglist, Byte next, void *vdata, Objid progr)
+{
+    Objid what = arglist.v.list[1].v.obj;
+    Objid target = arglist.v.list[2].v.obj;
+
+    free_var(arglist);
+
+    Var ancestors = db_ancestors(what, true);
+
+    if (ismember(arglist.v.list[2], ancestors, 1)) {
+	free_var(ancestors);
+	return make_var_pack(new_int(1));
+    }
+    else {
+	free_var(ancestors);
+	return make_var_pack(new_int(0));
+    }
+}
+
+Var nothing;			/* useful constant */
+Var clear;			/* useful constant */
+
 void
 register_objects(void)
 {
+    nothing.type = TYPE_OBJ;
+    nothing.v.obj = NOTHING;
+    clear.type = TYPE_CLEAR;
+    clear.v.num = 0;
+
     register_function("toobj", 1, 1, bf_toobj, TYPE_ANY);
     register_function("typeof", 1, 1, bf_typeof, TYPE_ANY);
     register_function_with_read_write("create", 1, 2, bf_create,
 				      bf_create_read, bf_create_write,
-				      TYPE_OBJ, TYPE_OBJ);
+				      TYPE_ANY, TYPE_OBJ);
     register_function_with_read_write("recycle", 1, 1, bf_recycle,
 				      bf_recycle_read, bf_recycle_write,
 				      TYPE_OBJ);
     register_function("object_bytes", 1, 1, bf_object_bytes, TYPE_OBJ);
     register_function("valid", 1, 1, bf_valid, TYPE_OBJ);
+    register_function("chparent", 2, 2, bf_chparent, TYPE_OBJ, TYPE_ANY);
     register_function("parent", 1, 1, bf_parent, TYPE_OBJ);
     register_function("children", 1, 1, bf_children, TYPE_OBJ);
-    register_function("chparent", 2, 2, bf_chparent, TYPE_OBJ, TYPE_OBJ);
+    register_function("ancestors", 1, 1, bf_ancestors, TYPE_OBJ);
+    register_function("descendants", 1, 1, bf_descendants, TYPE_OBJ);
     register_function("max_object", 0, 0, bf_max_object);
     register_function("players", 0, 0, bf_players);
     register_function("is_player", 1, 1, bf_is_player, TYPE_OBJ);
@@ -616,6 +770,7 @@ register_objects(void)
     register_function_with_read_write("move", 2, 2, bf_move,
 				      bf_move_read, bf_move_write,
 				      TYPE_OBJ, TYPE_OBJ);
+    register_function("isa", 2, 2, bf_isa, TYPE_OBJ, TYPE_OBJ);
 }
 
 char rcsid_objects[] = "$Id: objects.c,v 1.4 1998/12/14 13:18:39 nop Exp $";
diff --git a/storage.h b/storage.h
index c071823..83649a9 100644
--- a/storage.h
+++ b/storage.h
@@ -24,7 +24,7 @@
 
 typedef enum Memory_Type {
     M_AST_POOL, M_AST, M_PROGRAM, M_PVAL, M_NETWORK, M_STRING, M_VERBDEF,
-    M_LIST, M_PREP, M_PROPDEF, M_OBJECT_TABLE, M_OBJECT, M_FLOAT,
+    M_LIST, M_PREP, M_PROPDEF, M_OBJECT_TABLE, M_OBJECT, M_FLOAT, M_INT,
     M_STREAM, M_NAMES, M_ENV, M_TASK, M_PATTERN,
 
     M_BYTECODES, M_FORK_VECTORS, M_LIT_LIST,
diff --git a/structures.h b/structures.h
index f0cff94..469b922 100644
--- a/structures.h
+++ b/structures.h
@@ -18,6 +18,8 @@
 #ifndef Structures_h
 #define Structures_h 1
 
+#include <stdbool.h>
+
 #include "my-stdio.h"
 
 #include "config.h"
@@ -107,7 +109,39 @@ struct Var {
 #pragma pointer_size restore
 #endif
 
-extern Var zero;		/* useful constant */
+extern Var zero;		/* see numbers.c */
+extern Var nothing;		/* see objects.c */
+extern Var clear;		/* see objects.c */
+
+static inline Var
+new_int(int32 num)
+{
+    Var r;
+    r.type = TYPE_INT;
+    r.v.num = num;
+    return r;
+}
+
+static inline bool
+is_int(Var v)
+{
+    return TYPE_INT == v.type;
+}
+
+static inline Var
+new_obj(Objid obj)
+{
+    Var r;
+    r.type = TYPE_OBJ;
+    r.v.obj = obj;
+    return r;
+}
+
+static inline bool
+is_obj(Var v)
+{
+    return TYPE_OBJ == v.type;
+}
 
 #endif				/* !Structures_h */
 
diff --git a/version.h b/version.h
index 3dde0c8..1bacb1a 100644
--- a/version.h
+++ b/version.h
@@ -45,6 +45,10 @@ typedef enum {
 				 * change exists solely to turn off special
 				 * bug handling in read_bi_func_data().
 				 */
+    DBV_NextGen,		/* Introduced the next-generation database
+				 * format which fixes the data locality
+				 * problems in the v4 format.
+				 */
     Num_DB_Versions		/* Special: the current version is this - 1. */
 } DB_Version;
 
