diff --git a/config.json b/config.json
index ca5dcdd..b4f5cd2 100644
--- a/config.json
+++ b/config.json
@@ -4,11 +4,14 @@
 "screenHeight":600,
 "maxFPS":60,
 "FOV":100,
+"playerSpeed":5.0,
+"playerSprintModifier":2.0,
 "debug":{
   "__comment":"values are: enabled/disabled",
   "masterDebugToggle":"enabled",
   "drawDebugHud":"disabled",
   "drawLines":"enabled",
-  "overrideBackFaceCulling":"disabled"
+  "overrideBackFaceCulling":"disabled",
+  "frustumCullingOverridePercent":0
   }
 }
diff --git a/src/main/java/uk/org/floop/epq3d/Face.java b/src/main/java/uk/org/floop/epq3d/Face.java
index 3ee09fe..5d66ebb 100644
--- a/src/main/java/uk/org/floop/epq3d/Face.java
+++ b/src/main/java/uk/org/floop/epq3d/Face.java
@@ -4,6 +4,11 @@
     public PointComp[] points;
     public Point2D[] UVPoints;
     public Vector3D normal;
+
+    public double boundingSphereR;
+
+    public Point3D boundingSphereC;
+
     public Triangle[] tris;
     public int[][] trisFaceList;
     public boolean hasEdges;
@@ -13,7 +18,42 @@
     public boolean isValid;
     // fixed face
     public Face fixedFace;
-    public void initialise(){
+
+    public void initialise() {
+        double distance = 0;
+        double newDis;
+        Point3D pointA = points[0].point;
+        Point3D pointB = points[0].point;
+        Vector3D distanceVec = new Vector3D();
+        for (int i = 1; i < points.length; i += 1) {
+            distanceVec.createFrom2Points(points[0].point, points[i].point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance) {
+                pointA = points[i].point;
+                distance = newDis;
+            }
+        }
+        for (PointComp point : points) {
+            distanceVec.createFrom2Points(points[0].point, points[0].point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance) {
+                pointB = point.point;
+                distance = newDis;
+            }
+        }
+        boundingSphereC = new Point3D(
+                (pointA.x + pointB.x) / 2,
+                (pointA.y + pointB.y) / 2,
+                (pointA.z + pointB.z) / 2);
+        boundingSphereR = Math.sqrt(distance) / 2;
+        for (PointComp point :
+                points) {
+            distanceVec.createFrom2Points(boundingSphereC, point.point);
+            distance = distanceVec.getLength();
+            boundingSphereR = Math.max(boundingSphereR, distance);
+        }
+
+
         calculateNormal();
         perspectiveMappingMatrices = new Matrix[points.length - 2][2];
         separateTris();
@@ -31,295 +71,234 @@
         isInitialised = true;
     }
 
-    public void invalidate(){
-        for (Triangle tri:
-             tris) {
+    public void invalidate() {
+        for (Triangle tri :
+                tris) {
             tri.invalidate();
         }
     }
-    public int draw(drawData drawData) {
+
+    public void draw(drawData drawData) {
         double ang = normal.angleTo(new Vector3D(0.2, 0.5, 1));
         if (!isInitialised) {
             throw new RuntimeException("Face not initialised");
         }
-        if (isValid) {
-            // the check for backface culling has been done previously.
-            // initialise points
-            int numberOfPixels = 0;
-            // apply point transforms for all points within the face
-            boolean valid = applyPointTransforms(drawData.camMatrix, drawData.FPDis, drawData.scrX, drawData.scrY);
-            // this function is completed every frame, without checking whether pixels will be drawn first
-            bakePerspectiveMatrices(drawData);
+        // the check for backface culling has been done previously.
+        // initialise points
+        // apply point transforms for all points within the face
+        boolean valid = applyPointTransforms(drawData);
+        // this function is completed every frame, without checking whether pixels will be drawn first
+        bakePerspectiveMatrices(drawData);
 
-            // if all the points are valid (in front of the camera) draw all tris normally.
-            if (valid) {
-                for (Triangle tri :
-                        tris) {
-                    tri.draw(drawData, ang);
+        // if all the points are valid (in front of the camera) draw all tris normally.
+        if (valid) {
+            for (Triangle tri :
+                    tris) {
+                tri.draw(drawData, ang);
+            }
+        } else {
+            for (int tri_i = 0; tri_i < trisFaceList.length; tri_i += 1) {
+                // first, count up the number of invalid points in the triangle (points which are behind the camera)
+                int numOfInvalidPoints = 0;
+                for (int i = 0; i < 3; i += 1) {
+                    if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) {
+                        numOfInvalidPoints += 1;
+                    }
                 }
-            } else {
-            /*todo check wether there is any slowdown here
-            it might be that the compiler just sorts it but i'm not 100% sure.
-            fix would be to change it so that I only need to loop through points once, and also reduce the number
-            of temporary point objects i use */
-                for (int tri_i = 0; tri_i < trisFaceList.length; tri_i += 1) {
-                    // first, count up the number of invalid points in the triangle (points which are behind the camera)
-                    int numOfInvalidPoints = 0;
+                if (numOfInvalidPoints == 0) { // if there are no invalid points, treat the triangle normally
+                    // project all points in triangle
+                    tris[tri_i].draw(drawData, ang);
+                } else if (numOfInvalidPoints == 1) {// if one point is invalid, interpolate 2 new points and draw 2 new triangles:
+                    // find which point is the invalid one TODO could optimise and find this beforehand
+                    int invalidIndex = -1;
                     for (int i = 0; i < 3; i += 1) {
                         if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) {
-                            numOfInvalidPoints += 1;
+                            invalidIndex = i;
+                            break;
                         }
                     }
-                    if (numOfInvalidPoints == 0) { // if there are no invalid points, treat the triangle normally
-                        // project all points in triangle
-                        tris[tri_i].draw(drawData, ang);
-                    } else if (numOfInvalidPoints == 1) {// if one point is invalid, interpolate 2 new points and draw 2 new triangles:
-                        // find which point is the invalid one TODO could optimise and find this beforehand
-                        int invalidIndex = -1;
-                        for (int i = 0; i < 3; i += 1) {
-                            if (points[trisFaceList[tri_i][i]].getRotatedPoint().z < 0.001) {
-                                invalidIndex = i;
-                                break;
-                            }
-                        }
-                        if (invalidIndex == -1) {
-                            throw new RuntimeException("How did this happen?!");
-                        }
-                        Point3D oldPoint1 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 1, 3)]].getRotatedPoint();
-                        Point3D oldPoint2 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 2, 3)]].getRotatedPoint();
-                        Point3D invalidPoint = points[trisFaceList[tri_i][invalidIndex]].getRotatedPoint();
-                        Point3D newPoint1;
-                        Point3D newPoint2;
-                        // assign old points 1 and 2 based on the index of the invalid point
-                        {
-                            // interpolate between oldPoint1 and invalidPoint
-                            // solving for z = 0.001 for the line between oldPoint1 and invalidPoint,
-                            // separately in the xz and yz planes.
-                            double gradX = (oldPoint1.z - invalidPoint.z) /
-                                    (oldPoint1.x - invalidPoint.x);
-                            double gradY = (oldPoint1.z - invalidPoint.z) /
-                                    (oldPoint1.y - invalidPoint.y);
+                    if (invalidIndex == -1) {
+                        throw new RuntimeException("How did this happen?!");
+                    }
+                    Point3D oldPoint1 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 1, 3)]].getRotatedPoint();
+                    Point3D oldPoint2 = points[trisFaceList[tri_i][Math.floorMod(invalidIndex + 2, 3)]].getRotatedPoint();
+                    Point3D invalidPoint = points[trisFaceList[tri_i][invalidIndex]].getRotatedPoint();
+                    Point3D newPoint1;
+                    Point3D newPoint2;
+                    // assign old points 1 and 2 based on the index of the invalid point
+                    // interpolate between oldPoint1 and invalidPoint
+                    newPoint1 = interpolate(oldPoint1, invalidPoint);
+                    // interpolate between oldPoint2 and invalid point
+                    newPoint2 = interpolate(oldPoint2, invalidPoint);
 
-                            newPoint1 = new Point3D(
-                                    (0.001 + gradX * oldPoint1.x - oldPoint1.z) / gradX,
-                                    (0.001 + gradY * oldPoint1.y - oldPoint1.z) / gradY,
-                                    0.001);
-                            if (!Double.isFinite(gradX)) {
-                                newPoint1.x = oldPoint1.x;
-                            }
-                            if (!Double.isFinite(gradY)) {
-                                newPoint1.y = oldPoint1.y;
-                            }
+                    Triangle newTri;
+                    newTri = new Triangle(oldPoint1.project(drawData), newPoint2.project(drawData), newPoint1.project(drawData),
+                            new boolean[]{false, false, false},
+                            tris[tri_i].texture,
+                            tris[tri_i].perspectiveMappingMatrix);
+                    newTri.draw(drawData, ang);
+                    newTri = new Triangle(oldPoint1.project(drawData), oldPoint2.project(drawData), newPoint2.project(drawData),
+                            new boolean[]{false, false, false},
+                            tris[tri_i].texture,
+                            tris[tri_i].perspectiveMappingMatrix);
+                    newTri.draw(drawData, ang);
+                } else if (numOfInvalidPoints == 2) { // if two points are invalid, interpolate and draw triangle w/ new points:
+                    int validIndex = -1;
+                    for (int i = 0; i < 3; i += 1) {
+                        if (points[trisFaceList[tri_i][i]].getRotatedPoint().z > 0.001) {
+                            validIndex = i;
+                            break;
                         }
+                    }
+                    if (validIndex == -1) {
+                        throw new RuntimeException("How did this happen?!");
+                    }
+                    Point3D invalidPoint1 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 1, 3)]].getRotatedPoint();
+                    Point3D invalidPoint2 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 2, 3)]].getRotatedPoint();
+                    Point3D oldPoint = points[trisFaceList[tri_i][validIndex]].getRotatedPoint();
+                    Point3D newPoint1;
+                    Point3D newPoint2;
+                    // interpolate for z = 0.001 between invalid1 and oldPoint
+                    newPoint1 = interpolate(oldPoint, invalidPoint1);
 
-                        // interpolate between oldPoint2 and invalid point
-                        {
-                            // interpolate between oldPoint2 and invalidPoint
-                            // solving for z = 0.001 for the line between oldPoint2 and invalidPoint,
-                            // separately in the xz and yz planes.
-                            double gradX = (oldPoint2.z - invalidPoint.z) /
-                                    (oldPoint2.x - invalidPoint.x);
-                            double gradY = (oldPoint2.z - invalidPoint.z) /
-                                    (oldPoint2.y - invalidPoint.y);
+                    // interpolate for z = 0.001 between invalid2 and oldPoint
+                    newPoint2 = interpolate(oldPoint, invalidPoint2);
 
-                            newPoint2 = new Point3D(
-                                    (0.001 + gradX * oldPoint2.x - oldPoint2.z) / gradX,
-                                    (0.001 + gradY * oldPoint2.y - oldPoint2.z) / gradY,
-                                    0.001);
-                            if (!Double.isFinite(gradX)) {
-                                newPoint2.x = oldPoint2.x;
-                            }
-                            if (!Double.isFinite(gradY)) {
-                                newPoint2.y = oldPoint2.y;
-                            }
-                        }
+                    // create and draw new triangle
+                    Triangle newTri;
+                    newTri = new Triangle(newPoint1.project(drawData), newPoint2.project(drawData), oldPoint.project(drawData),
+                            new boolean[]{false, false, false},
+                            tris[tri_i].texture,
+                            tris[tri_i].perspectiveMappingMatrix);
+                    newTri.draw(drawData, ang);
+                } // if all points are invalid, do nothing
+            }
+        }
+    }
+    private Point3D interpolate(Point3D oldPoint, Point3D invalidPoint){
+        double gradX = (oldPoint.z - invalidPoint.z) /
+                (oldPoint.x - invalidPoint.x);
+        double gradY = (oldPoint.z - invalidPoint.z) /
+                (oldPoint.y - invalidPoint.y);
 
-                        Triangle newTri;
-                        newTri = new Triangle(oldPoint1.project(drawData), newPoint2.project(drawData), newPoint1.project(drawData),
-                                new boolean[]{false, false, false},
-                                tris[tri_i].texture,
-                                tris[tri_i].perspectiveMappingMatrix);
-                        newTri.draw(drawData, ang);
-                        newTri = new Triangle(oldPoint1.project(drawData), oldPoint2.project(drawData), newPoint2.project(drawData),
-                                new boolean[]{false, false, false},
-                                tris[tri_i].texture,
-                                tris[tri_i].perspectiveMappingMatrix);
-                        newTri.draw(drawData, ang);
-                    } else if (numOfInvalidPoints == 2) { // if two points are invalid, interpolate and draw triangle w/ new points:
-                        int validIndex = -1;
-                        for (int i = 0; i < 3; i += 1) {
-                            if (points[trisFaceList[tri_i][i]].getRotatedPoint().z > 0.001) {
-                                validIndex = i;
-                                break;
-                            }
-                        }
-                        if (validIndex == -1) {
-                            throw new RuntimeException("How did this happen?!");
-                        }
-                        Point3D invalidPoint1 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 1, 3)]].getRotatedPoint();
-                        Point3D invalidPoint2 = points[trisFaceList[tri_i][Math.floorMod(validIndex + 2, 3)]].getRotatedPoint();
-                        Point3D oldPoint = points[trisFaceList[tri_i][validIndex]].getRotatedPoint();
-                        Point3D newPoint1;
-                        Point3D newPoint2;
-                        // interpolate for z = 0.001 between invalid1 and oldPoint
-                        {
-                            double gradX = (oldPoint.z - invalidPoint1.z) /
-                                    (oldPoint.x - invalidPoint1.x);
-                            double gradY = (oldPoint.z - invalidPoint1.z) /
-                                    (oldPoint.y - invalidPoint1.y);
+        Point3D newPoint = new Point3D(
+                (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX,
+                (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY,
+                0.001);
+        if (!Double.isFinite(gradX)) {
+            newPoint.x = oldPoint.x;
+        }
+        if (!Double.isFinite(gradY)) {
+            newPoint.y = oldPoint.y;
+        }
+        return newPoint;
+    }
+    /*
+            if (valid) {
+                //drawTris(img, zBuf, FPDis, scrX, scrY);}
+            else {
+                ArrayList<PointComp> newPoints = new ArrayList<>();
 
-                            newPoint1 = new Point3D(
-                                    (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX,
-                                    (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY,
-                                    0.001);
-                            if (!Double.isFinite(gradX)) {
-                                newPoint1.x = oldPoint.x;
-                            }
-                            if (!Double.isFinite(gradY)) {
-                                newPoint1.y = oldPoint.y;
-                            }
-                        }
-                        // interpolate for z = 0.001 between invalid2 and oldPoint
-                        {
-                            double gradX = (oldPoint.z - invalidPoint2.z) /
-                                    (oldPoint.x - invalidPoint2.x);
-                            double gradY = (oldPoint.z - invalidPoint2.z) /
-                                    (oldPoint.y - invalidPoint2.y);
+                // if there are points behind the camera, loop through all the points and interpolate a point that is.
+                // The perspective mapping matrix is calculated beforehand, so we don't need to move UVS
+                PointComp lastPoint = points[points.length - 1];
+                boolean lastValid = lastPoint.getRotatedPoint().z > 0.1;
+                boolean thisValid;
+                for (PointComp point:
+                        points) {
+                    thisValid = point.getRotatedPoint().z > 0.1;
+                    // We need to do different things depending on whether the previous point was also a valid point, or not.
+                    // first - if only 1 of the last point or this point were valid,
+                    // interpolate between them to get the point at the screen. (XOR)
+                    if(lastValid ^ thisValid){
+                        // solving for z = 0.1 for the line between thisPoint and lastPoint,
+                        // separately in the xz and yz planes.
+                        double gradX = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
+                                (point.getRotatedPoint().x - lastPoint.getRotatedPoint().x);
+                        double gradY = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
+                                (point.getRotatedPoint().y - lastPoint.getRotatedPoint().y);
 
-                            newPoint2 = new Point3D(
-                                    (0.001 + gradX * oldPoint.x - oldPoint.z) / gradX,
-                                    (0.001 + gradY * oldPoint.y - oldPoint.z) / gradY,
-                                    0.001);
-                            if (!Double.isFinite(gradX)) {
-                                newPoint2.x = oldPoint.x;
-                            }
-                            if (!Double.isFinite(gradY)) {
-                                newPoint2.y = oldPoint.y;
-                            }
-                        }
-
-                        // create and draw new triangle
-                        Triangle newTri;
-                        newTri = new Triangle(newPoint1.project(drawData), newPoint2.project(drawData), oldPoint.project(drawData),
-                                new boolean[]{false, false, false},
-                                tris[tri_i].texture,
-                                tris[tri_i].perspectiveMappingMatrix);
-                        newTri.draw(drawData, ang);
-                    } // if all points are invalid, do nothing
+                        newPoints.add(new PointComp(
+                                (0.1+gradX*point.getRotatedPoint().x-point.getRotatedPoint().z)/gradX,
+                                (0.1+gradY*point.getRotatedPoint().y-point.getRotatedPoint().z)/gradY,
+                                0.1));
+                        if(!Double.isFinite(gradX)){
+                            newPoints.get(newPoints.size() - 1).point.x = point.getRotatedPoint().x;}
+                        if(!Double.isFinite(gradY)){
+                            newPoints.get(newPoints.size() - 1).point.y = point.getRotatedPoint().y;}
+                    }
+                    // finally - if the current point is valid, then add it to the list
+                    if(thisValid){
+                        newPoints.add(new PointComp(
+                                point.getRotatedPoint().x,
+                                point.getRotatedPoint().y,
+                                point.getRotatedPoint().z));
+                    }
+                    lastPoint = point;
+                    lastValid = thisValid;
+                }
+                // there must be at least 3 points in the face for it to be drawn successfully
+                if(newPoints.size() >= 3) {
+                    // finished fixing points, now we need to create a new face consisting of those points.
+                    fixedFace.points = newPoints.toArray(new PointComp[0]);
+                    fixedFace.separateTris();
+                    // invalidate all the points so they are actually calculated
+                    for (PointComp point :
+                            newPoints) {
+                        point.invalidate();
+                    }
+                    Matrix identMat = new Matrix(3,3);
+                    // we use an identity matrix because the points of the fixed face are in camera coordinates already.
+                    // we just need the projected 2d point.
+                    identMat.setItems(new double[][]{
+                            {1,0,0},
+                            {0,1,0},
+                            {0,0,1}
+                    });
+                    fixedFace.applyPointTransforms(identMat, FPDis, scrX, scrY);
+                    fixedFace.drawTris(img, zBuf, FPDis, scrX, scrY);
                 }
             }
             return numberOfPixels;
-
-        } else {
-            return 0;
         }
-    }
-
-/*
-        if (valid) {
-            //drawTris(img, zBuf, FPDis, scrX, scrY);}
-        else {
-            ArrayList<PointComp> newPoints = new ArrayList<>();
-
-            // if there are points behind the camera, loop through all the points and interpolate a point that is.
-            // The perspective mapping matrix is calculated beforehand, so we don't need to move UVS
-            PointComp lastPoint = points[points.length - 1];
-            boolean lastValid = lastPoint.getRotatedPoint().z > 0.1;
-            boolean thisValid;
-            for (PointComp point:
-                    points) {
-                thisValid = point.getRotatedPoint().z > 0.1;
-                // We need to do different things depending on whether the previous point was also a valid point, or not.
-                // first - if only 1 of the last point or this point were valid,
-                // interpolate between them to get the point at the screen. (XOR)
-                if(lastValid ^ thisValid){
-                    // solving for z = 0.1 for the line between thisPoint and lastPoint,
-                    // separately in the xz and yz planes.
-                    double gradX = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
-                            (point.getRotatedPoint().x - lastPoint.getRotatedPoint().x);
-                    double gradY = (point.getRotatedPoint().z - lastPoint.getRotatedPoint().z) /
-                            (point.getRotatedPoint().y - lastPoint.getRotatedPoint().y);
-
-                    newPoints.add(new PointComp(
-                            (0.1+gradX*point.getRotatedPoint().x-point.getRotatedPoint().z)/gradX,
-                            (0.1+gradY*point.getRotatedPoint().y-point.getRotatedPoint().z)/gradY,
-                            0.1));
-                    if(!Double.isFinite(gradX)){
-                        newPoints.get(newPoints.size() - 1).point.x = point.getRotatedPoint().x;}
-                    if(!Double.isFinite(gradY)){
-                        newPoints.get(newPoints.size() - 1).point.y = point.getRotatedPoint().y;}
-                }
-                // finally - if the current point is valid, then add it to the list
-                if(thisValid){
-                    newPoints.add(new PointComp(
-                            point.getRotatedPoint().x,
-                            point.getRotatedPoint().y,
-                            point.getRotatedPoint().z));
-                }
-                lastPoint = point;
-                lastValid = thisValid;
-            }
-            // there must be at least 3 points in the face for it to be drawn successfully
-            if(newPoints.size() >= 3) {
-                // finished fixing points, now we need to create a new face consisting of those points.
-                fixedFace.points = newPoints.toArray(new PointComp[0]);
-                fixedFace.separateTris();
-                // invalidate all the points so they are actually calculated
-                for (PointComp point :
-                        newPoints) {
-                    point.invalidate();
-                }
-                Matrix identMat = new Matrix(3,3);
-                // we use an identity matrix because the points of the fixed face are in camera coordinates already.
-                // we just need the projected 2d point.
-                identMat.setItems(new double[][]{
-                        {1,0,0},
-                        {0,1,0},
-                        {0,0,1}
-                });
-                fixedFace.applyPointTransforms(identMat, FPDis, scrX, scrY);
-                fixedFace.drawTris(img, zBuf, FPDis, scrX, scrY);
-            }
-        }
-        return numberOfPixels;
-    }
-*/
-    public boolean applyPointTransforms(Matrix camMatrix, double FPDis, int scrX, int scrY){
+    */
+    public boolean applyPointTransforms(drawData drawData) {
         boolean valid = true;
-        for (PointComp point:
+        for (PointComp point :
                 points) {
-            point.setRotatedPoint(camMatrix);
+            point.setRotatedPoint(drawData.camMatrix);
             // if any points are behind the camera, we will need to handle it differently.
-            if(point.getRotatedPoint().z < 0.001){
+            if (point.getRotatedPoint().z < 0.001) {
                 valid = false;
-            }
-            else{
+            } else {
                 // only worth calculating the projected point if that point is valid
-                point.setProjectedPoint(FPDis, scrX, scrY);
+                point.setProjectedPoint(drawData);
             }
         }
         return valid;
     }
-    public void separateTris(){
+
+    public void separateTris() {
         Triangle[] newTris = new Triangle[points.length - 2];
         int[][] newTrisFaceList = new int[points.length - 2][3];
 
-        for(int i = 0; i< newTris.length; i+=1){
+        for (int i = 0; i < newTris.length; i += 1) {
             newTrisFaceList[i][0] = 0;
-            newTrisFaceList[i][1] = i+1;
-            newTrisFaceList[i][2] = i+2;
+            newTrisFaceList[i][1] = i + 1;
+            newTrisFaceList[i][2] = i + 2;
 
             newTris[i] = new Triangle(
                     points[0].getProjectedPoint(),
-                    points[i+1].getProjectedPoint(),
-                    points[i+2].getProjectedPoint(),
+                    points[i + 1].getProjectedPoint(),
+                    points[i + 2].getProjectedPoint(),
                     new boolean[]{hasEdges, hasEdges, hasEdges},
                     texture, perspectiveMappingMatrices[i]);
         }
         tris = newTris;
         trisFaceList = newTrisFaceList;
     }
-    public void calculateNormal(){
+
+    public void calculateNormal() {
         // too many new variables
         Point3D point0 = points[0].point;
         Point3D point1 = points[1].point;
@@ -327,14 +306,17 @@
         Vector3D vec2 = new Vector3D(); // initialisation otherwise intellij gets mad
 
         // find a vector which is not inline with other vectors
-        boolean valid = false; int i = 2;
-        while(!valid && i <= points.length) {
+        boolean valid = false;
+        int i = 2;
+        while (!valid && i <= points.length) {
             point0 = point1;
             point1 = points[Math.floorMod(i, points.length)].point;
 
-            vec2.x = point1.x - point0.x; vec2.y = point1.y - point0.y; vec2.z = point1.z - point0.z;
+            vec2.x = point1.x - point0.x;
+            vec2.y = point1.y - point0.y;
+            vec2.z = point1.z - point0.z;
             double angle = Math.abs(vec1.angleTo(vec2));
-            if(angle > 0.1 && angle < 2*Math.PI - 0.1){
+            if (angle > 0.1 && angle < 2 * Math.PI - 0.1) {
                 //  if the angle between the vectors is between a threshold, the two vectors are valid.
                 // else, calculate the second vector using a different set of points.
                 valid = true;
@@ -342,9 +324,6 @@
             i += 1;
         }
         isValid = valid;
-        if (!isValid){
-            int a = 12;
-        }
         normal = vec1.cross(vec2);
     }
 
@@ -354,49 +333,49 @@
         // temporary unit up vector for reference
         Vector2D vFin = new Vector2D(0, 1);
         // temporary vector representing a vector between two points on the uv / triangle
-        Vector2D vOrig = new Vector2D(0,0);
+        Vector2D vOrig = new Vector2D(0, 0);
 
         // repeat for every triangle (this method must be called BEFORE separateTris is called)
         int i = 0;
-        for (int[] tri: trisFaceList) {
+        for (int[] tri : trisFaceList) {
             // generate two matrices -
             // uvTo00 takes the uv's point 1 to 0,0 with its point 2 at 0,1 (rotated, translated and scaled)
             Matrix uvTo00 = new Matrix(4, 4);
-            uvTo00.setItems(new double[][]{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}});
+            uvTo00.setItems(new double[][]{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
             {
                 vOrig.createFrom2Points(UVPoints[tri[0]], UVPoints[tri[1]]);
                 double rotAng = -vOrig.angleTo(vFin);
                 // set to rotation
                 MultMat.setItems(new double[][]{
-                        {Math.cos(rotAng), Math.sin(rotAng),0,0},
-                        {-Math.sin(rotAng),Math.cos(rotAng),0,0},
-                        {0,0,1,0},
-                        {0,0,0,1},
+                        {Math.cos(rotAng), Math.sin(rotAng), 0, 0},
+                        {-Math.sin(rotAng), Math.cos(rotAng), 0, 0},
+                        {0, 0, 1, 0},
+                        {0, 0, 0, 1},
                 });
                 uvTo00.multiply(MultMat);
 
-                double scaleFac = 1/ vOrig.getLength();
+                double scaleFac = 1 / vOrig.getLength();
                 // set to scaling
                 MultMat.setItems(new double[][]{
-                        {scaleFac,0,0,0},
-                        {0,scaleFac,0,0},
-                        {0,0,scaleFac,0},
-                        {0,0,0,1},
+                        {scaleFac, 0, 0, 0},
+                        {0, scaleFac, 0, 0},
+                        {0, 0, scaleFac, 0},
+                        {0, 0, 0, 1},
                 });
                 uvTo00.multiply(MultMat);
                 // set to translation
                 MultMat.setItems(new double[][]{
-                        {1,0,0,-UVPoints[tri[0]].x},
-                        {0,1,0,-UVPoints[tri[0]].y},
-                        {0,0,1,0},
-                        {0,0,0,1},
+                        {1, 0, 0, -UVPoints[tri[0]].x},
+                        {0, 1, 0, -UVPoints[tri[0]].y},
+                        {0, 0, 1, 0},
+                        {0, 0, 0, 1},
                 });
                 uvTo00.multiply(MultMat);
 
             }
             // faceTo00 takes the face's point 1 to 0,0 with its point 2 at 1,0 (rotated, translated and scale
             Matrix faceTo00 = new Matrix(4, 4);
-            faceTo00.setItems(new double[][]{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}});
+            faceTo00.setItems(new double[][]{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}});
             {
                 Vector3D rotVector301 = new Vector3D();
                 Vector3D rotVector302 = new Vector3D();
@@ -411,10 +390,10 @@
                     vOrig.y = rotVector301.y;
                     rotAng = vFin.angleTo(vOrig);
                     MultMat.setItems(new double[][]{
-                            { Math.cos(rotAng), Math.sin(rotAng), 0, 0},
+                            {Math.cos(rotAng), Math.sin(rotAng), 0, 0},
                             {-Math.sin(rotAng), Math.cos(rotAng), 0, 0},
-                            { 0,                0,                1, 0},
-                            { 0, 0, 0, 1},
+                            {0, 0, 1, 0},
+                            {0, 0, 0, 1},
                     });
                     faceTo00 = MultMat.multiplyGetResult(faceTo00);
                     faceTo00.multiplyVec3to(rotVector301, rotVector301);
@@ -423,8 +402,8 @@
                     vOrig.y = rotVector301.y;
                     rotAng = -vFin.angleTo(vOrig);
                     MultMat.setItems(new double[][]{
-                            {1, 0,                 0,                0},
-                            {0,  Math.cos(rotAng), Math.sin(rotAng), 0},
+                            {1, 0, 0, 0},
+                            {0, Math.cos(rotAng), Math.sin(rotAng), 0},
                             {0, -Math.sin(rotAng), Math.cos(rotAng), 0},
                             {0, 0, 0, 1},
                     });
@@ -438,8 +417,8 @@
                     rotAng = -vFin2.angleTo(vOrig);
                     MultMat.setItems(new double[][]{
                             {Math.cos(rotAng), 0, -Math.sin(rotAng), 0},
-                            {0,                1,  0,                0},
-                            {Math.sin(rotAng), 0,  Math.cos(rotAng), 0},
+                            {0, 1, 0, 0},
+                            {Math.sin(rotAng), 0, Math.cos(rotAng), 0},
                             {0, 0, 0, 1},
                     });
                     faceTo00 = MultMat.multiplyGetResult(faceTo00);
@@ -448,17 +427,17 @@
                 rotVector301.createFrom2Points(points[tri[0]].point, points[tri[1]].point);
                 double scaleFac = 1 / rotVector301.getLength();
                 MultMat.setItems(new double[][]{
-                        {scaleFac,0,0,0},
-                        {0,scaleFac,0,0},
-                        {0,0,scaleFac,0},
-                        {0,0,0,1},
+                        {scaleFac, 0, 0, 0},
+                        {0, scaleFac, 0, 0},
+                        {0, 0, scaleFac, 0},
+                        {0, 0, 0, 1},
                 });
                 faceTo00.multiply(MultMat);
                 MultMat.setItems(new double[][]{
-                        {1,0,0,-points[tri[0]].point.x},
-                        {0,1,0,-points[tri[0]].point.y},
-                        {0,0,1,-points[tri[0]].point.z},
-                        {0,0,0,1},
+                        {1, 0, 0, -points[tri[0]].point.x},
+                        {0, 1, 0, -points[tri[0]].point.y},
+                        {0, 0, 1, -points[tri[0]].point.z},
+                        {0, 0, 0, 1},
                 });
                 faceTo00.multiply(MultMat);
 
@@ -483,7 +462,7 @@
                 faceTo00 = MultMat.multiplyGetResult(faceTo00);
 
                 double yShearFac = (pointUV.y - pointFace.y) / pointUV.x;
-                if (!Double.isFinite(yShearFac)){
+                if (!Double.isFinite(yShearFac)) {
                     yShearFac = 0;
                 }
                 MultMat.setItems(new double[][]{
@@ -523,12 +502,12 @@
         // should decrease processing time of each pixel by a lot
     }
 
-    private void bakePerspectiveMatrices(drawData drawData){
+    private void bakePerspectiveMatrices(drawData drawData) {
         // calculate matrix which converts 2d points into 3d points
         {
             // first, we get the rotated plane points and normal points
             Point3D planePoint = points[0].getRotatedPoint();
-            Vector3D rotatedNormalVector = new Vector3D(0,0,0);
+            Vector3D rotatedNormalVector = new Vector3D(0, 0, 0);
             drawData.camMatrix.multiplyVec3to(normal, rotatedNormalVector);
             /* next we define matrices according to the simultaneous equations:
             1, derived from projection:
@@ -555,8 +534,8 @@
             int y = 0;
             Matrix simulMat = new Matrix(3, 3);
             simulMat.setItems(new double[][]{
-                    {0, drawData.FPDis, (2*x/(double)drawData.scrX)-1},
-                    {drawData.scrX*drawData.FPDis, 0, drawData.scrY-2*y},
+                    {0, drawData.FPDis, (2 * x / (double) drawData.scrX) - 1},
+                    {drawData.scrX * drawData.FPDis, 0, drawData.scrY - 2 * y},
                     {normal.x, normal.y, normal.z},
             });
             Matrix simulMat2 = new Matrix(1, 3);
diff --git a/src/main/java/uk/org/floop/epq3d/Object3d.java b/src/main/java/uk/org/floop/epq3d/Object3d.java
index ff85518..c6327b3 100644
--- a/src/main/java/uk/org/floop/epq3d/Object3d.java
+++ b/src/main/java/uk/org/floop/epq3d/Object3d.java
@@ -1,5 +1,7 @@
 package uk.org.floop.epq3d;
 
+import java.util.ArrayList;
+
 public class Object3d {
     public PointComp[] points;
     public int[][] faceList;
@@ -11,46 +13,57 @@
     public boolean hasEdges;
     public Texture[] textures;
 
-    public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, Texture[] _textures, boolean initialise){
+    public Object3d(PointComp[] _points, int[][] _faceList, Point2D[][] _uvPoints, boolean _hasEdges, Texture[] _textures, boolean initialise) {
         points = _points;
         faceList = _faceList;
         uvPoints = _uvPoints;
         hasEdges = _hasEdges;
         textures = _textures;
 
-        if(initialise){initialise();}
+        if (initialise) {
+            initialise();
+        }
     }
 
 
-    public void invalidate(){
-        for (Face face:
-             faces) {
+    public void invalidate() {
+        for (Face face :
+                faces) {
             face.invalidate();
         }
     }
-    public void draw(drawData drawData){
-        int iterator = 0;
-        for (Face face:
-             faces) {
+
+    public void draw(drawData drawData, ArrayList<Integer> frustumInfo) {
+        for (Face face :
+                faces) {
             // check whether faces are pointing towards or away from the camera
-            Vector3D camVec = new Vector3D(0,0,0);
+            Vector3D camVec = new Vector3D(0, 0, 0);
             camVec.createFrom2Points(drawData.playerPos, face.points[0].point);
-            int numberOfPixels = 0;
-            if(drawData.backfaceCullingDisabled || face.normal.angleTo(camVec) >= Math.PI/2-0.01){
-                numberOfPixels = face.draw(drawData);
+            if (drawData.backfaceCullingDisabled || face.normal.angleTo(camVec) >= Math.PI / 2 - 0.01) {
+                // check for frustum culling of faces
+                // works the same as for collections.
+                // see explanation within ObjectCollection
+                boolean draw = true;
+                for (int planeId:
+                        frustumInfo) {
+                    double dist = drawData.frustumPlanes[planeId].getDistance(face.boundingSphereC);
+                    if (dist < -face.boundingSphereR + face.boundingSphereR * drawData.frustumCullingOverridePercent){
+                        draw = false;
+                        break;
+                    }
+                }
+                if(draw){face.draw(drawData);}
             }
-            if(drawData.debugHud){
-            drawData.debugImg.getGraphics().drawString(iterator + ": " +numberOfPixels , 10, 20 + 10*iterator);
-            iterator += 1;}
         }
     }
-    public void initialise(){
+
+    public void initialise() {
         // init faces
         faces = new Face[faceList.length];
-        for (int face = 0; face < faceList.length; face+= 1) {
+        for (int face = 0; face < faceList.length; face += 1) {
             faces[face] = new Face();
             faces[face].points = new PointComp[faceList[face].length];
-            for (int point = 0; point < faceList[face].length; point += 1){
+            for (int point = 0; point < faceList[face].length; point += 1) {
                 faces[face].points[point] = points[faceList[face][point]];
             }
             faces[face].hasEdges = hasEdges;
@@ -63,36 +76,33 @@
         double newDis;
         Point3D pointA = points[0].point;
         Point3D pointB = points[0].point;
-        // todo - maybe use some vector classes?7
-        // i can't be bothered
-        for (int i = 1; i < points.length; i+=1) {
-            newDis = Math.pow(points[0].point.x - points[i].point.x, 2) +
-                    Math.pow(points[0].point.y - points[i].point.y, 2) +
-                    Math.pow(points[0].point.z - points[i].point.z, 2);
-            if (newDis >= distance){
+        Vector3D distanceVec = new Vector3D();
+        for (int i = 1; i < points.length; i += 1) {
+            distanceVec.createFrom2Points(points[0].point, points[i].point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance) {
                 pointA = points[i].point;
-                distance = newDis;}}
+                distance = newDis;
+            }
+        }
         for (PointComp point : points) {
-            newDis = Math.pow(pointA.x - point.point.x, 2) +
-                    Math.pow(pointA.y - point.point.y, 2) +
-                    Math.pow(pointA.z - point.point.z, 2);
-            if (newDis >= distance){
+            distanceVec.createFrom2Points(points[0].point, points[0].point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance) {
                 pointB = point.point;
-                distance = newDis;}}
+                distance = newDis;
+            }
+        }
         boundingSphereC = new Point3D(
                 (pointA.x + pointB.x) / 2,
-                (pointA.y + pointB.y)/2,
-                (pointA.z + pointB.z)/2);
-        boundingSphereR = Math.sqrt(distance)/2;
-        for (PointComp point:
+                (pointA.y + pointB.y) / 2,
+                (pointA.z + pointB.z) / 2);
+        boundingSphereR = Math.sqrt(distance) / 2;
+        for (PointComp point :
                 points) {
-            distance = Math.sqrt(
-                    Math.pow(boundingSphereC.x - point.point.x, 2)+
-                    Math.pow(boundingSphereC.y - point.point.y, 2)+
-                    Math.pow(boundingSphereC.z - point.point.z, 2));
-            if(distance > boundingSphereR){
-                boundingSphereR = distance;
-            }
+            distanceVec.createFrom2Points(boundingSphereC, point.point);
+            distance = distanceVec.getLength();
+            boundingSphereR = Math.max(boundingSphereR, distance);
         }
     }
 }
diff --git a/src/main/java/uk/org/floop/epq3d/ObjectCollection.java b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java
index 0433f08..999ddf1 100644
--- a/src/main/java/uk/org/floop/epq3d/ObjectCollection.java
+++ b/src/main/java/uk/org/floop/epq3d/ObjectCollection.java
@@ -5,14 +5,14 @@
 // stores both objects and other object collections
 public class ObjectCollection {
     public String name;
-    public ArrayList<PointComp> points = new ArrayList<PointComp>();
+    public ArrayList<PointComp> points = new ArrayList<>();
 
     //private Object3d collectionObject;
     public double boundingSphereR;
     public Point3D boundingSphereC;
 
-    public ArrayList<ObjectCollection> subCollections = new ArrayList<ObjectCollection>();
-    public ArrayList<Object3d> objects = new ArrayList<Object3d>();
+    public ArrayList<ObjectCollection> subCollections = new ArrayList<>();
+    public ArrayList<Object3d> objects = new ArrayList<>();
 
     public ArrayList<PointComp> initialiseAll(){
         for (ObjectCollection subCollection:
@@ -24,6 +24,43 @@
 
         return points;
     }
+    public void initialise(){
+        // init bounding sphere
+        double distance = 0;
+        double newDis;
+        Point3D pointA = points.get(0).point;
+        Point3D pointB = points.get(0).point;
+        Vector3D distanceVec = new Vector3D();
+        // find the point which is the furthest away from arbitrary point 0
+        for (int i = 1; i < points.size(); i+=1) {
+            distanceVec.createFrom2Points(points.get(0).point, points.get(i).point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance){
+                pointA = points.get(i).point;
+                distance = newDis;
+            }
+        }
+        // find the point which is the furthest away the point which was found previously
+        for (PointComp point : points) {
+            distanceVec.createFrom2Points(pointA, point.point);
+            newDis = distanceVec.getLength();
+            if (newDis >= distance){
+                pointB = point.point;
+                distance = newDis;}}
+        // define the bounding sphere in terms of these two furthest points
+        boundingSphereC = new Point3D(
+                (pointA.x + pointB.x)/2,
+                (pointA.y + pointB.y)/2,
+                (pointA.z + pointB.z)/2);
+        boundingSphereR = Math.sqrt(distance)/2;
+        // make the radius bigger depending on the ??
+        for (PointComp point:
+                points) {
+            distanceVec.createFrom2Points(boundingSphereC, point.point);
+            distance = distanceVec.getLength();
+            boundingSphereR = Math.max(boundingSphereR, distance);
+        }
+    }
     public void invalidate(boolean invalidatePoints){
         // the first level of object collections should contain all the points for the sublevels.
         // this means that we only need to invalidate them at the top level
@@ -41,37 +78,60 @@
         }
     }
 
-    public void draw(drawData drawData){
+    public void draw(drawData drawData, ArrayList<Integer> frustumInfo){
         for (Object3d object:
              objects) {
             boolean draw = true;
-            int i = 0;
-            for (Plane plane:
-                 drawData.frustumPlanes) {
+            ArrayList<Integer> newFrustumInfo = new ArrayList<>();
+            // works the same as for collections.
+            // see explanation below
+            for (int planeId:
+                    frustumInfo) {
+                double dist = drawData.frustumPlanes[planeId].getDistance(object.boundingSphereC);
+                if(drawData.debugHud || true) {
                     drawData.debugImg.getGraphics().drawString(
-                            "Dis: " + String.format("%.1f", plane.getDistance(object.boundingSphereC)), 500, 10 + 20*i);
-                if(plane.getDistance(object.boundingSphereC) < -object.boundingSphereR){
+                            "Dis: " + String.format("%.1f", dist + object.boundingSphereR), 500, 10 + 20 * planeId);
+                }
+                if (dist < -object.boundingSphereR){
                     draw = false;
-                    break;}
-                i += 1;
+                    break;
+                } else if (dist < object.boundingSphereR){
+                    newFrustumInfo.add(planeId);
+                }
             }
-            if(draw){object.draw(drawData);}
+            if(draw){object.draw(drawData, newFrustumInfo);}
         }
-        // todo check for frustum culling
         for(ObjectCollection collection:
         subCollections){
             boolean draw = true;
-            int i = 0;
-            for (Plane plane:
-                    drawData.frustumPlanes) {
-                drawData.debugImg.getGraphics().drawString(
-                        "Dis: " + String.format("%.1f", plane.getDistance(collection.boundingSphereC)), 500, 10 + 20*i);
-                if(plane.getDistance(collection.boundingSphereC) < -collection.boundingSphereR){
+            ArrayList<Integer> newFrustumInfo = new ArrayList<>();
+            for (int planeId:
+            frustumInfo){
+                double dist = drawData.frustumPlanes[planeId].getDistance(collection.boundingSphereC);
+                if(drawData.debugHud) {
+                    drawData.debugImg.getGraphics().drawString(
+                            "Dis: " + String.format("%.1f", dist), 500, 10 + 20 * planeId);
+                }
+                /* if the distance is less than (more negative than) the negative bounding sphere radius, we know that
+                the bounding sphere lies completely outside the plane, so we don't need to draw it.
+                 */
+                if(dist < -collection.boundingSphereR){
                     draw = false;
-                    break;}
-                i += 1;
+                    break;
+                }
+                /* if the distance is less than the _positive_ radius of the sphere, then we know that the sphere
+                 intersects that frustum plane. Therefore, it's worth checking that frustum plane further collections
+                 down the line.
+                 However, if  the distance is greater than the positive radius, then we know that the sphere does not
+                 intersect that frustum plane at all, which means it is pointless to check it.
+                */
+                else if(dist < collection.boundingSphereR) {
+                    newFrustumInfo.add(planeId);
+                }
+                // else if not technically required because of break but it makes me happy
+
             }
-            collection.draw(drawData);
+            if(draw){collection.draw(drawData, newFrustumInfo);}
         }
     }
     public void addCollection(){
@@ -82,60 +142,22 @@
     }
     // making sure that all the point indices make sense might be a nightmare but ehh
     public void addObject(Object3d object){
-        PointComp[] newpoints = object.points;
+        PointComp[] newPoints = object.points;
         objects.add(object);
         // add the new object
 
         // merge lists
-        for (PointComp newpoint : newpoints) {
+        for (PointComp newPoint : newPoints) {
             boolean found = false;
             // find out if any of the new points already exist in the list
             for (PointComp point : points) {
-                if (newpoint == point) {
+                if (newPoint == point) {
                     found = true;
                     break;
                 }
             }
             if (!found) {
-                points.add(newpoint);
-            }
-        }
-    }
-    public void initialise(){
-        // init bounding sphere
-        double distance = 0;
-        double newDis;
-        Point3D pointA = points.get(0).point;
-        Point3D pointB = points.get(0).point;
-        // todo - maybe use some vector classes?
-        for (int i = 1; i < points.size(); i+=1) {
-            newDis = Math.pow(points.get(0).point.x - points.get(i).point.x, 2) +
-                    Math.pow(points.get(0).point.y - points.get(i).point.y, 2) +
-                    Math.pow(points.get(0).point.z - points.get(i).point.z, 2);
-            if (newDis >= distance){
-                pointA = points.get(i).point;
-                distance = newDis;}}
-        for (PointComp point : points) {
-            newDis = Math.pow(pointA.x - point.point.x, 2) +
-                    Math.pow(pointA.y - point.point.y, 2) +
-                    Math.pow(pointA.z - point.point.z, 2);
-            if (newDis >= distance){
-                pointB = point.point;
-                distance = newDis;}}
-        boundingSphereC = new Point3D(
-                (pointA.x + pointB.x) / 2,
-                (pointA.y + pointB.y)/2,
-                (pointA.z + pointB.z)/2);
-        boundingSphereR = Math.sqrt(distance)/2;
-        boolean valid = false;
-        for (PointComp point:
-                points) {
-            distance = Math.sqrt(
-                    Math.pow(boundingSphereC.x - point.point.x, 2)+
-                            Math.pow(boundingSphereC.y - point.point.y, 2)+
-                            Math.pow(boundingSphereC.z - point.point.z, 2));
-            if(distance > boundingSphereR){
-                boundingSphereR = distance;
+                points.add(newPoint);
             }
         }
     }
diff --git a/src/main/java/uk/org/floop/epq3d/Player.java b/src/main/java/uk/org/floop/epq3d/Player.java
index eeac9ce..16d0be2 100644
--- a/src/main/java/uk/org/floop/epq3d/Player.java
+++ b/src/main/java/uk/org/floop/epq3d/Player.java
@@ -7,20 +7,32 @@
     //public Plane[] frustumPlanes = new Plane[6];
     // image that represents the player's position on the board
     // current position of the player on the board grid
+    // working variables
     private final Matrix rotMatrix = new Matrix(3, 3);
     public final Matrix camMatrix = new Matrix(4, 3);
     private final Matrix invRotMatrix = new Matrix(3, 3);
     private final Matrix invCamMatrix = new Matrix(4, 3);
     protected Point3D FPWorldPos;
     protected PointComp[] ScreenCornerPosS = new PointComp[4];
+    public Vector3D viewVector = new Vector3D(0,0,0);
+
+    public boolean isSprinting = false;
+
+    // movement variables
     private final Point3D position = new Point3D(0,0,0);
     private final Point3D rotation = new Point3D(0,Math.PI / 2, 0);
     private final Vector3D direction = new Vector3D(0,0,0);
-    public Vector3D viewVector = new Vector3D(0,0,0);
+
+    // button variables
+    public int[] keysPressed = new int[6];
+    // order: fwd, bkwd, left, right, up, down
+    // constants
     public double mouseSensitivity = 0.005; // radians per pixel
     // called when the player is initialised
     public Player(drawData drawData) {
-        drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV)/2d);
+        //drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV)/2d);
+        drawData.FPDis = 1/Math.tan(Math.toRadians(drawData.FOV/2d));
+
         for(int i = 0; i < 6; i+=1){
             drawData.frustumPlanes[i] = new Plane();
         }
@@ -29,41 +41,82 @@
         double yVal = (double)drawData.scrY / (double)drawData.scrX;
         // flip x and y because reasons
         // thank you past me for this incredible description
-        ScreenCornerPosS[0] = new PointComp(-yVal,-1,  0);
-        ScreenCornerPosS[1] = new PointComp(yVal,-1,  0);
-        ScreenCornerPosS[2] = new PointComp(-yVal,1,  0);
-        ScreenCornerPosS[3] = new PointComp(yVal,1,  0);
+        ScreenCornerPosS[0] = new PointComp(-yVal,-1, drawData.FPDis);
+        ScreenCornerPosS[1] = new PointComp(yVal,-1, drawData.FPDis);
+        ScreenCornerPosS[2] = new PointComp(-yVal,1, drawData.FPDis);
+        ScreenCornerPosS[3] = new PointComp(yVal,1, drawData.FPDis);
     }
 
     public void keyPressed(int key) {
-        if (key == KeyEvent.VK_W && direction.x < 1) {
-            direction.x += 1;
-        } else if (key == KeyEvent.VK_S && -direction.x < 1) {
-            direction.x -= 1;
-        } else if (key == KeyEvent.VK_A && direction.y < 1) {
-            direction.y += 1;
-        } else if (key == KeyEvent.VK_D && -direction.y < 1) {
-            direction.y -= 1;
-        } else if (key == KeyEvent.VK_SPACE && direction.z < 1) {
-            direction.z += 1;
-        } else if (key == KeyEvent.VK_SHIFT && -direction.z < 1) {
-            direction.z -= 1;
+        // handling direction
+        switch(key){
+            case KeyEvent.VK_W:
+                keysPressed[0] = 1;
+                break;
+            case KeyEvent.VK_S:
+                keysPressed[1] = 1;
+                break;
+            case KeyEvent.VK_A:
+                keysPressed[2] = 1;
+                break;
+            case KeyEvent.VK_D:
+                keysPressed[3] = 1;
+                break;
+            case KeyEvent.VK_SPACE:
+                keysPressed[4] = 1;
+                break;
+            case KeyEvent.VK_SHIFT:
+                keysPressed[5] = 1;
+                break;
         }
+//        if (key == KeyEvent.VK_W && direction.x < 1) {
+//            direction.x += 1;
+//        } else if (key == KeyEvent.VK_S && -direction.x < 1) {
+//            direction.x -= 1;
+//        } else if (key == KeyEvent.VK_A && direction.y < 1) {
+//            direction.y += 1;
+//        } else if (key == KeyEvent.VK_D && -direction.y < 1) {
+//            direction.y -= 1;
+//        } else if (key == KeyEvent.VK_SPACE && direction.z < 1) {
+//            direction.z += 1;
+//        } else if (key == KeyEvent.VK_SHIFT && -direction.z < 1) {
+//            direction.z -= 1;
+//        }
     }
     public void keyReleased(int key){
-        if (key == KeyEvent.VK_W && direction.x > -1) {
-            direction.x -= 1;
-        } else if (key == KeyEvent.VK_S && -direction.x > -1) {
-            direction.x -= -1;
-        } else if (key == KeyEvent.VK_A && direction.y > -1) {
-            direction.y -= 1;
-        } else if (key == KeyEvent.VK_D && -direction.y > -1) {
-            direction.y -= -1;
-        } else if (key == KeyEvent.VK_SPACE && direction.z > -1) {
-            direction.z -= 1;
-        } else if (key == KeyEvent.VK_SHIFT && -direction.z > -1) {
-            direction.z -= -1;
+        switch(key){
+            case KeyEvent.VK_W:
+                keysPressed[0] = 0;
+                break;
+            case KeyEvent.VK_S:
+                keysPressed[1] = 0;
+                break;
+            case KeyEvent.VK_A:
+                keysPressed[2] = 0;
+                break;
+            case KeyEvent.VK_D:
+                keysPressed[3] = 0;
+                break;
+            case KeyEvent.VK_SPACE:
+                keysPressed[4] = 0;
+                break;
+            case KeyEvent.VK_SHIFT:
+                keysPressed[5] = 0;
+                break;
         }
+//        if (key == KeyEvent.VK_W && direction.x > -1) {
+//            direction.x -= 1;
+//        } else if (key == KeyEvent.VK_S && -direction.x > -1) {
+//            direction.x -= -1;
+//        } else if (key == KeyEvent.VK_A && direction.y > -1) {
+//            direction.y -= 1;
+//        } else if (key == KeyEvent.VK_D && -direction.y > -1) {
+//            direction.y -= -1;
+//        } else if (key == KeyEvent.VK_SPACE && direction.z > -1) {
+//            direction.z -= 1;
+//        } else if (key == KeyEvent.VK_SHIFT && -direction.z > -1) {
+//            direction.z -= -1;
+//        }
     }
     public void tick(drawData drawData) {
         // gets called once every tick, before the repainting process happens.
@@ -82,6 +135,14 @@
         } else if(rotation.y > Math.PI){
             rotation.y = Math.PI;
         }
+        // calculate the speed multiplier based on playerSpeed and the last frame time.
+        double speedMultiplier = drawData.playerSpeed * drawData.lastFrameTime / 1000;
+        // calculate the direction based on the current keys pressed
+        direction.x = keysPressed[0] - keysPressed[1];
+        direction.y = keysPressed[2] - keysPressed[3];
+        direction.z = keysPressed[4] - keysPressed[5];
+
+
         // define rotation and translation matrices
         Matrix zMat = new Matrix(3, 3);
         zMat.setItems(new double[][]{
@@ -126,9 +187,9 @@
             izMat.multiplyVec3to(direction, dirVec);
             double dirLen = dirVec.getLength();
             if (dirLen != 0) {
-                dirVec.x = dirVec.x / (dirLen * 40);
-                dirVec.y = dirVec.y / (dirLen * 40);
-                dirVec.z = dirVec.z / (dirLen * 40);
+                dirVec.x = dirVec.x * speedMultiplier / (dirLen);
+                dirVec.y = dirVec.y * speedMultiplier / (dirLen);
+                dirVec.z = dirVec.z * speedMultiplier / (dirLen);
             }
             position.translate(dirVec);
 
@@ -162,9 +223,9 @@
 
         // calculate camera focal point and edges in world coordinates
         FPWorldPos = new Point3D(
-                position.x - viewVector.x*drawData.FPDis,
-                position.y - viewVector.y*drawData.FPDis,
-                position.z - viewVector.z* drawData.FPDis);
+                position.x,
+                position.y,
+                position.z);
         for (PointComp point:
              ScreenCornerPosS) {
             point.invalidate();
@@ -172,8 +233,9 @@
         }
         // find frustum planes
         // near plane
-        drawData.frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(),
-                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
+//        drawData.frustumPlanes[0].initFrom3Points(ScreenCornerPosS[0].getRotatedPoint(),
+//                ScreenCornerPosS[1].getRotatedPoint(), ScreenCornerPosS[2].getRotatedPoint());
+        drawData.frustumPlanes[0].initFromPointAndVector(position, viewVector);
         // far plane
         int farPlaneDis = 1000;
         drawData.frustumPlanes[1].initFromPointAndVector(
@@ -196,11 +258,7 @@
         // bottom plane
         drawData.frustumPlanes[5].initFrom3Points(FPWorldPos,
                 ScreenCornerPosS[2].getRotatedPoint(), ScreenCornerPosS[0].getRotatedPoint());
-
-        // update drawData
     }
-
-    // returnValue Functions
     public Point3D getPos() {
         return position;
     }
diff --git a/src/main/java/uk/org/floop/epq3d/Point3D.java b/src/main/java/uk/org/floop/epq3d/Point3D.java
index 3d1b2cc..d8fe766 100644
--- a/src/main/java/uk/org/floop/epq3d/Point3D.java
+++ b/src/main/java/uk/org/floop/epq3d/Point3D.java
@@ -46,16 +46,34 @@
         );
     }
     public Point2D project(drawData drawData) {
-        return new Point2D(
-                drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)),
-                (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX)))
-        );
+        Point2D point = new Point2D(0,0);
+//                draw Data.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1)),
+//                (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX)))
+        point.x = drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1));
+        point.y = (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX)));
+        /* link:
+        https://en.wikipedia.org/wiki/3D_projection
+        Mathematical formula
+         */
+//        point.x = (int)((point.y * drawData.scrX) / (point.z * 1)*drawData.FPDis);
+//        point.y = (int)((point.x * drawData.scrX) / (point.z * 1)*drawData.FPDis);
+
+        if(Math.abs(point.x) > 5000 || Math.abs(point.y) > 5000){
+            int debug = 12;
+        }
+        return point;
     }
-    public void projectOn(double fpdis, int scrX, int scrY, Point2D point){
+    public void projectOn(drawData drawData, Point2D point){
         // this is a mess of random fixes because none of my coordinates are correct. :(
         // sorry
-        point.x = scrX - (int)(scrX*0.5*((fpdis*y)/(z) + 1));
-        point.y = (int)(scrX*0.5*((fpdis*x)/(z) + ((double)scrY/(double)scrX)));
+        point.x = drawData.scrX - (int)(drawData.scrX*0.5*((drawData.FPDis*y)/(z) + 1));
+        point.y = (int)(drawData.scrX*0.5*((drawData.FPDis*x)/(z) + ((double)drawData.scrY/(double)drawData.scrX)));
+                /* link:
+        https://en.wikipedia.org/wiki/3D_projection
+        Mathematical formula
+         */
+//        point.x = (int)((-y * drawData.scrX) / (z * 2)*drawData.FPDis) + drawData.scrX / 2;
+//        point.y = (int)((x * drawData.scrX) / (z * 2)*drawData.FPDis) + drawData.scrX / 2;
     }
     public double[] get(){
         return new double[]{x, y, z};
diff --git a/src/main/java/uk/org/floop/epq3d/PointComp.java b/src/main/java/uk/org/floop/epq3d/PointComp.java
index 934be18..c82a1e3 100644
--- a/src/main/java/uk/org/floop/epq3d/PointComp.java
+++ b/src/main/java/uk/org/floop/epq3d/PointComp.java
@@ -32,9 +32,9 @@
             isRotatedPointValid = true;
         }
     }
-    public void setProjectedPoint(double FPDis, int scrX, int scrY){
+    public void setProjectedPoint(drawData drawData){
         if(!isProjectedPointValid) {
-            rotatedPoint.projectOn(FPDis, scrX, scrY, projectedPoint);
+            rotatedPoint.projectOn(drawData, projectedPoint);
             projectedPoint.z = rotatedPoint.z;
             isProjectedPointValid = true;
         }
diff --git a/src/main/java/uk/org/floop/epq3d/Screen.java b/src/main/java/uk/org/floop/epq3d/Screen.java
index 4baf557..f0b7841 100644
--- a/src/main/java/uk/org/floop/epq3d/Screen.java
+++ b/src/main/java/uk/org/floop/epq3d/Screen.java
@@ -4,11 +4,12 @@
 import java.awt.*;
 import java.awt.event.*;
 import java.awt.image.BufferedImage;
+import java.util.ArrayList;
 
 public class Screen extends JPanel implements ActionListener, KeyListener, MouseListener, MouseMotionListener {
     // __working variables__
 
-    public boolean initialised = false;
+    public boolean initialised;
     public long lastTime = 0;
         // mouse
     private boolean captured = false;
@@ -40,7 +41,6 @@
     // double ang = 0;
     // __config variables__
     // controls the delay between each tick in ms
-    private final int DELAY = 1;
 
     // suppress serialization warning
     private static final long serialVersionUID = 490905409104883233L;
@@ -69,14 +69,9 @@
 
         // initialize the game state
         player = new Player(drawData);
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-            System.out.println("aaaaaaaaaaa");
-            //throw new RuntimeException(e);
-        }
+        lastTime = System.currentTimeMillis();
         // this frameTimer will call the actionPerformed() method every DELAY ms
-        frameTimer = new Timer(DELAY, this);
+        frameTimer = new Timer(drawData.delay, this);
         frameTimer.start();
         initialised = true;
     }
@@ -106,8 +101,6 @@
         Toolkit.getDefaultToolkit().sync();
     }
     private void drawScreen(Graphics g)  {
-
-
 //        System.out.println(zBuf.getRGB(0,0));
 //        zBuf.setRGB(0,0, 100);
 //        System.out.println(zBuf.getRGB(0,0));
@@ -145,12 +138,14 @@
         try{t1.draw(img);}catch (NullPointerException ignored){}
         try{t2.draw(img);}catch (NullPointerException ignored){}
         ang += 0.02;*/
-
+        drawData.lastFrameTime = System.currentTimeMillis()- lastTime;
+        lastTime = System.currentTimeMillis();
         mainCollection.invalidate(true);
-        mainCollection.draw(drawData);
+        ArrayList<Integer> FrustumInfo = new ArrayList<>();
+        for(int i = 0; i<6; i += 1){FrustumInfo.add(i);}
+        mainCollection.draw(drawData, FrustumInfo);
         // DEBUG DRAWING
         {
-            drawData.debugImg.getGraphics().setColor(new Color(255,255,255,0));
             //debugImg.getGraphics().drawString(Math.round(1000 / (float) (System.currentTimeMillis() - lastTime)) + " fps", 10, 10);
             drawData.debugImg.getGraphics().drawString("FrameTime: " + (System.currentTimeMillis() - lastTime) + " millis", 10, 10);
             if(drawData.debugHud) {
@@ -188,19 +183,21 @@
                 }
             }
             // write debug overlay
-            g.drawImage(drawData.debugImg, 0, 0, this);
             BufferedImage bufImg = new BufferedImage(drawData.scrX, drawData.scrY, BufferedImage.TYPE_INT_ARGB);
             for(int x = 0; x < drawData.scrX; x +=1){
                 for(int y = 0; y < drawData.scrY; y += 1){
                     bufImg.setRGB(x, y, drawData.drawImg[x][y]);
                     drawData.drawImg[x][y] = 0;
                     drawData.zBuf[x][y] = 0;
-                    drawData.debugImg.setRGB(x, y, 0);
                 }
             }
             g.drawImage(bufImg, 0, 0, this);
-
-            lastTime = System.currentTimeMillis();
+            g.drawImage(drawData.debugImg, 0, 0, this);
+            for(int x = 0; x < drawData.scrX; x +=1){
+                for(int y = 0; y < drawData.scrY; y += 1){
+                    drawData.debugImg.setRGB(x, y, 0);
+                }
+            }
         }
 //        HTTPPost post = new HTTPPost();
 //        // json WRITING
diff --git a/src/main/java/uk/org/floop/epq3d/Triangle.java b/src/main/java/uk/org/floop/epq3d/Triangle.java
index b6cd6c2..3bc0e14 100644
--- a/src/main/java/uk/org/floop/epq3d/Triangle.java
+++ b/src/main/java/uk/org/floop/epq3d/Triangle.java
@@ -39,77 +39,84 @@
     public int draw(drawData drawData, double ang){
         //
         long lastMillis;
+        if (!is_initialised){
+            if(initialise(drawData)){
+                int[] point1;
+                int[] point2;
+                char currentLine = 'A';
 
-        if (!is_initialised){initialise();}
-        int[] point1;
-        int[] point2;
-        char currentLine = 'A';
+                lastMillis = System.currentTimeMillis();
 
-        lastMillis = System.currentTimeMillis();
-
-        point1 = LineLong.nextPix();
-        point2 = LineA.nextPix();
-        for(int x = min.x+1; x <= max.x; x += 1) {
-            while(x-1 == point1[0]) {
-                if(LineLong.isDrawn && // draw line pixels if needed, and on screen
-                        point1[0] > 0 && point1[1] > 0 && point1[0] < drawData.scrX && point1[1] < drawData.scrY){
-                    drawData.drawImg[point1[0]][point1[1]] = Color.HSBtoRGB(0, 1, 1);
-                }
-                try {
-                    point1 = LineLong.nextPix();
-                }
-                catch (Exception e){
-                    throw new RuntimeException("accessed too many line pixels");
-                }
-            }
-            while(x-1 == point2[0]) {
-                if (currentLine == 'A') {
-                    try{
-                        if(LineA.isDrawn &&                // draw line pixels if needed, and on screen
-                                point2[0] > 0 && point2[1] > 0 && point2[0] < drawData.scrX && point2[1] < drawData.scrY){
-                            drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1);
+                point1 = LineLong.nextPix();
+                point2 = LineA.nextPix();
+                for(int x = min.x+1; x <= max.x; x += 1) {
+                    while(x-1 == point1[0]) {
+                        if(LineLong.isDrawn && // draw line pixels if needed, and on screen
+                                point1[0] > 0 && point1[1] > 0 && point1[0] < drawData.scrX && point1[1] < drawData.scrY){
+                            drawData.drawImg[point1[0]][point1[1]] = Color.HSBtoRGB(0, 1, 1);
                         }
-                        point2 = LineA.nextPix();
+                        try {
+                            point1 = LineLong.nextPix();
+                        }
+                        catch (Exception e){
+                            throw new RuntimeException("accessed too many line pixels");
+                        }
                     }
-                    catch (RuntimeException e){
-                        currentLine = 'B';
-                        // point2 = LineB.nextPix();
+                    while(x-1 == point2[0]) {
+                        if (currentLine == 'A') {
+                            try{
+                                if(LineA.isDrawn &&                // draw line pixels if needed, and on screen
+                                        point2[0] > 0 && point2[1] > 0 && point2[0] < drawData.scrX && point2[1] < drawData.scrY){
+                                    drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1);
+                                }
+                                point2 = LineA.nextPix();
+                            }
+                            catch (RuntimeException e){
+                                currentLine = 'B';
+                                // point2 = LineB.nextPix();
+                            }
+                        }
+                        else {
+                            if(LineB.isDrawn &&                // draw line pixels if needed, and on screen
+                                    point2[0] > 0 && point2[1] > 0 && point2[0] < drawData.scrX && point2[1] < drawData.scrY){
+                                drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1);
+                            }
+                            point2 = LineB.nextPix();
+                        }
+                    }
+                    // cancel drawing if the x value of the triangle is out of bounds
+                    if (x >= drawData.scrX) {break;}
+                    if (x > 0) {
+                        // check which way to loop
+                        // TODO - work out a way of not needing to test for this every time
+                        if (point1[1] < point2[1]) {
+                            for (int y = Math.max(point1[1], 0); y <= Math.min(point2[1], drawData.scrY - 1); y += 1) {
+                                // function only exists so I don't have to copy paste code everywhere.
+                                drawPix(drawData, currentLine, x, y, point1[1], point2[1], ang);
+                            }
+                        } else {
+                            for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], drawData.scrY - 1); y += 1) {
+                                drawPix(drawData, currentLine, x,y, point1[1], point2[1], ang);
+                            }
+                        }
                     }
                 }
-                else {
-                    if(LineB.isDrawn &&                // draw line pixels if needed, and on screen
-                            point2[0] > 0 && point2[1] > 0 && point2[0] < drawData.scrX && point2[1] < drawData.scrY){
-                        drawData.drawImg[point2[0]][point2[1]] = Color.HSBtoRGB(0, 1, 1);
-                    }
-                    point2 = LineB.nextPix();
-                }
-            }
-            // cancel drawing if the x value of the triangle is out of bounds
-            if (x >= drawData.scrX) {break;}
-            if (x > 0) {
-                // check which way to loop
-                // TODO - work out a way of not needing to test for this every time
-                if (point1[1] < point2[1]) {
-                    for (int y = Math.max(point1[1], 0); y <= Math.min(point2[1], drawData.scrY - 1); y += 1) {
-                        // function only exists so I don't have to copy paste code everywhere.
-                        drawPix(drawData, currentLine, x, y, point1[1], point2[1], ang);
-                    }
-                } else {
-                    for (int y = Math.max(point2[1], 0); y <= Math.min(point1[1], drawData.scrY - 1); y += 1) {
-                        drawPix(drawData, currentLine, x,y, point1[1], point2[1], ang);
+                lastMillis = (System.currentTimeMillis() - lastMillis);
+                return (int)lastMillis;
+
                     }
                 }
-            }
-        }
-        lastMillis = (System.currentTimeMillis() - lastMillis);
-        return (int)lastMillis;
+        return 0;
     }
-    public void initialise(){
+    public boolean initialise(drawData drawData){
         if (point1 == null || point2 == null || point3 == null){
             throw new NullPointerException();
         }
         min = new Point2D(Math.min(point1.x, Math.min(point2.x, point3.x)), Math.min(point1.y, Math.min(point2.y, point3.y)));
         max = new Point2D(Math.max(point1.x, Math.max(point2.x, point3.x)), Math.max(point1.y, Math.max(point2.y, point3.y)));
+        if(max.x < 0 || min.x > drawData.scrX || max.y < 0 || min.y > drawData.scrY){
+            return false;
+        }
         // woo horrible IFs mess.
         // we need to figure out which points touch the edges in order to find which line is the 'full length' edge,
         // and then assign line A and line B in order
@@ -148,6 +155,7 @@
         }
         // assign points to lines
         is_initialised = true;
+        return true;
     }
     private void drawPix(drawData drawData, char currentLine, int x, int y, int y1, int y2, double ang){
         // find Z coordinate of pixel
diff --git a/src/main/java/uk/org/floop/epq3d/drawData.java b/src/main/java/uk/org/floop/epq3d/drawData.java
index 71121d5..ea51778 100644
--- a/src/main/java/uk/org/floop/epq3d/drawData.java
+++ b/src/main/java/uk/org/floop/epq3d/drawData.java
@@ -5,6 +5,7 @@
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 
+import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.FileReader;
 import java.io.IOException;
@@ -15,13 +16,20 @@
  */
 public class drawData {
     // config
-    int scrX;
-    int scrY;
-    int delay;
-    int FOV;
-    boolean debugHud;
-    boolean backfaceCullingDisabled;
 
+    public int scrX;
+    public int scrY;
+    public int delay;
+    public int FOV;
+
+    // debug
+    public boolean debugHud;
+    public boolean backfaceCullingDisabled;
+    public int frustumCullingOverridePercent;
+
+    // player
+    public double playerSpeed;
+    public double playerSprintModifier;
     // draw onto
     public int[][] drawImg;
     public int[][] zBuf;
@@ -34,6 +42,7 @@
     public double FPDis;
     public Plane[] frustumPlanes = new Plane[6];
     public Point2D mouseRel;
+
     public drawData(String filePath){
         Object obj;
         try {
@@ -46,8 +55,10 @@
         JSONObject file = (JSONObject)obj;
         scrX = (int) (long) file.get("screenWidth");
         scrY = (int) (long) file.get("screenHeight");
-        delay = (int) (1/((double) (long) file.get("maxFPS")));
+        delay = (int) ( 1000*(1/((double) (long) file.get("maxFPS"))));
         FOV = (int) (long) file.get("FOV");
+        playerSpeed = (double) file.get("playerSpeed");
+        playerSprintModifier = (double) file.get("playerSprintModifier");
 
 
         // debug
@@ -55,12 +66,14 @@
         if(toBoolean(debugFile.get("masterDebugToggle"))){
             debugHud = toBoolean(debugFile.get("drawDebugHud"));
             backfaceCullingDisabled = toBoolean(debugFile.get("overrideBackFaceCulling"));
+            frustumCullingOverridePercent = (int)(long)debugFile.get("frustumCullingOverridePercent");
         }
 
         drawImg = new int[scrX][scrY];
         zBuf = new int[scrX][scrY];
         debugImg = new BufferedImage(scrX, scrY, BufferedImage.TYPE_INT_ARGB);
         debugImg.createGraphics();
+        debugImg.getGraphics().setColor(new Color(0,0,0,255));
     }
     private Boolean toBoolean(Object obj){
         if(Objects.equals(obj.toString(), "enabled")){