diff --git a/libs/models/test/Makefile.am b/libs/models/test/Makefile.am
index 14fe9e3dc..2014a546c 100644
--- a/libs/models/test/Makefile.am
+++ b/libs/models/test/Makefile.am
@@ -2,7 +2,7 @@ AUTOMAKE_OPTIONS= foreign
 
 INCLUDES= -I$(srcdir) -I$(top_srcdir)/include
 
-check_PROGRAMS=testclip testcontents
+check_PROGRAMS=testclip testcontents testportals
 EXTRA_DIST=	trace-id.c trace-qf-bad.c hulls.h main.c
 
 testclip_SOURCES=testclip.c hulls.c
@@ -13,4 +13,8 @@ testcontents_SOURCES=testcontents.c hulls.c
 testcontents_LDADD= $(top_builddir)/libs/models/libQFmodels.la $(top_builddir)/libs/util/libQFutil.la
 testcontents_DEPENDENCIES= $(top_builddir)/libs/models/libQFmodels.la $(top_builddir)/libs/util/libQFutil.la
 
+testportals_SOURCES=testportals.c hulls.c
+testportals_LDADD= $(top_builddir)/libs/models/libQFmodels.la $(top_builddir)/libs/util/libQFutil.la
+testportals_DEPENDENCIES= $(top_builddir)/libs/models/libQFmodels.la $(top_builddir)/libs/util/libQFutil.la
+
 TESTS=$(check_PROGRAMS)
diff --git a/libs/models/test/testclip.c b/libs/models/test/testclip.c
index d7d4db31d..a6639b436 100644
--- a/libs/models/test/testclip.c
+++ b/libs/models/test/testclip.c
@@ -40,7 +40,6 @@ typedef struct {
 		qboolean    startsolid;
 		qboolean    inopen;
 		qboolean    inwater;
-		int         num_portals;
 	}           expect;
 } test_t;
 
@@ -51,153 +50,153 @@ box_t player = { {16, 16, 28} };
 
 test_t tests[] = {
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{-64, 0, 0}, { 64, 0, 0}, {     1, 1, 1, 0, 0, 3}},
+		{-64, 0, 0}, { 64, 0, 0}, {     1, 1, 1, 0, 0}},
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 0, 3}},
+		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 0}},
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{ 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0, 3}},
+		{ 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0}},
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 1, 1, 0, 3}},
+		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 1, 1, 0}},
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{  0, 0, 0}, {  0, 8, 0}, {     1, 1, 1, 0, 0, 3}},
+		{  0, 0, 0}, {  0, 8, 0}, {     1, 1, 1, 0, 0}},
 	{"Point, Three parallel planes 1", &point, &hull_tpp1,
-		{ 40, 0, 0}, { 40, 8, 0}, {     1, 0, 0, 1, 0, 3}},
+		{ 40, 0, 0}, { 40, 8, 0}, {     1, 0, 0, 1, 0}},
 
 	{"Point, Three parallel planes 2", &point, &hull_tpp2,
-		{-64, 0, 0}, { 64, 0, 0}, {     1, 1, 1, 0, 0, 3}},
+		{-64, 0, 0}, { 64, 0, 0}, {     1, 1, 1, 0, 0}},
 	{"Point, Three parallel planes 2", &point, &hull_tpp2,
-		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 0, 3}},
+		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 0}},
 	{"Point, Three parallel planes 2", &point, &hull_tpp2,
-		{ 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0, 3}},
+		{ 40, 0, 0}, {-88, 0, 0}, {0.0625, 0, 0, 1, 0}},
 	{"Point, Three parallel planes 2", &point, &hull_tpp2,
-		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 1, 1, 0, 3}},
+		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 1, 1, 0}},
 
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{-64, 0, 0}, { 64, 0, 0}, { 0.875, 0, 1, 1, 1, 3}},
+		{-64, 0, 0}, { 64, 0, 0}, { 0.875, 0, 1, 1, 1}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 0, 1, 1, 3}},
+		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 0, 1, 1}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{ 40, 0, 0}, {-88, 0, 0}, {0.5625, 0, 0, 1, 1, 3}},
+		{ 40, 0, 0}, {-88, 0, 0}, {0.5625, 0, 0, 1, 1}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 0, 1, 1, 3}},
+		{  0, 0, 0}, { 64, 0, 0}, {  0.75, 0, 0, 1, 1}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{  0, 0, 0}, {  0, 8, 0}, {     1, 0, 0, 1, 0, 3}},
+		{  0, 0, 0}, {  0, 8, 0}, {     1, 0, 0, 1, 0}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{ 40, 0, 0}, { 40, 8, 0}, {     1, 0, 0, 0, 1, 3}},
+		{ 40, 0, 0}, { 40, 8, 0}, {     1, 0, 0, 0, 1}},
 	{"Point, Three parallel planes with water", &point, &hull_tppw,
-		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 0, 1, 1, 3}},
+		{  0, 0, 0}, { 40, 0, 0}, {     1, 0, 0, 1, 1}},
 	{"Box, Three parallel planes with water", &box, &hull_tppw,
-		{-32, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 1, 3}},
+		{-32, 0, 0}, { 40, 0, 0}, {     1, 0, 1, 1, 1}},
 	{"Box, Three parallel planes with water", &box, &hull_tppw,
-		{-32, 0, 0}, { 20, 0, 0}, {     1, 0, 1, 1, 0, 3}},
+		{-32, 0, 0}, { 20, 0, 0}, {     1, 0, 1, 1, 0}},
 
 	{"Point, Step 1", &point, &hull_step1,
-		{ -16, 0, 8}, {16, 0, 24}, { 0.5, 0, 0, 1, 0, 5}},
+		{ -16, 0, 8}, {16, 0,  24}, {  0.5, 0, 0, 1, 0}},
 	{"Box, Step 1", &box, &hull_step1,
-		{ -16, 0, 8}, {16, 0, 24}, { 0.25, 0, 0, 1, 0, 5}},
+		{ -16, 0, 8}, {16, 0,  24}, { 0.25, 0, 0, 1, 0}},
 	{"Box, Step 1", &box, &hull_step1,
-		{ -16, 0, 8}, {16, 0, 40}, { 0.25, 0, 0, 1, 0, 5}},
+		{ -16, 0, 8}, {16, 0,  40}, { 0.25, 0, 0, 1, 0}},
 	{"Box, Step 1", &box, &hull_step1,
-		{ -16, 0, 8}, {16, 0, 135}, { 0.25, 0, 0, 1, 0, 5}},
+		{ -16, 0, 8}, {16, 0, 135}, { 0.25, 0, 0, 1, 0}},
 	{"Box, Step 1", &box, &hull_step1,
-		{ -16, 0, 8}, {16, 0, 136}, { 1, 0, 0, 1, 0, 5}},
+		{ -16, 0, 8}, {16, 0, 136}, {    1, 0, 0, 1, 0}},
 
 	{"Point, Covered Step", &point, &hull_covered_step,
-		{-24, 0,   8}, {-24, 0, 72}, { 0.5, 0, 0, 1, 0, 9}},
+		{ -24, 0,   8}, {-24, 0, 72}, {   0.5, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{-32, 0,   8}, {-32, 0, 72}, { 0.375, 0, 0, 1, 0, 9}},
+		{ -32, 0,   8}, {-32, 0, 72}, { 0.375, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{-24, 0,   8}, {-24, 0, 72}, { 0.375, 0, 0, 1, 0, 9}},
+		{ -24, 0,   8}, {-24, 0, 72}, { 0.375, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{-25, 0,   8}, {  7, 0, 72}, { 0.375, 0, 0, 1, 0, 9}},
+		{ -25, 0,   8}, {  7, 0, 72}, { 0.375, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{ -8, 0,  40}, {-16, 0, 40}, { 0.5, 0, 0, 1, 0, 9}},
+		{  -8, 0,  40}, {-16, 0, 40}, {   0.5, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{-17, 0,   8}, { -1, 0, 72}, { 1, 0, 0, 1, 0, 9}},
+		{ -17, 0,   8}, { -1, 0, 72}, {     1, 0, 0, 1, 0}},
 	{"Box, Covered Step", &box, &hull_covered_step,
-		{ -8, 0,  40}, {  8, 0, 72}, { 1, 0, 0, 1, 0, 9}},
+		{  -8, 0,  40}, {  8, 0, 72}, {     1, 0, 0, 1, 0}},
 	{"Box, Covered Step touch backside", &box, &hull_covered_step,
-		{ -8, 0,   8}, {-12, 0, 12}, { 1, 0, 0, 1, 0, 9}},
+		{  -8, 0,   8}, {-12, 0, 12}, {     1, 0, 0, 1, 0}},
 	{"Point, Covered Step touch backside", &point, &hull_covered_step,
-		{  0, 0,   8}, {-12, 0, 12}, { 1, 0, 1, 1, 0, 9}},
+		{   0, 0,   8}, {-12, 0, 12}, {     1, 0, 1, 1, 0}},
 	{"Point, Covered Step start solid", &point, &hull_covered_step,
-		{  0, 0,  -4}, {-16, 0, 12}, { 1, 0, 1, 1, 0, 9}},
+		{   0, 0,  -4}, {-16, 0, 12}, {     1, 0, 1, 1, 0}},
 	{"Point, Covered Step start solid", &point, &hull_covered_step,
-		{  4, 0,  -4}, {-12, 0, 12}, { 1, 1, 1, 0, 0, 9}},
+		{   4, 0,  -4}, {-12, 0, 12}, {     1, 1, 1, 0, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ -4, 0,   4}, {-12, 0, 12}, { 1, 0, 1, 1, 0, 9}},
+		{  -4, 0,   4}, {-12, 0, 12}, {     1, 0, 1, 1, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ -17, 0,   4}, {-17, 0, 12}, { 1, 0, 1, 1, 0, 9}},
+		{ -17, 0,   4}, {-17, 0, 12}, {     1, 0, 1, 1, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ -4, 0,   4}, {-16, 0, 36}, { 0.875, 0, 1, 1, 0, 9}},
+		{  -4, 0,   4}, {-16, 0, 36}, { 0.875, 0, 1, 1, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ -4, 0,   4}, {-16, 0,  4}, { 1, 1, 1, 0, 0, 9}},
+		{  -4, 0,   4}, {-16, 0,  4}, {     1, 1, 1, 0, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{-12, 0,   4}, {-16, 0,  4}, { 1, 1, 1, 0, 0, 9}},
+		{ -12, 0,   4}, {-16, 0,  4}, {     1, 1, 1, 0, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{-16, 0,   4}, { -4, 0,  4}, { 1, 1, 1, 0, 0, 9}},
+		{ -16, 0,   4}, { -4, 0,  4}, {     1, 1, 1, 0, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ 16, 0, -16}, { 16, 0, 64}, { 1, 1, 1, 0, 0, 9}},
+		{  16, 0, -16}, { 16, 0, 64}, {     1, 1, 1, 0, 0}},
 	{"Box, Covered Step start solid", &box, &hull_covered_step,
-		{ 16, 0,  16}, { 16, 0, 64}, { 1, 0, 1, 1, 0, 9}},
+		{  16, 0,  16}, { 16, 0, 64}, {     1, 0, 1, 1, 0}},
 
 	{"Box,  Step 2", &box, &hull_step2,
-		{ 0, 0, 64}, {0, 0, 0}, { 0.375, 0, 0, 1, 0, 5}},
+		{ 0, 0, 64}, {0, 0, 0}, { 0.375, 0, 0, 1, 0}},
 	{"Box,  Step 3", &box, &hull_step3,
-		{ 0, 0, 64}, {0, 0, 0}, { 0.375, 0, 0, 1, 0, 5}},
+		{ 0, 0, 64}, {0, 0, 0}, { 0.375, 0, 0, 1, 0}},
 
 	{"Box,  Ramp", &box, &hull_ramp,
-		{ 0, 0, 16}, {0, 0, 0}, { 0.5, 0, 0, 1, 0, 4}},
+		{ 0, 0, 16}, { 0, 0, 0}, {   0.5, 0, 0, 1, 0}},
 	{"Box,  Ramp", &box, &hull_ramp,
-		{-16, 0, 8}, {16, 0, 8}, { 1, 0, 0, 1, 0, 4}},
+		{-16, 0, 8}, {16, 0, 8}, {     1, 0, 0, 1, 0}},
 	{"Box,  Ramp", &box, &hull_ramp,
-		{-16, 0, 5}, {16, 0, 5}, { 0.125, 0, 0, 1, 0, 4}},
+		{-16, 0, 5}, {16, 0, 5}, { 0.125, 0, 0, 1, 0}},
 
 	{"Box,  Simple Wedge", &box, &hull_simple_wedge,
-		{ 0, 0, 16}, {0, 0, 0}, { 0.5, 0, 0, 1, 0, 3}},
+		{  0, 0, 16}, { 0, 0, 0}, {  0.5, 0, 0, 1, 0}},
 	{"Box,  Simple Wedge", &box, &hull_simple_wedge,
-		{-16, 0, 8}, {16, 0, 8}, { 1, 0, 0, 1, 0, 3}},
+		{-16, 0,  8}, {16, 0, 8}, {    1, 0, 0, 1, 0}},
 	{"Box,  Simple Wedge", &box, &hull_simple_wedge,
-		{-16, 0, 5}, {16, 0, 5}, { 0.25, 0, 0, 1, 0, 3}},
+		{-16, 0,  5}, {16, 0, 5}, { 0.25, 0, 0, 1, 0}},
 	{"Box,  Simple Wedge", &box, &hull_simple_wedge,
-		{-16, 0, 12}, {16, 0, 4}, { 0.5, 0, 0, 1, 0, 3}},
+		{-16, 0, 12}, {16, 0, 4}, {  0.5, 0, 0, 1, 0}},
 
 	{"Box,  Hole. slide in", &box, &hull_hole,
-		{ 0, -16,  0}, {  0,  16,   0}, { 1, 0, 0, 1, 0, 13}},
+		{ 0, -16,  0}, {  0,  16,   0}, {    1, 0, 0, 1, 0}},
 	{"Box,  Hole. slide out", &box, &hull_hole,
-		{ 0,  16,  0}, {  0, -16,   0}, { 1, 0, 0, 1, 0, 13}},
+		{ 0,  16,  0}, {  0, -16,   0}, {    1, 0, 0, 1, 0}},
 	{"Box,  Hole. tight", &box, &hull_hole,
-		{ 0,  16,  0}, {  0,  16,  16}, { 0, 0, 0, 1, 0, 13}},
+		{ 0,  16,  0}, {  0,  16,  16}, {    0, 0, 0, 1, 0}},
 	{"Box,  Hole. tight", &box, &hull_hole,
-		{ 0,  16,  0}, {  0,  16, -16}, { 0, 0, 0, 1, 0, 13}},
+		{ 0,  16,  0}, {  0,  16, -16}, {    0, 0, 0, 1, 0}},
 	{"Box,  Hole. tight", &box, &hull_hole,
-		{ 0,  16,  0}, { 16,  16,   0}, { 0, 0, 0, 1, 0, 13}},
+		{ 0,  16,  0}, { 16,  16,   0}, {    0, 0, 0, 1, 0}},
 	{"Box,  Hole. tight", &box, &hull_hole,
-		{ 0,  16,  0}, {-16,  16,   0}, { 0, 0, 0, 1, 0, 13}},
+		{ 0,  16,  0}, {-16,  16,   0}, {    0, 0, 0, 1, 0}},
 	{"Box,  Hole. edge", &box, &hull_hole,
-		{ 0, -16,  1}, {  0,  16,   1}, { 0.25, 0, 0, 1, 0, 13}},
+		{ 0, -16,  1}, {  0,  16,   1}, { 0.25, 0, 0, 1, 0}},
 	{"Box,  Hole. edge", &box, &hull_hole,
-		{ 0, -16, -1}, {  0,  16,  -1}, { 0.25, 0, 0, 1, 0, 13}},
+		{ 0, -16, -1}, {  0,  16,  -1}, { 0.25, 0, 0, 1, 0}},
 	{"Box,  Hole. edge", &box, &hull_hole,
-		{ 1, -16,  0}, {  1,  16,   0}, { 0.25, 0, 0, 1, 0, 13}},
+		{ 1, -16,  0}, {  1,  16,   0}, { 0.25, 0, 0, 1, 0}},
 	{"Box,  Hole. edge", &box, &hull_hole,
-		{-1, -16,  0}, { -1,  16,   0}, { 0.25, 0, 0, 1, 0, 13}},
+		{-1, -16,  0}, { -1,  16,   0}, { 0.25, 0, 0, 1, 0}},
 
 	{"Box,  ridge", &box, &hull_ridge,
-		{4, 0, 41}, { 4, 0, 39}, { 0.5, 0, 0, 1, 0, 8}},
+		{4, 0, 41}, { 4, 0, 39}, { 0.5, 0, 0, 1, 0}},
 	{"Box,  ridge, all solid", &box, &hull_ridge,
-		{4, 41, 0}, { 4, 39, 0}, { 1, 1, 1, 0, 0, 8}},
+		{4, 41, 0}, { 4, 39, 0}, {   1, 1, 1, 0, 0}},
 
 	{"Box,  cave. start solid", &box, &hull_cave,
-		{  4, 0, -16}, {  4, 0,  0}, { 0.5, 0, 1, 0, 1, 14}},
+		{  4, 0, -16}, {  4, 0,  0}, { 0.5, 0, 1, 0, 1}},
 	{"Box,  cave. start solid", &box, &hull_cave,
-		{ -8, 0, -16}, { -8, 0,  0}, { 0.5, 0, 1, 0, 1, 14}},
+		{ -8, 0, -16}, { -8, 0,  0}, { 0.5, 0, 1, 0, 1}},
 	{"Box,  cave. start solid", &box, &hull_cave,
-		{ 54, 0, -16}, { 54, 0, 56}, { 0.5, 0, 1, 1, 0, 14}},
+		{ 54, 0, -16}, { 54, 0, 56}, { 0.5, 0, 1, 1, 0}},
 	{"Box,  cave", &box, &hull_cave,
-		{ 54, 0,  12}, { 54, 0, 28}, { 0.5, 0, 0, 1, 0, 14}},
+		{ 54, 0,  12}, { 54, 0, 28}, { 0.5, 0, 0, 1, 0}},
 	{"Big Box,  cave. start solid", &bigbox, &hull_cave,
-		{ 54, 0,   0}, { 54, 0, 8}, { 0.5, 0, 1, 1, 0, 14}},
+		{ 54, 0,   0}, { 54, 0,  8}, { 0.5, 0, 1, 1, 0}},
 };
 #define num_tests (sizeof (tests) / sizeof (tests[0]))
 
@@ -260,31 +259,9 @@ run_test (test_t *test)
 		hull_t     *hull = test->hull;
 		int         i, j;
 		portlist_t *p;
-		clipport_t *portal;
 		clipleaf_t *leaf;
-		int         side;
 
 		hull->nodeleafs = MOD_BuildBrushes (hull);
-		for (i = hull->firstclipnode; i <= hull->lastclipnode; i++) {
-			for (j = 0; j < 2; j++) {
-				if (((hull->clipnodes[i].children[j] >= 0)
-					 != (!hull->nodeleafs[i].leafs[j]))
-					|| (hull->nodeleafs[i].leafs[j]
-						&& (hull->nodeleafs[i].leafs[j]->contents
-							!= hull->clipnodes[i].children[j]))) {
-					printf ("bad nodeleaf %d %d\n", i, j);
-					err = 1;
-				}
-			}
-			if (hull->nodeleafs[i].leafs[0]
-				&& (hull->nodeleafs[i].leafs[0]
-					== hull->nodeleafs[i].leafs[1])) {
-				printf ("bad nodeleaf %d %d\n", i, j);
-				err = 1;
-			}
-		}
-		if (err)
-			goto nodeleaf_bail;
 		for (i = hull->firstclipnode; i <= hull->lastclipnode; i++) {
 			for (j = 0; j < 2; j++) {
 				leaf = hull->nodeleafs[i].leafs[j];
@@ -293,19 +270,6 @@ run_test (test_t *test)
 				portal_list = collect_portals (leaf->portals, portal_list);
 			}
 		}
-		for (i = 0, p = portal_list; p; i++, p = p->next) {
-			if (!p->portal->winding || !p->portal->edges) {
-				err = 1;
-				printf ("portal with missing vertex/edge information\n");
-			}
-		}
-		if (err)
-			goto nodeleaf_bail;
-		if (i != test->expect.num_portals) {
-			err = 1;
-			printf ("bad portal count: %d %d\n", test->expect.num_portals, i);
-			goto nodeleaf_bail;
-		}
 		for (p = portal_list; p; p = p->next) {
 			for (j = 0; j < p->portal->winding->numpoints; j++) {
 				p->portal->winding->points[j][0]
@@ -317,29 +281,8 @@ run_test (test_t *test)
 			}
 			FreeWinding (p->portal->edges);
 			p->portal->edges = WindingVectors (p->portal->winding, 0);
-			for (j = 0; j < 2; j++) {
-				int         found = 0;
-
-				leaf = p->portal->leafs[j];
-				for (portal = leaf->portals; portal;
-					 portal = portal->next[side]) {
-				//printf("%p %d %p %p %p\n", p, j, leaf, portal, p->portal);
-					side = portal->leafs[1] == leaf;
-					if (!side && portal->leafs[0] != leaf) {
-						printf ("mislinked portal\n");
-						err = 1;
-					}
-					if (portal == p->portal)
-						found = 1;
-				}
-				if (!found) {
-					printf ("portal unreachable from leaf\n");
-					err = 1;
-				}
-			}
 		}
 	}
-nodeleaf_bail:
 	while (portal_list) {
 		portlist_t *t = portal_list;
 		portal_list = portal_list->next;
diff --git a/libs/models/test/testportals.c b/libs/models/test/testportals.c
new file mode 100644
index 000000000..f5f31f4e6
--- /dev/null
+++ b/libs/models/test/testportals.c
@@ -0,0 +1,181 @@
+#include <stdlib.h>
+#include "QF/va.h"
+
+#include "getopt.h"
+#include "world.h"
+#include "QF/winding.h"
+
+#include "hulls.h"
+
+#define FloatCompare(a, b) (fabs (a - b) < 1e-5)
+#undef VectorCompare
+#define VectorCompare(x, y)									\
+	(FloatCompare (x[0], y[0]) && FloatCompare (x[1], y[1])	\
+	 && FloatCompare (x[2], y[2]))
+
+
+typedef struct {
+	const char *desc;
+	hull_t     *hull;
+	struct {
+		int         num_portals;
+	}           expect;
+} test_t;
+
+test_t tests[] = {
+	{"Three parallel planes 1",
+		&hull_tpp1,         { 3}},
+	{"Three parallel planes 2",
+		&hull_tpp2,         { 3}},
+	{"Three parallel planes with water",
+		&hull_tppw,         { 3}},
+	{"Step 1",
+		&hull_step1,        { 5}},
+	{"Covered Step",
+		&hull_covered_step, { 9}},
+	{"Step 2",
+		&hull_step2,        { 5}},
+	{"Step 3",
+		&hull_step3,        { 5}},
+	{"Ramp",
+		&hull_ramp,         { 4}},
+	{"Simple Wedge",
+		&hull_simple_wedge, { 3}},
+	{"Hole",
+		&hull_hole,         {13}},
+	{"ridge",
+		&hull_ridge,        { 8}},
+	{"cave",
+		&hull_cave,         {14}},
+};
+#define num_tests (sizeof (tests) / sizeof (tests[0]))
+
+int verbose = 0;
+
+typedef struct portlist_s {
+	struct portlist_s *next;
+	clipport_t *portal;
+} portlist_t;
+
+static portlist_t *
+collect_portals (clipport_t *portal, portlist_t *portal_list)
+{
+	portlist_t *p;
+
+	if (!portal)
+		return portal_list;
+	for (p = portal_list; p; p = p->next)
+		if (p->portal == portal)
+			return portal_list;
+	p = malloc (sizeof (portlist_t));
+	p->portal = portal;
+	p->next = portal_list;
+	portal_list = p;
+	portal_list = collect_portals (portal->next[0], portal_list);
+	portal_list = collect_portals (portal->next[1], portal_list);
+	return portal_list;
+}
+
+static int
+run_test (test_t *test)
+{
+	const char *desc;
+	int         err = 0;
+	static int  output = 0;
+	portlist_t *portal_list = 0;
+
+	if (!test->hull->nodeleafs) {
+		hull_t     *hull = test->hull;
+		int         i, j;
+		portlist_t *p;
+		clipport_t *portal;
+		clipleaf_t *leaf;
+		int         side;
+
+		hull->nodeleafs = MOD_BuildBrushes (hull);
+		for (i = hull->firstclipnode; i <= hull->lastclipnode; i++) {
+			for (j = 0; j < 2; j++) {
+				if (((hull->clipnodes[i].children[j] >= 0)
+					 != (!hull->nodeleafs[i].leafs[j]))
+					|| (hull->nodeleafs[i].leafs[j]
+						&& (hull->nodeleafs[i].leafs[j]->contents
+							!= hull->clipnodes[i].children[j]))) {
+					printf ("bad nodeleaf %d %d\n", i, j);
+					err = 1;
+				}
+			}
+			if (hull->nodeleafs[i].leafs[0]
+				&& (hull->nodeleafs[i].leafs[0]
+					== hull->nodeleafs[i].leafs[1])) {
+				printf ("bad nodeleaf %d %d\n", i, j);
+				err = 1;
+			}
+		}
+		if (err)
+			goto nodeleaf_bail;
+		for (i = hull->firstclipnode; i <= hull->lastclipnode; i++) {
+			for (j = 0; j < 2; j++) {
+				leaf = hull->nodeleafs[i].leafs[j];
+				if (!leaf)
+					continue;
+				portal_list = collect_portals (leaf->portals, portal_list);
+			}
+		}
+		for (i = 0, p = portal_list; p; i++, p = p->next) {
+			if (!p->portal->winding || !p->portal->edges) {
+				err = 1;
+				printf ("portal with missing vertex/edge information\n");
+			}
+		}
+		if (err)
+			goto nodeleaf_bail;
+		if (i != test->expect.num_portals) {
+			err = 1;
+			printf ("bad portal count: %d %d\n", test->expect.num_portals, i);
+			goto nodeleaf_bail;
+		}
+		for (p = portal_list; p; p = p->next) {
+			for (j = 0; j < 2; j++) {
+				int         found = 0;
+
+				leaf = p->portal->leafs[j];
+				for (portal = leaf->portals; portal;
+					 portal = portal->next[side]) {
+				//printf("%p %d %p %p %p\n", p, j, leaf, portal, p->portal);
+					side = portal->leafs[1] == leaf;
+					if (!side && portal->leafs[0] != leaf) {
+						printf ("mislinked portal\n");
+						err = 1;
+					}
+					if (portal == p->portal)
+						found = 1;
+				}
+				if (!found) {
+					printf ("portal unreachable from leaf\n");
+					err = 1;
+				}
+			}
+		}
+	}
+nodeleaf_bail:
+	while (portal_list) {
+		portlist_t *t = portal_list;
+		portal_list = portal_list->next;
+		free (t);
+	}
+	MOD_FreeBrushes (test->hull);
+
+	if (test->desc)
+		desc = va ("(%d) %s", (int)(long)(test - tests), test->desc);
+	else
+		desc = va ("test #%d", (int)(long)(test - tests));
+	if (verbose >= 0 || err) {
+		if (output)
+			puts("");
+		output = 1;
+		printf ("%s: %s\n", !err ? "PASS" : "FAIL", desc);
+	}
+	return !err;
+}
+
+#include "main.c"