diff --git a/.gitattributes b/.gitattributes
index 383d8b7dea..ccaba150a2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,4 +4,5 @@
 *.wav filter=lfs diff=lfs merge=lfs -text
 *.ogg filter=lfs diff=lfs merge=lfs -text
 *.ico filter=lfs diff=lfs merge=lfs -text
+assets/world/map/*.bin filter=lfs diff=lfs merge=lfs -text
 * !text !filter !merge !diff
diff --git a/assets/world/map/veloren_0_5_0_0.bin b/assets/world/map/veloren_0_5_0_0.bin
new file mode 100644
index 0000000000..c8cbb2cd01
Binary files /dev/null and b/assets/world/map/veloren_0_5_0_0.bin differ
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 9011d49d34..fb492f5c0a 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -112,10 +112,15 @@ impl Server {
             settings.world_seed,
             WorldOpts {
                 seed_elements: true,
-                world_file: if let Some(ref file) = settings.map_file {
-                    FileOpts::Load(file.clone())
+                world_file: if let Some(ref opts) = settings.map_file {
+                    opts.clone()
                 } else {
-                    FileOpts::Generate
+                    // Load default map from assets.
+                    //
+                    // TODO: Consider using some naming convention to automatically change this
+                    // with changing versions, or at least keep it in a constant somewhere that's
+                    // easy to change.
+                    FileOpts::LoadAsset("world.map.veloren_0_5_0_0".into())
                 },
                 ..WorldOpts::default()
             },
diff --git a/server/src/settings.rs b/server/src/settings.rs
index 92d6f7409b..0a8bffa4ec 100644
--- a/server/src/settings.rs
+++ b/server/src/settings.rs
@@ -1,6 +1,9 @@
 use portpicker::pick_unused_port;
 use serde_derive::{Deserialize, Serialize};
 use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf};
+use world::sim::FileOpts;
+
+const DEFAULT_WORLD_SEED: u32 = 5284;
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
 #[serde(default)]
@@ -15,7 +18,9 @@ pub struct ServerSettings {
     //pub login_server: whatever
     pub start_time: f64,
     pub admins: Vec<String>,
-    pub map_file: Option<PathBuf>,
+    /// When set to None, loads the default map file (if available); otherwise, uses the value of
+    /// the file options to decide how to proceed.
+    pub map_file: Option<FileOpts>,
 }
 
 impl Default for ServerSettings {
@@ -23,7 +28,7 @@ impl Default for ServerSettings {
         Self {
             gameserver_address: SocketAddr::from(([0; 4], 14004)),
             metrics_address: SocketAddr::from(([0; 4], 14005)),
-            world_seed: 5284,
+            world_seed: DEFAULT_WORLD_SEED,
             server_name: "Veloren Alpha".to_owned(),
             server_description: "This is the best Veloren server.".to_owned(),
             max_players: 100,
@@ -89,6 +94,11 @@ impl ServerSettings {
     }
 
     pub fn singleplayer() -> Self {
+        let mut load = Self::load();
+        if let None = load.map_file {
+            // If lodaing the default map file, make sure the seed is also default.
+            load.world_seed = DEFAULT_WORLD_SEED;
+        };
         Self {
             //BUG: theoretically another process can grab the port between here and server creation, however the timewindow is quite small
             gameserver_address: SocketAddr::from((
diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs
index 9ba202dfed..5f1b088dd2 100644
--- a/world/src/block/mod.rs
+++ b/world/src/block/mod.rs
@@ -70,7 +70,7 @@ impl<'a> BlockGen<'a> {
                         // (height max) [12.70, 46.33] + 3 = [15.70, 49.33]
                         / (1.0 + 3.0 * cliff_sample.chaos)
                         + 3.0;
-                    // COnservative range of radius: [8, 47]
+                    // Conservative range of radius: [8, 47]
                     let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8;
 
                     max_height.max(
@@ -191,8 +191,8 @@ impl<'a> BlockGen<'a> {
                         .gen_ctx
                         .warp_nz
                         .get(wposf.div(24.0))
-                        .mul((chaos - 0.1).max(0.0).powf(2.0))
-                        .mul(48.0);
+                        .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0))
+                        .mul(/*48.0*/ 16.0);
                     let warp = Lerp::lerp(0.0, warp, warp_factor);
 
                     let surface_height = alt + warp;
@@ -227,7 +227,7 @@ impl<'a> BlockGen<'a> {
                         false,
                         height,
                         on_cliff,
-                        basement.min(basement + warp),
+                        basement + height - alt,
                         (if water_level <= alt {
                             water_level + warp
                         } else {
@@ -430,7 +430,7 @@ impl<'a> ZCache<'a> {
                 0.0
             };
 
-        let min = self.sample.alt - (self.sample.chaos * 48.0 + cave_depth);
+        let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth);
         let min = min - 4.0;
 
         let cliff = BlockGen::get_cliff_height(
diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs
index dec47fdd52..5d400490c2 100644
--- a/world/src/column/mod.rs
+++ b/world/src/column/mod.rs
@@ -829,12 +829,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
                 /* .mul((1.0 - chaos).min(1.0).max(0.3))
                 .mul(1.0 - humidity) */
                 /* .mul(32.0) */;
-        let basement = alt_
-            + sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
 
         let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
         let alt = alt_ + riverless_alt_delta;
-        let basement = basement.min(alt);
+        let basement = alt
+            + sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
+        // let basement = basement.min(alt);
 
         let rock = (sim.gen_ctx.small_nz.get(
             Vec3::new(wposf.x, wposf.y, alt as f64)
diff --git a/world/src/lib.rs b/world/src/lib.rs
index 3a7579fd47..61325b49ec 100644
--- a/world/src/lib.rs
+++ b/world/src/lib.rs
@@ -1,6 +1,6 @@
 #![deny(unsafe_code)]
 #![allow(incomplete_features)]
-#![feature(const_generics, label_break_value)]
+#![feature(arbitrary_enum_discriminant, const_generics, label_break_value)]
 
 mod all;
 mod block;
diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs
index a235472ff2..12da4b279e 100644
--- a/world/src/sim/erosion.rs
+++ b/world/src/sim/erosion.rs
@@ -232,7 +232,7 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
     let mut rivers = vec![RiverData::default(); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
     let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
     // (Roughly) area of a chunk, times minutes per second.
-    let mins_per_sec = /*64.0*/1.0; //1.0 / 64.0;
+    let mins_per_sec = /*64.0*/1.0/*1.0 / 16.0*/;
     let chunk_area_factor = neighbor_coef.x * neighbor_coef.y / mins_per_sec;
     // NOTE: This technically makes us discontinuous, so we should be cautious about using this.
     let derivative_divisor = 1.0;
@@ -567,16 +567,21 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
 /// Precompute the maximum slope at all points.
 ///
 /// TODO: See if allocating in advance is worthwhile.
-fn get_max_slope(h: &[Alt], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync)) -> Box<[f64]> {
+fn get_max_slope(
+    h: &[Alt],
+    rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync),
+    height_scale: impl Fn(usize) -> Alt + Sync,
+) -> Box<[f64]> {
     let min_max_angle = (15.0/*6.0*//*30.0*//*6.0*//*15.0*/ / 360.0 * 2.0 * f64::consts::PI).tan();
     let max_max_angle =
         (60.0/*54.0*//*50.0*//*54.0*//*45.0*/ / 360.0 * 2.0 * f64::consts::PI).tan();
     let max_angle_range = max_max_angle - min_max_angle;
-    let height_scale = 1.0 / 4.0; // 1.0; // 1.0 / CONFIG.mountain_scale as f64;
+    // let height_scale = 1.0 / 4.0; // 1.0; // 1.0 / CONFIG.mountain_scale as f64;
     h.par_iter()
         .enumerate()
         .map(|(posi, &z)| {
             let wposf = uniform_idx_as_vec2(posi).map(|e| e as f64) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
+            let height_scale = height_scale(posi);
             let wposz = z as f64 / height_scale;// * CONFIG.mountain_scale as f64;
             // Normalized to be between 6 and and 54 degrees.
             let div_factor = /*32.0*//*16.0*//*64.0*//*256.0*//*8.0 / 4.0*//*8.0*/(2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0/* * 8.0*//*1.0*//*4.0*//* * /*1.0*/16.0/* TerrainChunkSize::RECT_SIZE.x as f64 / 8.0 */*/;
@@ -723,12 +728,16 @@ fn erode(
     epsilon_0: impl Fn(usize) -> f32 + Sync,
     alpha: impl Fn(usize) -> f32 + Sync,
     is_ocean: impl Fn(usize) -> bool + Sync,
+    // scaling factors
+    height_scale: impl Fn(f32) -> Alt + Sync,
+    k_da_scale: impl Fn(f64) -> f64,
 ) {
     let compute_stats = true;
     log::debug!("Done draining...");
-    let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale as f64;
+    // let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64;
     let min_erosion_height = 0.0; // -<Alt as Float>::infinity();
-    let mmaxh = CONFIG.mountain_scale as f64 * height_scale;
+
+    // let mmaxh = CONFIG.mountain_scale as f64 * height_scale;
     // Since maximum uplift rate is expected to be 5.010e-4 m * y^-1, and
     // 1.0 height units is 1.0 / height_scale m, whatever the
     // max uplift rate is (in units / y), we can find dt by multiplying by
@@ -749,20 +758,21 @@ fn erode(
     let dt = max_uplift as f64/* / height_scale*/ /* * CONFIG.mountain_scale as f64*/ / /*5.010e-4*/1e-3/*0.2e-3*/;
     log::debug!("dt={:?}", dt);
     // Minimum sediment thickness before we treat erosion as sediment based.
-    let sediment_thickness = 1.0e-4 * dt;
+    let sediment_thickness = |_n| /*6.25e-5*/1.0e-4 * dt;
     let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
     let chunk_area = neighbor_coef.x * neighbor_coef.y;
     let min_length = neighbor_coef.reduce_partial_min();
     let max_stable = /*max_slope * */min_length * min_length / (dt/* / 2.0*/); //1.0/* + /*max_uplift as f64 / dt*/sed / dt*/;
-                                                                               // Landslide constant: ideally scaled to 10e-2 m / y^-1
-    let l = /*200.0 * max_uplift as f64;*/(1.0e-2 /*/ CONFIG.mountain_scale as f64*/ * height_scale);
-    let l_tot = l * dt;
+
+    // Landslide constant: ideally scaled to 10e-2 m / y^-1
+    // let l = /*200.0 * max_uplift as f64;*/(1.0e-2 /*/ CONFIG.mountain_scale as f64*/ * height_scale);
+    // let l_tot = l * dt;
     // Debris flow coefficient (m / year).
     let k_df = 1.0e-4/*0.0*/;
     // Debris flow area coefficient (m^(-2q)).
     let q = 0.2;
     let q_ = /*1.0*/1.5/*1.0*/;
-    let k_da = /*5.0*//*2.5*//*5.0*//*2.5*//*5.0*/2.5 * 4.0.powf(2.0 * q);
+    let k_da = /*5.0*//*2.5*//*5.0*//*2.5*//*5.0*/2.5 * k_da_scale(q);
     let nx = WORLD_SIZE.x;
     let ny = WORLD_SIZE.y;
     let dx = TerrainChunkSize::RECT_SIZE.x as f64/* * height_scale*//* / CONFIG.mountain_scale as f64*/;
@@ -887,7 +897,8 @@ fn erode(
         || {
             rayon::join(
                 || {
-                    let max_slope = get_max_slope(h, rock_strength_nz);
+                    let max_slope =
+                        get_max_slope(h, rock_strength_nz, |posi| height_scale(n_f(posi)));
                     log::debug!("Got max slopes...");
                     max_slope
                 },
@@ -932,7 +943,8 @@ fn erode(
                     } else {
                         let old_b_i = b[posi];
                         let sed = (h_t[posi] - old_b_i) as f64;
-                        let k = if sed > sediment_thickness {
+                        let n = n_f(posi);
+                        let k = if sed > sediment_thickness(n) {
                             // Sediment
                             // k_fs
                             k_fs_mult_sed * kf(posi)
@@ -941,7 +953,7 @@ fn erode(
                             // k_fb
                             kf(posi)
                         } * dt;
-                        let n = n_f(posi) as f64;
+                        let n = n as f64;
                         let m = m_f(posi) as f64;
 
                         let mwrec_i = &mwrec[posi];
@@ -985,7 +997,8 @@ fn erode(
                             kf(posi)
                         } * dt; */
                         // let g_i = g(posi) as f64;
-                        let g_i = if sed > sediment_thickness {
+                        let n = n_f(posi);
+                        let g_i = if sed > sediment_thickness(n) {
                             g_fs_mult_sed * g(posi) as f64
                         } else {
                             g(posi) as f64
@@ -1381,7 +1394,8 @@ fn erode(
                 // let g_i = g(posi) as Compute;
                 let old_b_i = b_i;
                 let sed = (h_t_i - old_b_i) as f64;
-                let g_i = if sed > sediment_thickness {
+                let n = n_f(posi);
+                let g_i = if sed > sediment_thickness(n) {
                     g_fs_mult_sed * g(posi) as Compute
                 } else {
                     g(posi) as Compute
@@ -1463,6 +1477,7 @@ fn erode(
             } else {
                 // *is_done.at(posi) = done_val;
                 let posj = posj as usize;
+                let posj_stack = mstack_inv[posj];
                 // let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj)).map(|e| e as f64);
 
                 // Has an outgoing flow edge (posi, posj).
@@ -1478,7 +1493,7 @@ fn erode(
                 // h[i](t + dt) = (h[i](t) + δt * (uplift[i] + flux(i) * h[j](t + δt))) / (1 + flux(i) * δt).
                 // NOTE: posj has already been computed since it's downhill from us.
                 // Therefore, we can rely on wh being set to the water height for that node.
-                // let h_j = h[posj] as f64;
+                let h_j = h[posj_stack] as f64;
                 // let a_j = a[posj] as f64;
                 let wh_j = wh[posj] as f64;
                 // let old_a_i = a[posi] as f64;
@@ -1503,7 +1518,7 @@ fn erode(
                 }; */
 
                 // Only perform erosion if we are above the water level of the previous node.
-                if old_elev_i > wh_j/*h_j*//*h[posj]*/ {
+                if old_elev_i > wh_j/*h_j*//*h_j*//*h[posj]*/ {
                     let mut dtherm = 0.0f64;
                     /* {
                         // Thermal erosion (landslide)
@@ -1565,8 +1580,9 @@ fn erode(
                             assert!(posj_stack > stacki); */
                             // This can happen in cases where receiver kk is neither uphill of
                             // nor downhill from posi's direct receiver.
-                            if /*new_ht_i/*old_ht_i*/ >= (h_t[posj]/* + uplift(posj) as f64*/)*/old_elev_i /*>=*/> wh[posj] as f64/*h[posj]*//*h_j*/ {
-                                let h_j = h_stack[posj_stack] as f64;
+                            let h_j = h_stack[posj_stack] as f64;
+                            if /*new_ht_i/*old_ht_i*/ >= (h_t[posj]/* + uplift(posj) as f64*/)*/old_elev_i /*>=*/> /*wh[posj] as f64*//*h[posj]*/h_j {
+                                // let h_j = h_stack[posj_stack] as f64;
                                 let elev_j = h_j/* + a_j.max(0.0)*/;
                                 /* let flux = /*k * (p * chunk_area * area[posi] as f64).powf(m) / neighbor_distance;*/k_fs_fact[kk] + k_df_fact[kk];
                                 assert!(flux.is_normal() && flux.is_positive() || flux == 0.0);
@@ -1601,7 +1617,9 @@ fn erode(
                         let mut mask = [MaskType::new(false); 8];
                         mrec_downhill(&mrec, posi).for_each(|(kk, posj)| {
                             let posj_stack = mstack_inv[posj];
-                            if old_elev_i > wh[posj] as f64 {
+                            let h_j = h_stack[posj_stack];
+                            if /*new_h_t_i > (h_t[posj] + uplift(posj)) as f64 */old_elev_i > /*wh[posj]*/h_j as f64 {
+                                // let h_j = h_stack[posj_stack];
                                 // k_fs_weights[kk] = k_fs_fact[kk] as SimdType;
                                 // k_fs_weights[max] = k_fs_fact[kk] as SimdType;
                                 // /*k_fs_weights = */k_fs_weights.replace(max, k_fs_fact[kk]/*.extract(kk)*/ as f64);
@@ -1609,7 +1627,7 @@ fn erode(
                                 // k_df_weights[max] = k_df_fact[kk] as SimdType;
                                 // /*k_df_weights = */k_df_weights.replace(max, k_df_fact[kk]/*.extract(kk)*/ as f64);
                                 mask[kk] = MaskType::new(true);
-                                rec_heights[kk] = h_stack[posj_stack] as SimdType;
+                                rec_heights[kk] = h_j as SimdType;
                                 // rec_heights[max] = h[posj] as SimdType;
                                 // /*rec_heights = */rec_heights.replace(max, h[posj] as f64);
                                 // max += 1;
@@ -1928,7 +1946,7 @@ fn erode(
 
                     // If we dipped below the receiver's water level, set our height to the receiver's
                     // water level.
-                    if new_h_i <= wh_j/*elev_j*//*h[posj]*/ {
+                    if new_h_i <= wh_j/*h_j*//*elev_j*//*h[posj]*/ {
                         if compute_stats {
                             ncorr += 1;
                         }
@@ -1941,7 +1959,7 @@ fn erode(
                         // will be set precisely equal to the estimated height, meaning it
                         // effectively "vanishes" and just deposits sediment to its reciever.
                         // (This is probably related to criteria for block Gauss-Seidel, etc.).
-                        new_h_i = /*h[posj];*/wh_j;// - df_part.max(0.0);
+                        new_h_i = /*h[posj];*/wh_j/*h_j*/;// - df_part.max(0.0);
                         // df_part = 0.0;
                         /* let lposj = lake_sill[posj];
                         lake_sill[posi] = lposj;
@@ -2076,7 +2094,7 @@ fn erode(
                         sums += mag_slope;
                         // Just use the computed rate.
                     } */
-                    h_stack[/*(posi*/stacki] = new_h_i as Alt;
+                    h_stack[/*posi*/stacki] = new_h_i as Alt;
                     // println!("Delta: {:?} - {:?} = {:?}", new_h_i, h_p_i, new_h_i - h_p_i);
                     // a[posi] = df_part as Alt;
                     // Make sure to update the basement as well!
@@ -2327,7 +2345,8 @@ fn erode(
         let kd_factor =
             // 1.0;
             (1.0 / (max_slope / mid_slope/*.sqrt()*//*.powf(0.03125)*/).powf(/*2.0*/2.0))/*.min(kdsed)*/;
-        max_slopes[posi] = if sed > sediment_thickness && kdsed > 0.0 {
+        let n = n_f(posi);
+        max_slopes[posi] = if sed > sediment_thickness(n) && kdsed > 0.0 {
             // Sediment
             kdsed /* * kd_factor*/
         } else {
@@ -3386,6 +3405,10 @@ pub fn do_erosion(
     g: impl Fn(usize) -> f32 + Sync,
     epsilon_0: impl Fn(usize) -> f32 + Sync,
     alpha: impl Fn(usize) -> f32 + Sync,
+    // scaling factors
+    height_scale: impl Fn(f32) -> Alt + Sync,
+    k_d_scale: f64,
+    k_da_scale: impl Fn(f64) -> f64,
 ) -> (Box<[Alt]>, Box<[Alt]> /*, Box<[Alt]>*/) {
     log::debug!("Initializing erosion arrays...");
     let oldh_ = (0..WORLD_SIZE.x * WORLD_SIZE.y)
@@ -3479,7 +3502,7 @@ pub fn do_erosion(
     // Bedrock transport coefficients (diffusivity) in m^2 / year.  For now, we set them all to be equal
     // on land, but in theory we probably want to at least differentiate between soil, bedrock, and
     // sediment.
-    let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64;
+    /* let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64;
     let time_scale = 1.0; //1.0 / 4.0; // 4.0.powf(-n)
     let mmaxh = CONFIG.mountain_scale as f64 * height_scale;
     let dt = max_uplift as f64 / height_scale /* * CONFIG.mountain_scale as f64*/ / 5.010e-4;
@@ -3487,10 +3510,12 @@ pub fn do_erosion(
         2.0e-5 * dt;
     let kd_bedrock =
         /*1e-2*//*0.25e-2*/1e-2 / 1.0 * height_scale * height_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
-        /* * k_fb / 2e-5 */;
+        /* * k_fb / 2e-5 */; */
     let kdsed =
-        /*1.5e-2*//*1e-4*//*1.25e-2*//*1.5e-2 */1.5e-2 / 1.0 * height_scale * height_scale / time_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
+        /*1.5e-2*//*1e-4*//*1.25e-2*//*1.5e-2 *//*1.5e-2 / 1.0*//* * height_scale * height_scale / time_scale*//* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
+        1.5e-2 / 4.0
         /* * k_fb / 2e-5 */;
+    let kdsed = kdsed * k_d_scale;
     // let kd = |posi: usize| kd_bedrock; // if is_ocean(posi) { /*0.0*/kd_bedrock } else { kd_bedrock };
     let n = |posi: usize| n[posi];
     let m = |posi: usize| m[posi];
@@ -3499,6 +3524,8 @@ pub fn do_erosion(
     let g = |posi: usize| g[posi];
     let epsilon_0 = |posi: usize| epsilon_0[posi];
     let alpha = |posi: usize| alpha[posi];
+    let height_scale = |n| height_scale(n);
+    let k_da_scale = |q| k_da_scale(q);
     // Hillslope diffusion coefficient for sediment.
     let mut is_done = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y];
     (0..n_steps).for_each(|i| {
@@ -3529,6 +3556,8 @@ pub fn do_erosion(
             epsilon_0,
             alpha,
             |posi| is_ocean(posi),
+            height_scale,
+            k_da_scale,
         );
     });
     (h, b /*, a*/)
diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs
index af680e132d..97d5e2b3cd 100644
--- a/world/src/sim/mod.rs
+++ b/world/src/sim/mod.rs
@@ -30,6 +30,7 @@ use crate::{
     CONFIG,
 };
 use common::{
+    assets,
     terrain::{BiomeKind, TerrainChunkSize},
     vol::RectVolSize,
 };
@@ -113,6 +114,7 @@ pub(crate) struct GenCtx {
     pub uplift_nz: Worley,
 }
 
+#[derive(Clone, Debug, Deserialize, Serialize)]
 pub enum FileOpts {
     /// If set, generate the world map and do not try to save to or load from file
     /// (default).
@@ -120,8 +122,17 @@ pub enum FileOpts {
     /// If set, generate the world map and save the world file (path is created
     /// the same way screenshot paths are).
     Save,
+    /// If set, load the world file from this path in legacy format (errors if
+    /// path not found).  This option may be removed at some point, since it only applies to maps
+    /// generated before map saving was merged into master.
+    LoadLegacy(PathBuf),
     /// If set, load the world file from this path (errors if path not found).
     Load(PathBuf),
+    /// If set, look for  the world file at this asset specifier (errors if asset is not found).
+    ///
+    /// NOTE: Could stand to merge this with `Load` and construct an enum that can handle either a
+    /// PathBuf or an asset specifier, at some point.
+    LoadAsset(String),
 }
 
 impl Default for FileOpts {
@@ -145,17 +156,131 @@ impl Default for WorldOpts {
     }
 }
 
-/// A way to store certain components between runs of map generation.  Only intended for
-/// development purposes--no attempt is made to detect map invalidation or make sure that the map
-/// is synchronized with updates to noise-rs, changes to other parameters, etc.
+/// LEGACY: Remove when people stop caring.
 #[derive(Serialize, Deserialize)]
-pub struct WorldFile {
+#[repr(C)]
+pub struct WorldFileLegacy {
     /// Saved altitude height map.
     pub alt: Box<[Alt]>,
     /// Saved basement height map.
     pub basement: Box<[Alt]>,
 }
 
+/// Version of the world map intended for use in Veloren 0.5.0.
+#[derive(Serialize, Deserialize)]
+#[repr(C)]
+pub struct WorldMap_0_5_0 {
+    /// Saved altitude height map.
+    pub alt: Box<[Alt]>,
+    /// Saved basement height map.
+    pub basement: Box<[Alt]>,
+}
+
+/// Errors when converting a map to the most recent type (currently,
+/// shared by the various map types, but at some point we might switch to
+/// version-specific errors if it feels worthwhile).
+#[derive(Debug)]
+pub enum WorldFileError {
+    /// Map size was invalid, and it can't be converted to a valid one.
+    WorldSizeInvalid,
+}
+
+/// WORLD MAP.
+///
+/// A way to store certain components between runs of map generation.  Only intended for
+/// development purposes--no attempt is made to detect map invalidation or make sure that the map
+/// is synchronized with updates to noise-rs, changes to other parameters, etc.
+///
+/// The map is verisoned to enable format detection between versions of Veloren, so that when we
+/// update the map format we don't break existing maps (or at least, we will try hard not to break
+/// maps between versions; if we can't avoid it, we can at least give a reasonable error message).
+///
+/// NOTE: We rely somemwhat heavily on the implementation specifics of bincode to make sure this is
+/// backwards compatible.  When adding new variants here, Be very careful to make sure tha the old
+/// variants are preserved in the correct order and with the correct names and indices, and make
+/// sure to keep the #[repr(u32)]!
+///
+/// All non-legacy versions of world files should (ideally) fit in this format.  Since the format
+/// contains a version and is designed to be extensible backwards-compatibly, the only
+/// reason not to use this forever would be if we decided to move away from BinCode, or
+/// store data across multiple files (or something else weird I guess).
+///
+/// Update this when you add a new map version.
+#[derive(Serialize, Deserialize)]
+#[repr(u32)]
+pub enum WorldFile {
+    Veloren_0_5_0(WorldMap_0_5_0) = 0,
+}
+
+/// Data for the most recent map type.  Update this when you add a new map verson.
+pub type ModernMap = WorldMap_0_5_0;
+
+impl WorldFileLegacy {
+    #[inline]
+    /// Idea: each map type except the latest knows how to transform
+    /// into the the subsequent map version, and each map type including the
+    /// latest exposes an "into_modern()" method that converts this map type
+    /// to the modern map type.  Thus, to migrate a map from an old format to a new
+    /// format, we just need to transform the old format to the subsequent map
+    /// version, and then call .into_modern() on that--this should construct a call chain that
+    /// ultimately ends up with a modern version.
+    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
+        if self.alt.len() != self.basement.len()
+            || self.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize
+        {
+            return Err(WorldFileError::WorldSizeInvalid);
+        }
+
+        /* let f = |h| h;// / 4.0;
+        let mut map = map;
+        map.alt.par_iter_mut()
+            .zip(map.basement.par_iter_mut())
+            .for_each(|(mut h, mut b)| {
+                *h = f(*h);
+                *b = f(*b);
+            }); */
+
+        let map = WorldMap_0_5_0 {
+            alt: self.alt,
+            basement: self.basement,
+        };
+
+        map.into_modern()
+    }
+}
+
+impl WorldMap_0_5_0 {
+    #[inline]
+    pub fn into_modern(self) -> Result<ModernMap, WorldFileError> {
+        if self.alt.len() != self.basement.len()
+            || self.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize
+        {
+            return Err(WorldFileError::WorldSizeInvalid);
+        }
+
+        Ok(self)
+    }
+}
+
+impl WorldFile {
+    /// Turns map data from the latest version into a versioned WorldFile ready for serialization.
+    /// Whenever a new map is updated, just change the variant we construct here to make sure we're
+    /// using the latest map version.
+
+    pub fn new(map: ModernMap) -> Self {
+        WorldFile::Veloren_0_5_0(map)
+    }
+
+    #[inline]
+    /// Turns a WorldFile into the latest version.  Whenever a new map version is added, just add
+    /// it to this match statement.
+    pub fn into_modern(self) -> Result<ModernMap, (WorldFileError)> {
+        match self {
+            WorldFile::Veloren_0_5_0(map) => map.into_modern(),
+        }
+    }
+}
+
 pub struct WorldSim {
     pub seed: u32,
     pub(crate) chunks: Vec<SimChunk>,
@@ -291,8 +416,48 @@ impl WorldSim {
         let rock_strength_nz = ScaleBias::new(&rock_strength_nz)
             .set_scale(1.0 / rock_strength_scale); */
 
-        let height_scale = 1.0f64; // 1.0 / CONFIG.mountain_scale as f64;
-        let max_erosion_per_delta_t = /*8.0*//*32.0*//*1.0*//*32.0*//*32.0*//*16.0*//*64.0*//*32.0*/16.0/*128.0*//*1.0*//*0.2 * /*100.0*/250.0*//*128.0*//*16.0*//*128.0*//*32.0*/ * height_scale;
+        // Suppose the old world has grid spacing Δx' = Δy', new Δx = Δy.
+        // We define grid_scale such that Δx = height_scale * Δx' ⇒
+        //  grid_scale = Δx / Δx'.
+        let grid_scale = 1.0f64 / 4.0/*1.0*/;
+        // Now, suppose we want to generate a world with "similar" topography, defined in this case
+        // as having roughly equal slopes at steady state, with the simulation taking roughly as
+        // many steps to get to the point the previous world was at when it finished being
+        // simulated.
+        //
+        // Some computations with our coupled SPL/debris flow give us (for slope S constant) the following
+        // suggested scaling parameters to make this work:
+        //   k_fs_scale ≡ (K𝑓 / K𝑓') = grid_scale^(-2m) = grid_scale^(-2θn)
+        let k_fs_scale = |theta, n| grid_scale.powf(-2.0 * (theta * n) as f64);
+
+        //   k_da_scale ≡ (K_da / K_da') = grid_scale^(-2q)
+        let k_da_scale = |q| grid_scale.powf(-2.0 * q);
+        //
+        // Some other estimated parameters are harder to come by and *much* more dubious, not being accurate
+        // for the coupled equation. But for the SPL only one we roughly find, for h the height at steady
+        // state and time τ = time to steady state, with Hack's Law estimated b = 2.0 and various other
+        // simplifying assumptions, the estimate:
+        //   height_scale ≡ (h / h') = grid_scale^(n)
+        let height_scale = |n: f32| grid_scale.powf(n as f64) as Alt;
+        //   time_scale ≡ (τ / τ') = grid_scale^(n)
+        let time_scale = |n: f32| grid_scale.powf(n as f64);
+        //
+        // Based on this estimate, we have:
+        //   delta_t_scale ≡ (Δt / Δt') = time_scale
+        let delta_t_scale = |n: f32| time_scale(n);
+        //   alpha_scale ≡ (α / α') = height_scale^(-1)
+        let alpha_scale = |n: f32| height_scale(n).recip() as f32;
+        //
+        // Slightly more dubiously (need to work out the math better) we find:
+        //   k_d_scale ≡ (K_d / K_d') = grid_scale^2 / (height_scale * time_scale)
+        let k_d_scale = |n: f32| /*grid_scale.powi(2) / time_scale(n)*//*height_scale(n)*/grid_scale.powi(2) / (/*height_scale(n) * */time_scale(n))/* * (1.0 / 16.0)*/;
+        //   epsilon_0_scale ≡ (ε₀ / ε₀') = height_scale(n) / time_scale(n)
+        let epsilon_0_scale = |n| /*height_scale(n) as f32*//*1.0*/(height_scale(n) / time_scale(n)) as f32/* * 1.0 / 4.0*/;
+
+        // Approximate n for purposes of computation of parameters above over the whole grid (when
+        // a chunk isn't available).
+        let n_approx = 1.0;
+        let max_erosion_per_delta_t = /*8.0*//*32.0*//*1.0*//*32.0*//*32.0*//*16.0*//*64.0*//*32.0*/64.0/*128.0*//*1.0*//*0.2 * /*100.0*/250.0*//*128.0*//*16.0*//*128.0*//*32.0*/ * delta_t_scale(n_approx);
         let erosion_pow_low = /*0.25*//*1.5*//*2.0*//*0.5*//*4.0*//*0.25*//*1.0*//*2.0*//*1.5*//*1.5*//*0.35*//*0.43*//*0.5*//*0.45*//*0.37*/1.002;
         let erosion_pow_high = /*1.5*//*1.0*//*0.55*//*0.51*//*2.0*/1.002;
         let erosion_center = /*0.45*//*0.75*//*0.75*//*0.5*//*0.75*/0.5;
@@ -531,15 +696,56 @@ impl WorldSim {
         });
 
         // Calculate oceans.
-        let old_height =
-            |posi: usize| alt_old[posi].1 * CONFIG.mountain_scale * height_scale as f32;
+        let is_ocean = get_oceans(|posi: usize| alt_old[posi].1);
+        let is_ocean_fn = |posi: usize| is_ocean[posi];
         /* let is_ocean = (0..WORLD_SIZE.x * WORLD_SIZE.y)
         .into_par_iter()
         .map(|i| map_edge_factor(i) == 0.0)
         .collect::<Vec<_>>(); */
-        let is_ocean = get_oceans(old_height);
-        let is_ocean_fn = |posi: usize| is_ocean[posi];
+
         let turb_wposf_div = 8.0/*64.0*/;
+        let n_func = |posi| {
+            if is_ocean_fn(posi) {
+                return 1.0;
+            }
+            let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
+                .map(|e| e as f64);
+            let turb_wposf = wposf
+                .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
+                .div(turb_wposf_div);
+            let turb = Vec2::new(
+                gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
+                gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
+            ) * uplift_turb_scale
+                * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
+            // let turb = Vec2::zero();
+            let turb_wposf = wposf + turb;
+            let turb_wposi = turb_wposf
+                .map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as f64)
+                .map2(WORLD_SIZE, |e, f| (e as i32).max(f as i32 - 1).min(0));
+            let turb_posi = vec2_as_uniform_idx(turb_wposi);
+            let uheight = gen_ctx
+                .uplift_nz
+                .get(turb_wposf.into_array())
+                /* .min(0.5)
+                .max(-0.5)*/
+                .min(1.0)
+                .max(-1.0)
+                .mul(0.5)
+                .add(0.5);
+            /* if uheight > 0.8 {
+                1.5
+            } else {
+                1.0
+            } */
+            // ((1.5 - 0.6) * uheight + 0.6) as f32
+            // ((1.5 - 1.0) * uheight + 1.0) as f32
+            // ((3.5 - 1.5) * (1.0 - uheight) + 1.5) as f32
+            1.0
+        };
+        let old_height = |posi: usize| {
+            alt_old[posi].1 * CONFIG.mountain_scale * height_scale(n_func(posi)) as f32
+        };
 
         let uplift_nz_dist = gen_ctx.uplift_nz.clone().enable_range(true);
         // Recalculate altitudes without oceans.
@@ -694,58 +900,19 @@ impl WorldSim {
         let erosion_factor = |x: f64| (/*if x <= /*erosion_center*/alt_old_center_uniform/*alt_old_center*/ { erosion_pow_low.ln() } else { erosion_pow_high.ln() } * */(/*exp_inverse_cdf*//*logit*/inv_func(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform))/*0.5 + (x - 0.5).signum() * ((x - 0.5).mul(2.0).abs(
 ).powf(erosion_pow).mul(0.5))*//*.powf(0.5)*//*.powf(1.5)*//*.powf(2.0)*/;
         let rock_strength_div_factor = /*8.0*/(2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0;
-        let time_scale = 1.0; //4.0/*4.0*/;
-        let n_func = |posi| {
-            if is_ocean_fn(posi) {
-                return 1.0;
-            }
-            let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
-                .map(|e| e as f64);
-            let turb_wposf = wposf
-                .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
-                .div(turb_wposf_div);
-            let turb = Vec2::new(
-                gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
-                gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
-            ) * uplift_turb_scale
-                * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
-            // let turb = Vec2::zero();
-            let turb_wposf = wposf + turb;
-            let turb_wposi = turb_wposf
-                .map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as f64)
-                .map2(WORLD_SIZE, |e, f| (e as i32).max(f as i32 - 1).min(0));
-            let turb_posi = vec2_as_uniform_idx(turb_wposi);
-            let uheight = gen_ctx
-                .uplift_nz
-                .get(turb_wposf.into_array())
-                /* .min(0.5)
-                .max(-0.5)*/
-                .min(1.0)
-                .max(-1.0)
-                .mul(0.5)
-                .add(0.5);
-            /* if uheight > 0.8 {
-                1.5
-            } else {
-                1.0
-            } */
-            // ((1.5 - 0.6) * uheight + 0.6) as f32
-            // ((1.5 - 1.0) * uheight + 1.0) as f32
-            // ((3.5 - 1.5) * (1.0 - uheight) + 1.5) as f32
-            1.0
-        };
+        // let time_scale = 1.0; //4.0/*4.0*/;
         let theta_func = |posi| 0.4;
         let kf_func = {
             |posi| {
-                let m_i = (theta_func(posi) * n_func(posi)) as f64;
+                let kf_scale_i = k_fs_scale(theta_func(posi), n_func(posi)) as f64;
                 // let precip_mul = (0.25).powf(m);
                 if is_ocean_fn(posi) {
                     // multiplied by height_scale^(2m) to account for change in area.
-                    return 1.0e-4 * 4.0.powf(2.0 * m_i)/* / time_scale*/; // .powf(-(1.0 - 2.0 * m_i))/* * 4.0*/;
-                                                                          // return 2.0e-5;
-                                                                          // return 2.0e-6;
-                                                                          // return 2.0e-10;
-                                                                          // return 0.0;
+                    return 1.0e-4 * kf_scale_i/* / time_scale*/; // .powf(-(1.0 - 2.0 * m_i))/* * 4.0*/;
+                                                                 // return 2.0e-5;
+                                                                 // return 2.0e-6;
+                                                                 // return 2.0e-10;
+                                                                 // return 0.0;
                 }
                 let wposf = (uniform_idx_as_vec2(posi)
                     * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
@@ -784,7 +951,7 @@ impl WorldSim {
                 let oheight = /*alt_old*//*alt_base*/alt_old_no_ocean[/*(turb_posi / 64) * 64*/posi].0 as f64;
                 let oheight_2 = /*alt_old*//*alt_base*/(alt_old_no_ocean[/*(turb_posi / 64) * 64*/posi].1 as f64 / CONFIG.mountain_scale as f64);
 
-                // kf = 1.5e-4: high-high (plateau [fan sediment])
+                let kf_i = // kf = 1.5e-4: high-high (plateau [fan sediment])
                 // kf = 1e-4: high (plateau)
                 // kf = 2e-5: normal (dike [unexposed])
                 // kf = 1e-6: normal-low (dike [exposed])
@@ -808,17 +975,26 @@ impl WorldSim {
                 // ((1.0 - uheight) * (0.5 - 0.5 * /*((1.32 - uchaos as f64) / 1.32)*/oheight) * (1.5e-4 - 2.0e-6) + 2.0e-6)
                 // 2e-5
                 // multiplied by height_scale^(2m) to account for change in area.
-                2.5e-6 * 4.0.powf(/*-(1.0 - 2.0 * m_i)*/ 2.0 * m_i) /* / time_scale*//* / 4.0 * 0.25 *//* * 4.0*/
+                // 2.5e-6/* / time_scale*//* / 4.0 * 0.25 *//* * 4.0*/
+                1.0e-6
+                // 2.0e-6
                 // 2.9e-10
                 // ((1.0 - uheight) * (5e-5 - 2.9e-10) + 2.9e-10)
                 // ((1.0 - uheight) * (5e-5 - 2.9e-14) + 2.9e-14)
+                ;
+                kf_i * kf_scale_i
             }
         };
         let kd_func = {
             |posi| {
-                // let height_scale = 1.0 / 4.0.powf(-n_i);
+                let n = n_func(posi);
+                let kd_scale_i = k_d_scale(n);
                 if is_ocean_fn(posi) {
-                    return 1.0e-2 * 4.0.powf(-2.0) * time_scale;
+                    let kd_i =
+                        /*1.0e-2*/
+                        1.0e-2 / 4.0
+                    ;
+                    kd_i * kd_scale_i;
                 }
                 let wposf = (uniform_idx_as_vec2(posi)
                     * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
@@ -846,13 +1022,25 @@ impl WorldSim {
                     .max(-1.0)
                     .mul(0.5)
                     .add(0.5);
+                let uchaos = /* gen_ctx.chaos_nz.get((wposf.div(3_000.0)).into_array())
+                    .min(1.0)
+                    .max(-1.0)
+                    .mul(0.5)
+                    .add(0.5); */
+                    chaos[posi].1;
+
                 // kd = 1e-1: high (mountain, dike)
                 // kd = 1.5e-2: normal-high (plateau [fan sediment])
                 // kd = 1e-2: normal (plateau)
                 // multiplied by height_scale² to account for change in area, then divided by
                 // time_scale to account for lower dt.
-                1.0e-2 * 4.0.powf(-2.0) * time_scale // m_old^2 / y * (1 m_new / 4 m_old)^2
-                                                     // (uheight * (1.0e-1 - 1.0e-2) + 1.0e-2)
+                let kd_i = // 1.0e-2 * kd_scale_i;// m_old^2 / y * (1 m_new / 4 m_old)^2
+                    1.10e-2  / 4.0
+                    // (uheight * (1.0e-1 - 1.0e-2) + 1.0e-2)
+                    // ((1.0 - uheight) * (0.5 + 0.5 * ((1.32 - uchaos as f64) / 1.32)) * (1.0e-2 - 1.0e-3) + 1.0e-3)
+                    // (uheight * (1.0e-2 - 1.0e-3) + 1.0e-3) / 2.0
+                ;
+                kd_i * kd_scale_i
             }
         };
         let g_func = |posi| {
@@ -922,10 +1110,9 @@ impl WorldSim {
             // 1.5
         };
         let epsilon_0_func = |posi| {
-            let n_i = n_func(posi);
-            // height_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or
+            // epsilon_0_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or
             // hillslopes] equal to the ratio of the old to new area, to the power of -n_i.
-            let height_scale = 4.0.powf(-n_i);
+            let epsilon_0_scale_i = epsilon_0_scale(n_func(posi));
             if is_ocean_fn(posi) {
                 // marine: ε₀ = 2.078e-3
                 // divide by height scale, multiplied by time_scale, cancels out to 1; idea is that
@@ -952,7 +1139,11 @@ impl WorldSim {
                 //
                 //      ε₀ e^(-α' H') (Δt' * height_scale) / height_scale = ε₀' e^(-α' H') Δt'
                 //      ε₀ = ε₀'
-                return 2.078e-3 * height_scale/* * time_scale*/;
+                let epsilon_0_i =
+                    //2.078e-3
+                    2.078e-3 /  4.0
+                ;
+                return epsilon_0_i * epsilon_0_scale_i/* * time_scale*/;
                 // return 5.0;
             }
             let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
@@ -980,6 +1171,9 @@ impl WorldSim {
                 .max(-1.0)
                 .mul(0.5)
                 .add(0.5);
+            /* let n_i = n_func(posi);
+            let height_scale = height_scale(n_i);
+            let uheight = uheight / height_scale; */
             let wposf3 = Vec3::new(
                 wposf.x,
                 wposf.y,
@@ -994,9 +1188,9 @@ impl WorldSim {
                 .max(-1.0)
                 .mul(0.5)
                 .add(0.5);
-            let center = /*0.25*/0.4 / 4.0;
-            let dmin = center - /*0.15;//0.05*/0.05 / 4.0;
-            let dmax = center + /*0.05*//*0.10*/0.05 / 4.0; //0.05;
+            let center = /*0.25*/0.4;
+            let dmin = center - /*0.15;//0.05*/0.05;
+            let dmax = center + /*0.05*//*0.10*/0.05; //0.05;
             let log_odds = |x: f64| logit(x) - logit(center);
             let ustrength = logistic_cdf(
                 1.0 * logit(rock_strength.min(1.0f64 - 1e-7).max(1e-7))
@@ -1010,25 +1204,28 @@ impl WorldSim {
             // Nunnock River (fractured granite, least weathered?): ε₀ = 5.3e-5
             // The stronger the rock, the lower the production rate of exposed bedrock.
             // divide by height scale, then multiplied by time_scale, cancels out.
-            ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32 * height_scale
+            let epsilon_0_i =
+                // ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32
+                ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32 / 4.0
+            ;
             /* * time_scale*/
             // 0.0
+            ;
+            epsilon_0_i * epsilon_0_scale_i
         };
         let alpha_func = |posi| {
-            let n_i = n_func(posi);
             // height_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or
             // hillslopes] equal to the ratio of the old to new area, to the power of -n_i.
-            let height_scale = 4.0.powf(-n_i);
+            // the old height * height scale, and we take the rate as ε₀ * e^(-αH), to keep
+            // the rate of rate of change in soil production consistent we must divide H by
+            // height_scale.
+            //
+            // αH = α(H' * height_scale) = α'H'
+            // α = α' / height_scale
+            let alpha_scale_i = alpha_scale(n_func(posi));
             if is_ocean_fn(posi) {
                 // marine: α = 3.7e-2
-                // divided by height_scale; idea is that since the final height itself will be
-                // the old height * height scale, and we take the rate as ε₀ * e^(-αH), to keep
-                // the rate of rate of change in soil production consistent we must divide H by
-                // height_scale.
-                //
-                // αH = α(H' * height_scale) = α'H'
-                // α = α' / height_scale
-                return 3.7e-2 / height_scale;
+                return 3.7e-2 * alpha_scale_i;
             }
             let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
                 .map(|e| e as f64);
@@ -1055,6 +1252,9 @@ impl WorldSim {
                 .max(-1.0)
                 .mul(0.5)
                 .add(0.5);
+            /* let n_i = n_func(posi);
+            let height_scale = height_scale(n_i);
+            let uheight = uheight / height_scale; */
             let wposf3 = Vec3::new(
                 wposf.x,
                 wposf.y,
@@ -1069,9 +1269,9 @@ impl WorldSim {
                 .max(-1.0)
                 .mul(0.5)
                 .add(0.5);
-            let center = /*0.25*/0.4 / 4.0;
-            let dmin = center - /*0.15;//0.05*/0.05 / 4.0;
-            let dmax = center + /*0.05*//*0.10*/0.05 / 4.0; //0.05;
+            let center = /*0.25*/0.4;
+            let dmin = center - /*0.15;//0.05*/0.05;
+            let dmax = center + /*0.05*//*0.10*/0.05; //0.05;
             let log_odds = |x: f64| logit(x) - logit(center);
             let ustrength = logistic_cdf(
                 1.0 * logit(rock_strength.min(1.0f64 - 1e-7).max(1e-7))
@@ -1084,8 +1284,8 @@ impl WorldSim {
             // Nunnock river (fractured granite, least weathered?): α = 2e-3
             // Point Reyes: α = 1.6e-2
             // The stronger  the rock, the faster the decline in soil production.
-            // divided by height_scale.
-            (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32 / height_scale
+            let alpha_i = (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32;
+            alpha_i * alpha_scale_i
         };
         let uplift_fn = |posi| {
             if is_ocean_fn(posi) {
@@ -1304,45 +1504,85 @@ impl WorldSim {
             .collect::<Vec<_>>();
         let is_ocean_fn = |posi: usize| is_ocean[posi]; */
 
-        // Load map, if necessary.
+        // Parse out the contents of various map formats into the values we need.
         let parsed_world_file = (|| {
-            if let FileOpts::Load(ref path) = opts.world_file {
-                let file = match File::open(path) {
-                    Ok(file) => file,
-                    Err(err) => {
-                        log::warn!("Couldn't read path for maps: {:?}", err);
-                        return None;
-                    }
-                };
+            let map = match opts.world_file {
+                FileOpts::LoadLegacy(ref path) => {
+                    let file = match File::open(path) {
+                        Ok(file) => file,
+                        Err(err) => {
+                            log::warn!("Couldn't read path for maps: {:?}", err);
+                            return None;
+                        }
+                    };
 
-                let reader = BufReader::new(file);
-                let map: WorldFile = match bincode::deserialize_from(reader) {
-                    Ok(map) => map,
-                    Err(err) => {
-                        log::warn!("Couldn't parse map: {:?})", err);
-                        return None;
-                    }
-                };
+                    let reader = BufReader::new(file);
+                    let map: WorldFileLegacy = match bincode::deserialize_from(reader) {
+                        Ok(map) => map,
+                        Err(err) => {
+                            log::warn!("Couldn't parse legacy map: {:?}).  Maybe you meant to try a regular load?", err);
+                            return None;
+                        }
+                    };
 
-                if map.alt.len() != map.basement.len()
-                    || map.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize
-                {
-                    log::warn!("World size of map is invalid.");
-                    return None;
+                    map.into_modern()
                 }
+                FileOpts::Load(ref path) => {
+                    let file = match File::open(path) {
+                        Ok(file) => file,
+                        Err(err) => {
+                            log::warn!("Couldn't read path for maps: {:?}", err);
+                            return None;
+                        }
+                    };
 
-                /* let f = |h| h;// / 4.0;
-                let mut map = map;
-                map.alt.par_iter_mut()
-                    .zip(map.basement.par_iter_mut())
-                    .for_each(|(mut h, mut b)| {
-                        *h = f(*h);
-                        *b = f(*b);
-                    }); */
+                    let reader = BufReader::new(file);
+                    let map: WorldFile = match bincode::deserialize_from(reader) {
+                        Ok(map) => map,
+                        Err(err) => {
+                            log::warn!("Couldn't parse modern map: {:?}).  Maybe you meant to try a legacy load?", err);
+                            return None;
+                        }
+                    };
 
-                Some(map)
-            } else {
-                None
+                    map.into_modern()
+                }
+                FileOpts::LoadAsset(ref specifier) => {
+                    let reader = match assets::load_file(specifier, &["bin"]) {
+                        Ok(reader) => reader,
+                        Err(err) => {
+                            log::warn!(
+                                "Couldn't read asset specifier {:?} for maps: {:?}",
+                                specifier,
+                                err
+                            );
+                            return None;
+                        }
+                    };
+
+                    let map: WorldFile = match bincode::deserialize_from(reader) {
+                        Ok(map) => map,
+                        Err(err) => {
+                            log::warn!("Couldn't parse modern map: {:?}).  Maybe you meant to try a legacy load?", err);
+                            return None;
+                        }
+                    };
+
+                    map.into_modern()
+                }
+                FileOpts::Generate | FileOpts::Save => return None,
+            };
+
+            match map {
+                Ok(map) => Some(map),
+                Err(e) => {
+                    match e {
+                        WorldFileError::WorldSizeInvalid => {
+                            log::warn!("World size of map is invalid.");
+                        }
+                    }
+                    None
+                }
             }
         })();
 
@@ -1358,7 +1598,9 @@ impl WorldSim {
                 max_erosion_per_delta_t as f32,
                 n_steps,
                 &river_seed,
+                // varying conditions
                 &rock_strength_nz,
+                // initial conditions
                 |posi| alt_func(posi), // + if is_ocean_fn(posi) { 0.0 } else { 128.0 },
                 |posi| {
                     alt_func(posi)
@@ -1371,6 +1613,7 @@ impl WorldSim {
                 }, // if is_ocean_fn(posi) { old_height(posi) } else { 0.0 },
                 // |posi| 0.0,
                 is_ocean_fn,
+                // empirical constants
                 uplift_fn,
                 |posi| n_func(posi),
                 |posi| theta_func(posi),
@@ -1379,12 +1622,16 @@ impl WorldSim {
                 |posi| g_func(posi),
                 |posi| epsilon_0_func(posi),
                 |posi| alpha_func(posi),
+                // scaling factors
+                |n| height_scale(n),
+                k_d_scale(n_approx),
+                |q| k_da_scale(q),
             );
 
             // Quick "small scale" erosion cycle in order to lower extreme angles.
             do_erosion(
                 0.0,
-                (1.0 * height_scale) as f32,
+                (1.0/* * height_scale*/) as f32,
                 n_small_steps,
                 &river_seed,
                 &rock_strength_nz,
@@ -1392,7 +1639,7 @@ impl WorldSim {
                 |posi| basement[posi] as f32,
                 // |posi| /*alluvium[posi] as f32*/0.0f32,
                 is_ocean_fn,
-                |posi| uplift_fn(posi) * (1.0 * height_scale / max_erosion_per_delta_t),
+                |posi| uplift_fn(posi) * (1.0/* * height_scale*/ / max_erosion_per_delta_t),
                 |posi| n_func(posi),
                 |posi| theta_func(posi),
                 |posi| kf_func(posi),
@@ -1400,11 +1647,15 @@ impl WorldSim {
                 |posi| g_func(posi),
                 |posi| epsilon_0_func(posi),
                 |posi| alpha_func(posi),
+                |n| height_scale(n),
+                k_d_scale(n_approx),
+                |q| k_da_scale(q),
             )
         };
 
         // Save map, if necessary.
-        let map = WorldFile { alt, basement };
+        // NOTE: We wll always save a map with latest version.
+        let map = WorldFile::new(ModernMap { alt, basement });
         (|| {
             if let FileOpts::Save = opts.world_file {
                 use std::time::SystemTime;
@@ -1438,7 +1689,10 @@ impl WorldSim {
                 }
             }
         })();
-        let (alt, basement) = (map.alt, map.basement);
+
+        // Skip validation--we just performed a no-op conversion for this map, so it had better be
+        // valid!
+        let ModernMap { alt, basement } = map.into_modern().unwrap();
 
         // Additional small-scale eroson after map load, only used during testing.
         let (alt, basement /*, alluvium*/) = if n_post_load_steps == 0 {
@@ -1446,7 +1700,7 @@ impl WorldSim {
         } else {
             do_erosion(
                 0.0,
-                (1.0 * height_scale) as f32,
+                (1.0/* * height_scale*/) as f32,
                 n_post_load_steps,
                 &river_seed,
                 &rock_strength_nz,
@@ -1454,7 +1708,7 @@ impl WorldSim {
                 |posi| basement[posi] as f32,
                 // |posi| alluvium[posi] as f32,
                 is_ocean_fn,
-                |posi| uplift_fn(posi) * (1.0 * height_scale / max_erosion_per_delta_t),
+                |posi| uplift_fn(posi) * (1.0/* * height_scale*/ / max_erosion_per_delta_t),
                 |posi| n_func(posi),
                 |posi| theta_func(posi),
                 |posi| kf_func(posi),
@@ -1462,6 +1716,9 @@ impl WorldSim {
                 |posi| g_func(posi),
                 |posi| epsilon_0_func(posi),
                 |posi| alpha_func(posi),
+                |n| height_scale(n),
+                k_d_scale(n_approx),
+                |q| k_da_scale(q),
             )
         };
 
@@ -2207,10 +2464,10 @@ impl SimChunk {
             panic!("Halp!");
         } */
 
-        let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale;
-        let mut alt = CONFIG.sea_level.add(alt_pre.div(height_scale));
-        let mut basement = CONFIG.sea_level.add(basement_pre.div(height_scale));
-        let water_alt = CONFIG.sea_level.add(water_alt_pre.div(height_scale));
+        // let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale;
+        let mut alt = CONFIG.sea_level.add(alt_pre /*.div(height_scale)*/);
+        let mut basement = CONFIG.sea_level.add(basement_pre /*.div(height_scale)*/);
+        let water_alt = CONFIG.sea_level.add(water_alt_pre /*.div(height_scale)*/);
         let downhill = if downhill_pre == -2 {
             None
         } else if downhill_pre < 0 {