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}