code wiki / (root) / nx_procgen_water_test.nx

nx_procgen_water_test.nx source

↩ module page · 198 lines · 7988 B

1// nx_procgen_water_test.nx -- KATs for P4 causal water (rivers + rills). 2// Exit 0 = all pass; first failing KAT's number is the exit code. 3// 4// KAT 1 trace physics: on a synthetic cone, a trace from the apex is 5// strictly height-decreasing and reaches the map edge. 6// KAT 2 trace stops at a local minimum (synthetic bowl) -- water collects. 7// KAT 3 carve lowers exactly the channel: centre cells down by depth, 8// a far cell untouched; mask marks the path. 9// KAT 4 determinism: v2 twice -> byte-identical heightmaps AND masks. 10// KAT 5 v2 differs from v1 (water actually happened) and the mask is 11// non-empty on every preset x 2 seeds (width: rivers always form). 12// KAT 6 water mask overrides feature kinds: with-mask WATER count > 13// no-mask WATER count; conservation holds. 14// KAT 7 LOCAL_VARIATION axis moves UP on v2 vs v1 (worst-of-3 mountain 15// seeds) -- the ADD_DETAIL claim, instrument-proven. 16// KAT 8 no grade regression: v2 grade >= v1 grade on the same 17// (seed, preset), worst-of-3 mountain (water never makes the 18// verdict worse). 19// KAT 9 caprock resists: a top-band cell on a carve path is untouched 20// (and unmasked) while a lower cell on the same path carves -- 21// the STRUCTURAL-ESCALATION rung's law, geometry-exact. 22// license_tier: ORIGINAL 23 24import "nx_syscalls.nx" 25import "nx_tier.nx" 26import "nx_procgen_preset.nx" 27import "nx_procgen_water.nx" 28import "nx_procgen_biome.nx" 29import "nx_procgen_features.nx" 30import "nx_world_quality_grader.nx" 31 32const WW: nx_int = 64 33const WH: nx_int = 64 34 35// synthetic cone: height falls with distance from centre 36func t_fill_cone(hm: *i64) -> nx_int { 37 var y: nx_int = 0 38 while y < WH { 39 var x: nx_int = 0 40 while x < WW { 41 var dx: nx_int = x - 32 42 if dx < 0 { dx = 0 - dx } 43 var dy: nx_int = y - 32 44 if dy < 0 { dy = 0 - dy } 45 var d: nx_int = dx 46 if dy > d { d = dy } 47 hm[y * WW + x] = 1000 - d * 10 48 x = x + 1 49 } 50 y = y + 1 51 } 52 return 0 53} 54 55func main() -> i64 { 56 let n: nx_int = WW * WH 57 let hm: *i64 = sys_mmap(n * 8) as *i64 58 let path: *i64 = sys_mmap(n * 8) as *i64 59 60 // ---- KAT 1: cone trace strictly decreasing, reaches edge ---- 61 t_fill_cone(hm) 62 let len1: nx_int = nx_water_trace(hm, WW, WH, 32, 32, path, n) 63 if len1 < 2 { return 1 } 64 var i: nx_int = 1 65 while i < len1 { 66 if hm[path[i]] >= hm[path[i - 1]] { return 1 } 67 i = i + 1 68 } 69 let last: nx_int = path[len1 - 1] 70 let lx: nx_int = last % WW 71 let ly: nx_int = last / WW 72 var at_edge: nx_int = 0 73 if lx == 0 { at_edge = 1 } 74 if ly == 0 { at_edge = 1 } 75 if lx == WW - 1 { at_edge = 1 } 76 if ly == WH - 1 { at_edge = 1 } 77 if at_edge != 1 { return 1 } 78 79 // ---- KAT 2: bowl trace stops at the minimum ---- 80 i = 0 81 while i < n { hm[i] = 0 - hm[i]; i = i + 1 } // invert cone -> bowl 82 // start on the centre column: the Chebyshev bowl has flat diagonals 83 // (no strict descent from (2,2)); on-axis the descent is strict. 84 let len2: nx_int = nx_water_trace(hm, WW, WH, 32, 2, path, n) 85 if len2 < 2 { return 2 } 86 let term: nx_int = path[len2 - 1] 87 if nx_water_lowest_neighbor(hm, WW, WH, term % WW, term / WW) != 0 - 1 { return 2 } 88 89 // ---- KAT 3: carve geometry + mask ---- 90 t_fill_cone(hm) 91 let mask: *i64 = sys_mmap(n * 8) as *i64 92 i = 0 93 while i < n { mask[i] = 0; i = i + 1 } 94 let p0: nx_int = 10 * WW + 10 95 let p1: nx_int = 10 * WW + 11 96 path[0] = p0 97 path[1] = p1 98 let before0: nx_int = hm[p0] 99 let far: nx_int = 40 * WW + 40 100 let before_far: nx_int = hm[far] 101 nx_water_carve(hm, WW, WH, path, 2, 100, mask) 102 // p0 carved as centre (100) AND as p1's bank (50) 103 if hm[p0] != before0 - 150 { return 3 } 104 if hm[far] != before_far { return 3 } 105 if mask[p0] != 1 { return 3 } 106 if mask[far] != 0 { return 3 } 107 108 // ---- KAT 4: v2 determinism (heightmap + mask) ---- 109 let mask2: *i64 = sys_mmap(n * 8) as *i64 110 let la: *Landscape = nx_procgen_landscape_v2(4242, NX_PROCGEN_PRESET_MOUNTAIN, WW, WH, 1024, mask) 111 let lb: *Landscape = nx_procgen_landscape_v2(4242, NX_PROCGEN_PRESET_MOUNTAIN, WW, WH, 1024, mask2) 112 i = 0 113 while i < n { 114 if la.heightmap[i] != lb.heightmap[i] { return 4 } 115 if mask[i] != mask2[i] { return 4 } 116 i = i + 1 117 } 118 119 // ---- KAT 5: width -- water happens on every preset x 2 seeds ---- 120 var preset: nx_int = 0 121 while preset < NX_PROCGEN_N_PRESETS { 122 var s: nx_int = 0 123 while s < 2 { 124 let seed: nx_int = 1000 + s * 777 + preset * 31 125 let v1: *Landscape = nx_procgen_landscape(seed, preset, WW, WH, 1024) 126 let v2: *Landscape = nx_procgen_landscape_v2(seed, preset, WW, WH, 1024, mask) 127 var n_diff: nx_int = 0 128 var n_mask: nx_int = 0 129 i = 0 130 while i < n { 131 if v1.heightmap[i] != v2.heightmap[i] { n_diff = n_diff + 1 } 132 n_mask = n_mask + mask[i] 133 i = i + 1 134 } 135 if n_diff == 0 { return 5 } 136 if n_mask == 0 { return 5 } 137 s = s + 1 138 } 139 preset = preset + 1 140 } 141 142 // ---- KAT 6: mask overrides kinds; conservation holds ---- 143 let bm: *i64 = sys_mmap(n * 8) as *i64 144 let lw: *Landscape = nx_procgen_landscape_v2(4242, NX_PROCGEN_PRESET_FOREST, WW, WH, 1024, mask) 145 let nf: nx_int = lw.n_features 146 if nf <= 0 { return 6 } 147 nx_biome_map_build(lw.heightmap, WW, WH, 4242, bm) 148 let kinds_dry: *i64 = sys_mmap(nf * 8) as *i64 149 let kinds_wet: *i64 = sys_mmap(nf * 8) as *i64 150 nx_feature_assign(lw.feature_xs, lw.feature_ys, nf, bm, WW, WH, 4242, kinds_dry) 151 nx_feature_assign_with_water(lw.feature_xs, lw.feature_ys, nf, bm, mask, WW, WH, 4242, kinds_wet) 152 let hist: *i64 = sys_mmap(NX_FEAT_N_KINDS * 8) as *i64 153 nx_feature_hist_build(kinds_dry, nf, hist) 154 let dry_water: nx_int = hist[NX_FEAT_WATER] 155 nx_feature_hist_build(kinds_wet, nf, hist) 156 let wet_water: nx_int = hist[NX_FEAT_WATER] 157 var total: nx_int = 0 158 var k: nx_int = 0 159 while k < NX_FEAT_N_KINDS { total = total + hist[k]; k = k + 1 } 160 if total != nf { return 6 } 161 if wet_water < dry_water { return 6 } 162 163 // ---- KAT 7 + 8: LOCAL_VARIATION up, grade never down (mountain x3) ---- 164 let va: *i64 = sys_mmap(8 * 12) as *i64 165 let vb: *i64 = sys_mmap(8 * 12) as *i64 166 let null_ptr: *i64 = 0 as *i64 167 var worst_gain: nx_int = 99999 168 var s7: nx_int = 0 169 while s7 < 3 { 170 let seed: nx_int = 1000 + s7 * 777 171 let w1: *Landscape = nx_procgen_landscape(seed, NX_PROCGEN_PRESET_MOUNTAIN, WW, WH, 1024) 172 let w2: *Landscape = nx_procgen_landscape_v2(seed, NX_PROCGEN_PRESET_MOUNTAIN, WW, WH, 1024, mask) 173 nx_world_quality_grade(w1.heightmap, WW, WH, 1024, null_ptr, null_ptr, 0, va) 174 nx_world_quality_grade(w2.heightmap, WW, WH, 1024, null_ptr, null_ptr, 0, vb) 175 let gain: nx_int = vb[NX_WQG_OFF_LOCAL_VAR] - va[NX_WQG_OFF_LOCAL_VAR] 176 if gain < worst_gain { worst_gain = gain } 177 if vb[NX_WQG_OFF_GRADE] < va[NX_WQG_OFF_GRADE] { return 8 } 178 s7 = s7 + 1 179 } 180 if worst_gain <= 0 { return 7 } 181 182 // ---- KAT 9: caprock geometry ---- 183 t_fill_cone(hm) // apex (32,32) = 1000 184 i = 0 185 while i < n { mask[i] = 0; i = i + 1 } 186 let apex: nx_int = 32 * WW + 32 187 let lowc: nx_int = 10 * WW + 10 // height 780 on the cone 188 path[0] = apex 189 path[1] = lowc 190 let low_before: nx_int = hm[lowc] 191 nx_water_carve_band(hm, WW, WH, path, 2, 100, 0 - 1000000000, 900, mask) 192 if hm[apex] != 1000 { return 9 } // caprock untouched 193 if mask[apex] != 0 { return 9 } 194 if hm[lowc] != low_before - 100 { return 9 } // soft rock carves 195 if mask[lowc] != 1 { return 9 } 196 197 return 0 198}