diff --git a/.gitignore b/.gitignore
index 779ebd2..84de5ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,9 @@ pom.xml.asc
 .hg/
 .clj-kondo/*
 .lsp/*
+
+*.log
+
+.calva/*
+
+*.so
diff --git a/project.clj b/project.clj
index 4eca753..60fb6c8 100644
--- a/project.clj
+++ b/project.clj
@@ -2,13 +2,45 @@
   :description "A game about avoiding climate catastrophe"
   :license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
             :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"}
-  :dependencies [[org.clojure/clojure "1.10.3"]]
-  :main ^:skip-aot climate-game.core
+  :dependencies [[jme-clj "0.1.13"]
+                 [com.jme3/jmonkeyengine3 "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-testdata "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-terrain "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-plugins "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-niftygui "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-plugins "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-lwjgl "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-lwjgl-natives "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-jogg "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-jbullet "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-desktop "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-core "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-blender "3.0.0-SNAPSHOT"]
+                 [com.jme3/jME3-effects "3.0.0.20121220-SNAPSHOT"]
+                 [com.jme3/jME3-networking "3.0.0.20121220-SNAPSHOT"]
+                 [com.jme3/j-ogg-oggd "3.0.0-SNAPSHOT"]
+                 [com.jme3/j-ogg-vorbisd "3.0.0-SNAPSHOT"]
+                 [com.jme3/eventbus "3.0.0-SNAPSHOT"]
+                 [com.jme3/jbullet "3.0.0-SNAPSHOT"]
+                 [com.jme3/jinput "3.0.0-SNAPSHOT"]
+                 [com.jme3/lwjgl "3.0.0-SNAPSHOT"]
+                 [com.jme3/nifty "3.0.0-SNAPSHOT"]
+                 [com.jme3/nifty-default-controls "3.0.0-SNAPSHOT"]
+                 [com.jme3/nifty-style-black "3.0.0-SNAPSHOT"]
+                 [com.jme3/nifty-examples "3.0.0-SNAPSHOT"]
+                 [com.jme3/noise "3.0.0-SNAPSHOT"]
+                 [com.jme3/stack-alloc "3.0.0-SNAPSHOT"]
+                 [com.jme3/vecmath "3.0.0-SNAPSHOT"]
+                 [com.jme3/xmlpull-xpp3 "3.0.0-SNAPSHOT"]
+                 [org.clojure/clojure "1.10.3"]]
+  :java-source-paths ["src/java"]
+  :main ^:skip-aot cc.journeyman.climate-game.core
   :plugins [[lein-cloverage "1.2.2"]
             [lein-codox "0.10.7-cloverage"]]
   :profiles {:uberjar {:aot :all
                        :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}}
+  :repositories {"oss-sonatype"
+                 "https://oss.sonatype.org/content/repositories/snapshots/"}
+  :source-paths      ["src/clj"]
   :target-path "target/%s"
-  :url "http://example.com/FIXME"
-  
-  )
+  :url "http://example.com/FIXME")
diff --git a/src/cc/journeyman/climate_game/core.clj b/src/cc/journeyman/climate_game/core.clj
deleted file mode 100644
index 4bdc0ce..0000000
--- a/src/cc/journeyman/climate_game/core.clj
+++ /dev/null
@@ -1,7 +0,0 @@
-(ns cc.journeyman.climate-game.core
-  (:gen-class))
-
-(defn -main
-  "I don't do a whole lot ... yet."
-  [& args]
-  (println "Hello, World!"))
diff --git a/src/clj/cc/journeyman/climate_game/core.clj b/src/clj/cc/journeyman/climate_game/core.clj
new file mode 100644
index 0000000..45ba939
--- /dev/null
+++ b/src/clj/cc/journeyman/climate_game/core.clj
@@ -0,0 +1,22 @@
+(ns cc.journeyman.climate-game.core
+  (:require [jme-clj.core :refer [add-to-root box defsimpleapp geo material set* start]])
+  (:import [aaronperkins.planeteg PlanetApp]
+           [com.jme3.math ColorRGBA Vector3f]
+           [com.jme3.app SimpleApplication]
+           [com.jme3.scene Geometry]
+           [com.jme3.material Material]
+           [com.jme3.light DirectionalLight]))
+
+;; (defn init []
+;;   (let [box  (box 1 1 1)
+;;         geom (geo "Box" box)
+;;         mat  (material "Common/MatDefs/Misc/Unshaded.j3md")]
+;;     (set* mat :color "Color" ColorRGBA/Blue)
+;;     (set* geom :material mat)
+;;     (add-to-root geom)))
+
+;; (defsimpleapp app :init init)
+
+(def app (PlanetApp.))
+
+(start app)
\ No newline at end of file
diff --git a/src/java/aaronperkins/planeteg/PlanetApp.java b/src/java/aaronperkins/planeteg/PlanetApp.java
new file mode 100644
index 0000000..e38984e
--- /dev/null
+++ b/src/java/aaronperkins/planeteg/PlanetApp.java
@@ -0,0 +1,57 @@
+package aaronperkins.planeteg;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.material.Material;
+import com.jme3.light.DirectionalLight;
+
+/**
+ * Stolen in whole cloth from https://gist.github.com/aaronperkins/1775883
+ */
+public class PlanetApp extends SimpleApplication {
+
+    Geometry planet;
+    
+    public static void main(String[] args){
+        PlanetApp app = new PlanetApp();
+        app.start();
+    }
+ 
+    @Override
+    public void simpleInitApp() {
+                
+        // Setup camera
+        this.getCamera().setLocation(new Vector3f(0,0,1000));
+        this.getFlyByCamera().setMoveSpeed(200.0f);
+        
+        // Add sun
+        DirectionalLight sun = new DirectionalLight();
+        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
+        rootNode.addLight(sun);
+        
+        // Add planet
+        planet = new Geometry("Planet");
+        
+        PlanetMeshGen planetMeshGen = new PlanetMeshGen();
+        planetMeshGen.generateHeightmap();
+        planet.setMesh(planetMeshGen.generateMesh());
+        
+        Material mat = new Material(this.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
+        mat.setBoolean("UseVertexColor", true);
+        // Uncommet for wireframe
+        //mat.getAdditionalRenderState().setWireframe(true);
+        
+        planet.setMaterial(mat);
+        
+        rootNode.attachChild(planet);
+
+    }
+    
+    @Override
+    public void simpleUpdate(float tpf) {
+        planet.rotate(0, 0.005f*tpf, 0); 
+    }
+    
+}
+
diff --git a/src/java/aaronperkins/planeteg/PlanetMeshGen.java b/src/java/aaronperkins/planeteg/PlanetMeshGen.java
new file mode 100644
index 0000000..23805e6
--- /dev/null
+++ b/src/java/aaronperkins/planeteg/PlanetMeshGen.java
@@ -0,0 +1,330 @@
+package aaronperkins.planeteg;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.math.FastMath;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Stolen in whole cloth from https://gist.github.com/aaronperkins/1775883
+ * PlanetMeshGen
+ * Generates a planet from a random heightmap.
+ * Orginal source:
+ * http://ahuynh.posterous.com/article-1-generating-a-planet-in-opengl
+ * Adapted for jmonkeyengine by:
+ * ajperkins@gmail.com
+ */
+public class PlanetMeshGen {
+    
+    // Radius of planet
+    protected float planetRadius;
+    // Width of heightmap
+    protected int heightmapWidth;
+    // Stores heightmap data
+    protected float heightmapData[];
+    
+    public PlanetMeshGen() {
+        
+    }
+    
+    public Mesh generateMesh () {
+        return generateMesh(250);
+    }
+    
+    public Mesh generateMesh (float radius) {
+        planetRadius = radius;
+        
+        Mesh mesh = new Mesh();
+        
+        int gammaSamples = heightmapWidth;
+        int thetaSamples = (heightmapWidth - 1) * 2;
+        
+        List vertexList = new ArrayList();
+        List normalList = new ArrayList();
+        List indexList = new ArrayList();
+        List colorList = new ArrayList();
+        
+        // Horizontal points
+        float gammaStep = 2 * FastMath.PI / thetaSamples;
+
+        // Vertical points
+        float thetaStep = FastMath.PI / ( gammaSamples - 1 );
+        
+        // Generate vertices
+        for( int i = 0; i < thetaSamples; i++ ) {
+            float gamma = i * gammaStep;
+            for( int j = 0; j < gammaSamples; j++ ) {
+                float theta = j * thetaStep;
+
+                Vector3f pt = new Vector3f();
+                pt.x = planetRadius * FastMath.sin( theta ) * FastMath.cos( gamma );
+                pt.y = planetRadius * FastMath.cos( theta );
+                pt.z = planetRadius * FastMath.sin( theta ) * FastMath.sin( gamma );
+
+                float height = getHeight(i,j);
+
+                vertexList.add( pt.normalize().mult(planetRadius + height) );
+
+                // Set vertex colors
+                if( height <= 1f ) {
+                    colorList.add(0.0f);
+                    colorList.add(0.4f);
+                    colorList.add(0.8f);
+                    colorList.add(1.0f); // Ocean
+                } else if( height <= 1.5f ) {
+                    colorList.add(0.83f);
+                    colorList.add(0.72f);
+                    colorList.add(0.34f);
+                    colorList.add(1.0f); // Sand
+                } else if( height <= 10f ) {
+                    colorList.add(0.2f);
+                    colorList.add(0.6f);
+                    colorList.add(0.1f);
+                    colorList.add(1.0f); // Grass
+                } else {
+                    colorList.add(0.5f);
+                    colorList.add(0.5f);
+                    colorList.add(0.5f);
+                    colorList.add(1.0f); // Mountains
+                }
+            }
+        } 
+
+        // Generate normals
+        for( int i = 0; i < thetaSamples; i++ ) {
+            for( int j = 0; j < gammaSamples; j++ ) {
+                int i1 = i * gammaSamples + j;
+                int i2 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j;
+                int i3 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j + 1;
+                int i4 = i * gammaSamples + j + 1;
+
+                if( j >= gammaSamples-1 ) {
+                        i3 = gammaSamples + j + 1;
+                        i4 = j + 1;
+                }
+
+                Vector3f v1 = vertexList.get(i1);
+                Vector3f v2 = vertexList.get(i2);
+                Vector3f v3 = vertexList.get(i3);
+                Vector3f v4 = vertexList.get(i4);
+
+                Vector3f normal;
+                Vector3f t1, t2, t3, t4;
+                Vector3f n1, n2, n3, n4;
+
+                t1 = v1.subtract( v1 );
+                t2 = v2.subtract( v3 );
+                t3 = v3.subtract( v4 );
+                t4 = v4.subtract( v1 );
+
+                n1 = t1.cross( t2 ).normalize();
+                n2 = t2.cross( t3 ).normalize();
+                n3 = t3.cross( t4 ).normalize();
+                n4 = t4.cross( t1 ).normalize();
+
+                normal = n1.add( n2 ).add( n3 ).add( n4 ).normalize();
+                normalList.add(normal);
+            }
+        }
+        
+        // Generate indices
+        for( int i = 0; i < thetaSamples; i++ ) {
+            for( int j = 0; j < gammaSamples-1; j++ ) {
+                Integer i1 = i * gammaSamples + j;
+                Integer i2 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j;
+                Integer i3 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j + 1;
+                Integer i4 = i * gammaSamples + j + 1;
+
+                indexList.add( i1 );
+                indexList.add( i2 );
+                indexList.add( i3 );
+
+                indexList.add( i1 );
+                indexList.add( i3 );
+                indexList.add( i4 );
+            }
+        }
+        
+        // Set buffers
+        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[0])));
+        mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normalList.toArray(new Vector3f[0])));
+        mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(toIntArray(indexList)));
+        mesh.setBuffer(Type.Color, 4, BufferUtils.createFloatBuffer(toFloatArray(colorList)));
+        mesh.updateBound();
+        
+        return mesh;
+    }
+    
+
+    /**
+     * Create the heightmap for the planet with default values.
+     * 
+     */
+    public void generateHeightmap( ) {
+         generateHeightmap( 750, 19580427, 30, 90, 25000, .8f, .3f );
+    }
+    
+
+    /**
+     * Create the heightmap for the planet.
+     * 
+     * @param width         The width of the heightmap. Larger values mean more complex mesh
+     * @param seed          The random seed for generating the heightmap
+     * @param numIslands    Total number of land masses
+     * @param islandRadius  How big each land mass is
+     * @param iterations    More interation equals more complex features
+     * @param displacment   How high land features get
+     * @param smoothing     Lower numbers mean smoother land features
+     */
+    public void generateHeightmap(int width, int seed, int numIslands, float islandRadius, int iterations, float displacement, float smoothing ) {
+        heightmapWidth = width;
+        heightmapData = new float[heightmapWidth * heightmapWidth];
+        
+        Random rGenerator = new Random(seed);
+        
+        for( int j = 0; j < numIslands; j++ ) {
+        
+            // Find a random spot to grow an island
+            int sx = rGenerator.nextInt(heightmapWidth);
+            int sy = rGenerator.nextInt(heightmapWidth);
+            int x = sx, y = sy;
+            for( int i = 0; i < iterations; i++ ) {
+                float d = getData( x, y );
+                // Check neighbors
+                if( getData( x-1, y ) < d ) {
+                    setData( x-1, y, getData( x-1, y ) + displacement );
+                } else if( getData( x+1, y ) < d ) {
+                    setData( x+1, y, getData( x+1, y ) + displacement );
+                } else if( this.getData( x, y-1 ) < d ) {
+                    setData( x, y-1, getData( x, y-1 ) + displacement );
+                } else if( this.getData( x, y+1 ) < d ) {
+                    setData( x, y+1, getData( x, y+1 ) + displacement );
+                } else {
+                    setData( x, y, d + displacement );
+                }
+
+                switch( rGenerator.nextInt(4) ) {
+                    case 0:
+                        y++;
+                        if( inCircle( sx, sy, x, y, islandRadius ) && 
+                            y > 0 && y < heightmapWidth ) {
+                                break;
+                        } else {
+                                y--;
+                        }
+                    case 1:
+                        y--;
+                        if( inCircle( sx, sy, x, y, islandRadius ) && 
+                            y > 0 && y < heightmapWidth ) {
+                                break;
+                        } else {
+                                y++;
+                        }
+                    case 2:
+                        x++;
+                        if( inCircle( sx, sy, x, y, islandRadius ) && 
+                            x > 0 && x < heightmapWidth ) {
+                                break;
+                        } else {
+                                x--;
+                        }
+                    case 3:
+                        x--;
+                        if( inCircle( sx, sy, x, y, islandRadius ) &&
+                            x > 0 && x < heightmapWidth ) {
+                                break;
+                        } else {
+                                x++;
+                        }
+                }
+            }
+        }
+        
+        smooth( smoothing );
+    }
+
+    protected void smooth( float k ) {
+        for( int x = 1; x < heightmapWidth; x++ ) {
+            for( int z = 0; z < heightmapWidth; z++ ) {
+                heightmapData[ x * heightmapWidth + z ] = 
+                heightmapData[ (x-1) * heightmapWidth + z ] * ( 1 - k ) + 
+                heightmapData[ x * heightmapWidth + z ] * k;
+            }
+        }
+
+        for( int x = heightmapWidth-2; x >= 0; x-- ) {
+            for( int z = 0; z < heightmapWidth; z++ ) {
+                heightmapData[ x * heightmapWidth + z ] = 
+                heightmapData[ (x+1) * heightmapWidth + z ] * ( 1 - k ) + 
+                heightmapData[ x * heightmapWidth + z ] * k;
+            }
+        }
+
+        for( int x = 0; x < heightmapWidth; x++ ) {
+            for( int z = heightmapWidth-2; z >= 0; z-- ) {
+                heightmapData[ x * heightmapWidth + z ] = 
+                heightmapData[ x * heightmapWidth + (z+1) ] * ( 1 - k ) + 
+                heightmapData[ x * heightmapWidth + z ] * k;
+            }
+        }
+
+        for( int x = 0; x < heightmapWidth; x++ ) {
+            for( int z = 1; z < heightmapWidth; z++ ) {
+                heightmapData[ x * heightmapWidth + z ] = 
+                heightmapData[ x * heightmapWidth + (z-1) ] * ( 1 - k ) + 
+                heightmapData[ x * heightmapWidth + z ] * k;
+            }
+        }		
+    }
+    
+    protected float getHeight( int i, int j ) {
+        float offset;
+        if( i >= heightmapWidth ) {
+            offset = heightmapData[ ( ( 2 * heightmapWidth - 1 ) - i ) * heightmapWidth + j ];
+        } else {
+            offset = heightmapData[ i * heightmapWidth + j ];
+        }
+
+        return offset;
+    }
+    
+    protected float getData( int x, int y ) {
+        int index = x * heightmapWidth + y;
+        if (index < heightmapData.length && index >= 0)
+            return heightmapData[ index ];
+        else
+            return 0f;
+    }
+    
+    protected void setData( int x, int y, float val ) {
+        int index = x * heightmapWidth + y;
+        if (index < heightmapData.length)
+            heightmapData[ index ] = val;
+    }
+    
+    protected boolean inCircle ( int sx, int sy, int x, int y, float r ) {
+        return ( FastMath.pow( x-sx, 2) + FastMath.pow( y-sy, 2 ) ) < FastMath.pow( r, 2 );	
+    }
+    
+    protected int[] toIntArray(List list) {
+        int[] ret = new int[list.size()];
+        int i = 0;
+        for (Integer e : list)  
+            ret[i++] = e.intValue();
+        return ret;
+    }
+    
+    protected float[] toFloatArray(List list) {
+        float[] ret = new float[list.size()];
+        int i = 0;
+        for (Float e : list)  
+            ret[i++] = e.floatValue();
+        return ret;
+    }
+    
+} // End PlanetMeshGen Class