code wiki / (root) / nx_world_quality_grader.nx

nx_world_quality_grader.nx source

↩ module page · 905 lines · 36490 B

1// nx_world_quality_grader.nx -- mathematical verdict for procgen worlds. 2// 3// Per user 2026-05-16: "build an iterative loop like we got going on 4// elder ai i dont want to have to manually check if you generate good 5// maps. does minecraft or these other prc gens have something that 6// actually mathematically measures if the world is good like how god 7// says and i saw it was good as a confirmation check its not trash" 8// 9// Genesis 1:31 -- "And God saw every thing that he had made, and, 10// behold, it was very good." This primitive ships the substrate's 11// SABBATH GRADER for generated worlds: a per-tick verdict over a 12// heightmap that returns sealed-enum grade + per-axis Q14 scores + 13// refine-axis directive so the caller can iterate. 14// 15// Most procgens (Minecraft, Houdini, No Man's Sky, Spelunky) have 16// NO mathematical world-quality measurement -- generation is one-shot 17// noise with hand-tuned constants. The substrate's posture is 18// honest-perf-verdict: every output has a measurable verdict, and 19// every LOSE carries a named_improvement. This primitive surfaces 20// that posture at the WORLD level. 21// 22// FULL CAPABILITY (per feedback-maximum-capability-no-simplification 23// cardinal, 2026-05-16): 24// 25// 8 axes: 26// DIVERSITY -- (max-min) / max_possible_relief. Range-based. 27// LOCAL_VARIATION -- mean |neighbour-difference| / max_relief. 28// Banded (too jagged is also bad). 29// EXTREMES -- ratio of cells in top-10% + bottom-10%. 30// Signature features push this up. 31// FLATNESS_PENALTY -- ratio of cells at modal-bucket height. 32// HIGHER = MORE FLAT = WORSE; verdict inverts. 33// FRACTAL_DIM -- Hurst-exponent estimator via multi-scale 34// mean-abs-diff slope. Spehar 2003: most 35// aesthetically pleasing fractal dimension band 36// maps to H in [0.3, 0.7]. Peak band 0.4-0.6. 37// BIOME_COHERENCE -- fraction of cells where biome ID matches 38// expected elevation band (DESERT at low, 39// FOREST at mid, SNOW at high). Caller-supplied 40// biome_map. Skipped (verdict=MARGINAL) if NULL. 41// FEATURE_VARIETY -- normalised Shannon entropy of the feature-kind 42// histogram. An all-forest world scores low; 43// multi-kind worlds score high. Caller-supplied 44// feature_hist. Skipped (=MARGINAL) if NULL. 45// READABILITY -- fraction of cells where the 3x3 local stddev 46// sits in the "legible" band [0.05Q, 0.25Q] 47// relative to local mean. Too noisy -> illegible; 48// too smooth -> boring. 49// 50// Output buffer layout (12 i64): 51// out[0] = overall_grade (NX_GRADE_F .. NX_GRADE_S) 52// out[1] = diversity_q14 53// out[2] = local_variation_q14 54// out[3] = extremes_q14 55// out[4] = flatness_penalty_q14 (HIGHER = MORE FLAT = WORSE) 56// out[5] = fractal_dim_q14 57// out[6] = biome_coherence_q14 58// out[7] = feature_variety_q14 59// out[8] = readability_q14 60// out[9] = refine_axis (NX_WQG_REFINE_*) 61// out[10] = min_height_observed 62// out[11] = max_height_observed 63// 64// Loss audit: all ratios in Q14 fixed-point; no FP. Statistics 65// computed in two passes (min/max/sum + histogram + neighbour-diff) 66// over the heightmap. No squaring/variance to avoid i64 overflow 67// for large heightmaps. 68// 69// genealogy_id: spehar_2003_universal_aesthetic_fractals + 70// berlyne_1974_aesthetic_complexity + 71// kaplan_1987_environmental_preference + 72// shannon_1948_information_entropy + 73// nx_quality_grade_sclass_canon (substrate convention) 74// lineage_id: nx_world_quality_grader_8axis_q14_v2 75 76// nx_safety_envelope: 77// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 78// sil_target: SIL1 79// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 80// verdict: NOT_YET_EVALUATED 81 82import "nx_syscalls.nx" 83import "nx_tier.nx" 84const NX_MAGIC_23170: i64 = 23170 85const NX_MAGIC_4096: i64 = 4096 86const NX_MAGIC_8192: i64 = 8192 87const NX_MAGIC_46341: i64 = 46341 88const NX_MAGIC_12288: i64 = 12288 89const NX_MAGIC_16384: i64 = 16384 90const NX_MAGIC_25976: i64 = 25976 91const NX_MAGIC_32768: i64 = 32768 92const NX_MAGIC_38048: i64 = 38048 93const NX_MAGIC_42361: i64 = 42361 94const NX_MAGIC_46006: i64 = 46006 95const NX_MAGIC_49152: i64 = 49152 96const NX_MAGIC_51916: i64 = 51916 97const NX_MAGIC_54432: i64 = 54432 98const NX_MAGIC_56619: i64 = 56619 99const NX_MAGIC_58744: i64 = 58744 100const NX_MAGIC_60686: i64 = 60686 101const NX_MAGIC_62390: i64 = 62390 102const NX_MAGIC_63984: i64 = 63984 103const NX_MAGIC_65536: i64 = 65536 104const NX_MAGIC_9000: i64 = 9000 105const NX_MAGIC_12000: i64 = 12000 106const NX_MAGIC_8000: i64 = 8000 107const NX_MAGIC_14000: i64 = 14000 108const NX_MAGIC_6553: i64 = 6553 109 110// ===== Q14 ========================================================== 111const NX_WQG_Q: nx_int = 16384 112 113// ===== Output-buffer offsets ======================================= 114const NX_WQG_OUT_STRIDE: nx_int = 12 115const NX_WQG_OFF_GRADE: nx_int = 0 116const NX_WQG_OFF_DIVERSITY: nx_int = 1 117const NX_WQG_OFF_LOCAL_VAR: nx_int = 2 118const NX_WQG_OFF_EXTREMES: nx_int = 3 119const NX_WQG_OFF_FLATNESS_PENALTY: nx_int = 4 120const NX_WQG_OFF_FRACTAL_DIM: nx_int = 5 121const NX_WQG_OFF_BIOME_COHERENCE: nx_int = 6 122const NX_WQG_OFF_FEATURE_VARIETY: nx_int = 7 123const NX_WQG_OFF_READABILITY: nx_int = 8 124const NX_WQG_OFF_REFINE_AXIS: nx_int = 9 125const NX_WQG_OFF_MIN_HEIGHT: nx_int = 10 126const NX_WQG_OFF_MAX_HEIGHT: nx_int = 11 127 128// ===== Grade sealed enum =========================================== 129const NX_GRADE_F: nx_int = 0 130const NX_GRADE_D: nx_int = 1 131const NX_GRADE_C: nx_int = 2 132const NX_GRADE_B: nx_int = 3 133const NX_GRADE_A: nx_int = 4 134const NX_GRADE_S: nx_int = 5 135const NX_GRADE_COUNT: nx_int = 6 136 137// ===== Refine-axis sealed enum ===================================== 138const NX_WQG_REFINE_NONE: nx_int = 0 139const NX_WQG_REFINE_MORE_RELIEF: nx_int = 1 140const NX_WQG_REFINE_MORE_FEATURES: nx_int = 2 141const NX_WQG_REFINE_LESS_FLATNESS: nx_int = 3 142const NX_WQG_REFINE_ADD_DETAIL: nx_int = 4 143const NX_WQG_REFINE_SMOOTH_NOISE: nx_int = 5 144const NX_WQG_REFINE_FIX_FRACTAL: nx_int = 6 // fractal dim outside aesthetic band 145const NX_WQG_REFINE_FIX_BIOMES: nx_int = 7 // biome map doesn't match elevation 146const NX_WQG_REFINE_MORE_VARIETY: nx_int = 8 // feature kind histogram too narrow 147const NX_WQG_REFINE_IMPROVE_READ: nx_int = 9 // readability low 148const NX_WQG_REFINE_COUNT: nx_int = 10 149 150// ===== Histogram resolution ======================================== 151const NX_WQG_HIST_BUCKETS: nx_int = 16 152 153// ===== Score thresholds ============================================ 154const NX_WQG_AXIS_WIN_FLOOR: nx_int = 9830 // 0.6Q 155const NX_WQG_AXIS_MARGINAL_FLOOR: nx_int = 6553 // 0.4Q 156const NX_WQG_LOCAL_VAR_UPPER: nx_int = 13107 // 0.8Q 157const NX_WQG_FLATNESS_OK_CEIL: nx_int = 6553 // 0.4Q 158const NX_WQG_FLATNESS_BAD_CEIL: nx_int = 9830 // 0.6Q 159// Fractal-dim aesthetic band (Spehar 2003): peak score at H ~ 0.5, 160// MARGINAL outside [0.3, 0.7]. Scores are normalised so 0.5 161// -> Q (peak), and decay parabolically. WIN threshold same 0.6Q. 162 163// ===== Validity predicates ========================================== 164func nx_grade_is_valid(g: nx_int) -> nx_int { 165 if g >= NX_GRADE_F { if g <= NX_GRADE_S { return 1 } } 166 return 0 167} 168 169func nx_wqg_refine_is_valid(r: nx_int) -> nx_int { 170 if r >= NX_WQG_REFINE_NONE { if r < NX_WQG_REFINE_COUNT { return 1 } } 171 return 0 172} 173 174// ===== Verdict primitives ========================================== 175func _wqg_axis_verdict(score: nx_int, floor_win: nx_int, floor_marg: nx_int) -> nx_int { 176 if score >= floor_win { return 1 } 177 if score >= floor_marg { return 0 } 178 return 0 - 1 179} 180 181func _wqg_axis_verdict_banded( 182 score: nx_int, 183 floor_win: nx_int, 184 floor_marg: nx_int, 185 upper_marg: nx_int 186) -> nx_int { 187 if score >= floor_win { 188 if score <= upper_marg { return 1 } 189 return 0 190 } 191 if score >= floor_marg { return 0 } 192 return 0 - 1 193} 194 195// ===== Grade-letter helper (8 axes) ================================ 196// Mirrors nx_quality_grade.nx S-class rubric. Symmetric voting: 197// 8 WIN -> S 198// 7 WIN -> A (regardless of losses among the 1 non-WIN; loss caps at A) 199// 6 WIN -> B if losses <= 1, else C 200// 5 WIN -> B if losses == 0, else C 201// 4 WIN -> C if losses <= 1, else D 202// 3 WIN -> D 203// losses >= 4 -> F 204// else -> C 205func _wqg_grade_from_8( 206 v0: nx_int, v1: nx_int, v2: nx_int, v3: nx_int, 207 v4: nx_int, v5: nx_int, v6: nx_int, v7: nx_int 208) -> nx_int { 209 var wins: nx_int = 0 210 var losses: nx_int = 0 211 212 if v0 == 1 { wins = wins + 1 } 213 if v0 == 0 - 1 { losses = losses + 1 } 214 if v1 == 1 { wins = wins + 1 } 215 if v1 == 0 - 1 { losses = losses + 1 } 216 if v2 == 1 { wins = wins + 1 } 217 if v2 == 0 - 1 { losses = losses + 1 } 218 if v3 == 1 { wins = wins + 1 } 219 if v3 == 0 - 1 { losses = losses + 1 } 220 if v4 == 1 { wins = wins + 1 } 221 if v4 == 0 - 1 { losses = losses + 1 } 222 if v5 == 1 { wins = wins + 1 } 223 if v5 == 0 - 1 { losses = losses + 1 } 224 if v6 == 1 { wins = wins + 1 } 225 if v6 == 0 - 1 { losses = losses + 1 } 226 if v7 == 1 { wins = wins + 1 } 227 if v7 == 0 - 1 { losses = losses + 1 } 228 229 if losses >= 4 { return NX_GRADE_F } 230 if wins == 8 { return NX_GRADE_S } 231 if wins == 7 { return NX_GRADE_A } 232 if wins == 6 { 233 if losses <= 1 { return NX_GRADE_B } 234 return NX_GRADE_C 235 } 236 if wins == 5 { 237 if losses == 0 { return NX_GRADE_B } 238 return NX_GRADE_C 239 } 240 if wins == 4 { 241 if losses <= 1 { return NX_GRADE_C } 242 return NX_GRADE_D 243 } 244 if wins == 3 { return NX_GRADE_D } 245 return NX_GRADE_C 246} 247 248// ===== Refine-axis picker ========================================== 249// Picks the weakest axis as a refine directive. Priority: missing- 250// relief > extremes > local-var > flatness > fractal > biome > variety > readability. 251func _wqg_pick_refine_axis( 252 grade: nx_int, 253 diversity: nx_int, 254 local_var: nx_int, 255 extremes: nx_int, 256 flatness: nx_int, 257 fractal: nx_int, 258 biome: nx_int, 259 variety: nx_int, 260 readability: nx_int, 261 biome_provided: nx_int, 262 variety_provided: nx_int 263) -> nx_int { 264 if grade == NX_GRADE_S { return NX_WQG_REFINE_NONE } 265 266 if diversity < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_MORE_RELIEF } 267 if extremes < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_MORE_FEATURES } 268 if local_var < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_ADD_DETAIL } 269 if local_var > NX_WQG_LOCAL_VAR_UPPER { return NX_WQG_REFINE_SMOOTH_NOISE } 270 if flatness > NX_WQG_FLATNESS_BAD_CEIL { return NX_WQG_REFINE_LESS_FLATNESS } 271 if fractal < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_FIX_FRACTAL } 272 if biome_provided == 1 { 273 if biome < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_FIX_BIOMES } 274 } 275 if variety_provided == 1 { 276 if variety < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_MORE_VARIETY } 277 } 278 if readability < NX_WQG_AXIS_MARGINAL_FLOOR { return NX_WQG_REFINE_IMPROVE_READ } 279 280 // Marginal-but-not-failing -- weakest axis. 281 if diversity < NX_WQG_AXIS_WIN_FLOOR { return NX_WQG_REFINE_MORE_RELIEF } 282 if extremes < NX_WQG_AXIS_WIN_FLOOR { return NX_WQG_REFINE_MORE_FEATURES } 283 if fractal < NX_WQG_AXIS_WIN_FLOOR { return NX_WQG_REFINE_FIX_FRACTAL } 284 return NX_WQG_REFINE_NONE 285} 286 287// ===== Fractal-dim Hurst-exponent estimator ========================= 288// Multi-scale mean-abs-diff slope. At scale s in [1, 2, 4]: 289// D(s) = mean |h(x+s,y) - h(x,y)| 290// For fractional Brownian surface, D(s) ~ s^H where H is the Hurst 291// exponent. We estimate H = log(D(4)/D(1)) / log(4). No log table: 292// using integer ratio with explicit comparison bands. 293// 294// Spehar 2003 aesthetic peak: H ~ 0.5 (most pleasing). 295// Returns a score in [0, Q] that peaks at H = 0.5 and decays 296// parabolically on either side. H outside [0.0, 1.0] -> 0. 297// 298// Formula: score = Q * (1 - 4 * (H - 0.5)^2) clamped to [0, Q]. 299// So H = 0.5 -> Q; H = 0.25 -> Q * (1 - 0.25) = 0.75Q; 300// H = 0.0 -> 0; H = 1.0 -> 0. 301func _wqg_fractal_dim_score( 302 heightmap: *i64, width: nx_int, height: nx_int 303) -> nx_int { 304 // Need at least 5x5 to sample scale=4. 305 if width < 5 { return 0 } 306 if height < 5 { return 0 } 307 let q: nx_int = NX_WQG_Q 308 309 var sum1: nx_int = 0 310 var n1: nx_int = 0 311 var sum4: nx_int = 0 312 var n4: nx_int = 0 313 314 var y: nx_int = 0 315 while y < height { 316 var x: nx_int = 0 317 while x < width { 318 let idx: nx_int = y * width + x 319 if x + 1 < width { 320 let d1: nx_int = heightmap[idx + 1] - heightmap[idx] 321 var a1: nx_int = d1 322 if a1 < 0 { a1 = 0 - a1 } 323 sum1 = sum1 + a1 324 n1 = n1 + 1 325 } 326 if x + 4 < width { 327 let d4: nx_int = heightmap[idx + 4] - heightmap[idx] 328 var a4: nx_int = d4 329 if a4 < 0 { a4 = 0 - a4 } 330 sum4 = sum4 + a4 331 n4 = n4 + 1 332 } 333 x = x + 1 334 } 335 y = y + 1 336 } 337 if n1 == 0 { return 0 } 338 if n4 == 0 { return 0 } 339 let mean1: nx_int = sum1 / n1 340 let mean4: nx_int = sum4 / n4 341 if mean1 == 0 { return 0 } 342 // ratio = mean4 / mean1 in Q14. 343 let ratio_q14: nx_int = (mean4 * q) / mean1 344 // H = log4(ratio). Approximate via piecewise bands: 345 // ratio == 1 (Q) -> H = 0 (very rough surface) 346 // ratio == 2 (2Q) -> H = 0.5 (peak) 347 // ratio == 4 (4Q) -> H = 1.0 (smooth) 348 // ratio == sqrt(2) (1.41Q ~ 23170) -> H = 0.25 349 // ratio == sqrt(8) (2.83Q ~ 46341) -> H = 0.75 350 // 351 // Linear interp in log space via these anchor points; clamp. 352 var h_q14: nx_int = 0 353 if ratio_q14 <= q { h_q14 = 0 } 354 if ratio_q14 > q { 355 if ratio_q14 <= NX_MAGIC_23170 { 356 // H in [0, 0.25] = [0, 4096 Q14]; linear in this band. 357 h_q14 = ((ratio_q14 - q) * NX_MAGIC_4096) / (NX_MAGIC_23170 - q) 358 } 359 } 360 if ratio_q14 > NX_MAGIC_23170 { 361 if ratio_q14 <= q * 2 { 362 // H in [0.25, 0.5] = [4096, 8192] 363 h_q14 = NX_MAGIC_4096 + ((ratio_q14 - NX_MAGIC_23170) * (NX_MAGIC_8192 - NX_MAGIC_4096)) / (q * 2 - NX_MAGIC_23170) 364 } 365 } 366 if ratio_q14 > q * 2 { 367 if ratio_q14 <= NX_MAGIC_46341 { 368 // H in [0.5, 0.75] = [8192, 12288] 369 h_q14 = NX_MAGIC_8192 + ((ratio_q14 - q * 2) * (NX_MAGIC_12288 - NX_MAGIC_8192)) / (NX_MAGIC_46341 - q * 2) 370 } 371 } 372 if ratio_q14 > NX_MAGIC_46341 { 373 if ratio_q14 <= q * 4 { 374 // H in [0.75, 1.0] = [12288, 16384] 375 h_q14 = NX_MAGIC_12288 + ((ratio_q14 - NX_MAGIC_46341) * (NX_MAGIC_16384 - NX_MAGIC_12288)) / (q * 4 - NX_MAGIC_46341) 376 } 377 } 378 if ratio_q14 > q * 4 { h_q14 = q } 379 if h_q14 < 0 { h_q14 = 0 } 380 if h_q14 > q { h_q14 = q } 381 382 // score = q * (1 - 4 * (H - 0.5)^2) 383 let dh: nx_int = h_q14 - (q / 2) 384 let dh_sq: nx_int = (dh * dh) / q 385 var penalty: nx_int = 4 * dh_sq 386 if penalty > q { penalty = q } 387 return q - penalty 388} 389 390// ===== Biome-coherence score ======================================== 391// biome_map: parallel to heightmap, same dimensions. Each cell holds a 392// biome ID (0..n_biome_kinds-1). We classify each cell's elevation 393// into one of 4 bands (low/mid/hi/peak) and check the biome ID's 394// "expected band" matches. Expected bands are encoded in a lookup: 395// biome 8 (DESERT) -> LOW 396// biome 0 (TUNDRA) -> LOW 397// biome 1 (BOREAL_FOREST) -> MID 398// biome 5 (TEMPERATE_FOREST) -> MID 399// biome 11 (TROPICAL_RAINFOREST) -> MID 400// biome 4 (GRASSLAND) -> MID 401// biome 12 (SNOW) -> HIGH 402// biome 13 (ICE) -> PEAK 403// Any unknown biome -> any-band-counts-as-match. 404// 405// Score = fraction of cells whose biome's expected band matches the 406// elevation band they're in. 407func _wqg_biome_expected_band(biome: nx_int) -> nx_int { 408 // 0 = LOW, 1 = MID, 2 = HIGH, 3 = PEAK, -1 = ANY 409 if biome == 8 { return 0 } 410 if biome == 0 { return 0 } 411 if biome == 1 { return 1 } 412 if biome == 5 { return 1 } 413 if biome == 11 { return 1 } 414 if biome == 4 { return 1 } 415 if biome == 12 { return 2 } 416 if biome == 13 { return 3 } 417 return 0 - 1 418} 419 420func _wqg_biome_coherence_score( 421 heightmap: *i64, biome_map: *i64, 422 width: nx_int, height: nx_int, 423 hmin: nx_int, hmax: nx_int 424) -> nx_int { 425 let q: nx_int = NX_WQG_Q 426 let range: nx_int = hmax - hmin 427 if range <= 0 { return 0 } 428 let n: nx_int = width * height 429 let low_max: nx_int = hmin + range / 4 430 let mid_max: nx_int = hmin + (range * 2) / 4 431 let hi_max: nx_int = hmin + (range * 3) / 4 432 433 var n_match: nx_int = 0 434 var i: nx_int = 0 435 while i < n { 436 let v: nx_int = heightmap[i] 437 let b: nx_int = biome_map[i] 438 var band: nx_int = 0 439 if v > low_max { band = 1 } 440 if v > mid_max { band = 2 } 441 if v > hi_max { band = 3 } 442 let exp: nx_int = _wqg_biome_expected_band(b) 443 if exp == 0 - 1 { n_match = n_match + 1 } 444 if exp == band { n_match = n_match + 1 } 445 i = i + 1 446 } 447 // Each cell that matches contributes once; cap at q. 448 return (n_match * q) / n 449} 450 451// ===== Feature-variety (Shannon entropy of kind histogram) ========= 452// feature_hist[k] = count of features of kind k. n_kinds = histogram 453// length. We compute normalised entropy: 454// H = -sum( p_k * log2(p_k) ) where p_k = count_k / total 455// normalised = H / log2(n_kinds) in [0, 1] 456// All-one-kind -> H = 0; uniform distribution -> H = log2(n_kinds). 457// 458// Approximation in Q14: use 16-bin log2 lookup. Skip kinds with 0 459// count. Returns score in [0, Q]. 460func _wqg_log2_q14_table(p_bin: nx_int) -> nx_int { 461 // p_bin in [1, 16], returns log2(p_bin) in Q14. 462 if p_bin <= 1 { return 0 } 463 if p_bin == 2 { return NX_MAGIC_16384 } // 1.0 464 if p_bin == 3 { return NX_MAGIC_25976 } // 1.585 465 if p_bin == 4 { return NX_MAGIC_32768 } // 2.0 466 if p_bin == 5 { return NX_MAGIC_38048 } // 2.322 467 if p_bin == 6 { return NX_MAGIC_42361 } // 2.585 468 if p_bin == 7 { return NX_MAGIC_46006 } // 2.807 469 if p_bin == 8 { return NX_MAGIC_49152 } // 3.0 470 if p_bin == 9 { return NX_MAGIC_51916 } // 3.170 471 if p_bin == 10 { return NX_MAGIC_54432 } // 3.322 472 if p_bin == 11 { return NX_MAGIC_56619 } // 3.456 473 if p_bin == 12 { return NX_MAGIC_58744 } // 3.585 474 if p_bin == 13 { return NX_MAGIC_60686 } // 3.700 475 if p_bin == 14 { return NX_MAGIC_62390 } // 3.807 476 if p_bin == 15 { return NX_MAGIC_63984 } // 3.907 477 return NX_MAGIC_65536 // log2(16) = 4.0 478} 479 480func _wqg_feature_variety_score( 481 feature_hist: *i64, n_kinds: nx_int 482) -> nx_int { 483 let q: nx_int = NX_WQG_Q 484 if n_kinds <= 1 { return 0 } 485 var total: nx_int = 0 486 var i: nx_int = 0 487 while i < n_kinds { 488 total = total + feature_hist[i] 489 i = i + 1 490 } 491 if total == 0 { return 0 } 492 // Entropy in Q14: H = sum( p_k_q14 * log2(1/p_k) ). 493 // Quantise p_k to 1..16 bins for table lookup. 494 var ent_q14: nx_int = 0 495 var k: nx_int = 0 496 while k < n_kinds { 497 let c: nx_int = feature_hist[k] 498 if c > 0 { 499 // p_k = c / total. bin = round(total / c) clamped [1, 16]. 500 var bin: nx_int = total / c 501 if bin < 1 { bin = 1 } 502 if bin > 16 { bin = 16 } 503 let log_bin_q14: nx_int = _wqg_log2_q14_table(bin) 504 // contrib_q14 = p_k_q14 * log2(1/p_k) = (c/total) * log_bin 505 let contrib: nx_int = (c * log_bin_q14) / total 506 ent_q14 = ent_q14 + contrib 507 } 508 k = k + 1 509 } 510 // Normalise by log2(n_kinds). Clamp n_kinds bin lookup to 16. 511 var nb: nx_int = n_kinds 512 if nb > 16 { nb = 16 } 513 let max_ent: nx_int = _wqg_log2_q14_table(nb) 514 if max_ent <= 0 { return 0 } 515 let norm: nx_int = (ent_q14 * q) / max_ent 516 if norm > q { return q } 517 if norm < 0 { return 0 } 518 return norm 519} 520 521// ===== Readability ================================================== 522// Fraction of cells whose 3x3 local stddev (approximated as max-min 523// in the 3x3 neighborhood) is in the legible band [0.05, 0.25] of 524// max_relief. Too smooth -> boring. Too noisy -> illegible. 525// 526// We use range (max-min) over the 3x3 neighborhood as a stddev 527// proxy (avoids squaring + sqrt for Q14 budget). Stddev ~ range / 4 528// is a well-known approximation for moderate sample sizes. 529func _wqg_readability_score( 530 heightmap: *i64, width: nx_int, height: nx_int, 531 max_relief: nx_int 532) -> nx_int { 533 let q: nx_int = NX_WQG_Q 534 if width < 3 { return 0 } 535 if height < 3 { return 0 } 536 if max_relief <= 0 { return 0 } 537 let lo: nx_int = (max_relief * 819) / q // 0.05 538 let hi: nx_int = (max_relief * NX_MAGIC_4096) / q // 0.25 539 540 var n_legible: nx_int = 0 541 var n_inner: nx_int = 0 542 var y: nx_int = 1 543 while y < height - 1 { 544 var x: nx_int = 1 545 while x < width - 1 { 546 let c: nx_int = y * width + x 547 var lmin: nx_int = heightmap[c] 548 var lmax: nx_int = heightmap[c] 549 var dy: nx_int = 0 - 1 550 while dy <= 1 { 551 var dx: nx_int = 0 - 1 552 while dx <= 1 { 553 let v: nx_int = heightmap[(y + dy) * width + (x + dx)] 554 if v < lmin { lmin = v } 555 if v > lmax { lmax = v } 556 dx = dx + 1 557 } 558 dy = dy + 1 559 } 560 let rng: nx_int = lmax - lmin 561 if rng >= lo { 562 if rng <= hi { 563 n_legible = n_legible + 1 564 } 565 } 566 n_inner = n_inner + 1 567 x = x + 1 568 } 569 y = y + 1 570 } 571 if n_inner == 0 { return 0 } 572 return (n_legible * q) / n_inner 573} 574 575// ===== Main grader (8 axes) ========================================= 576// heightmap: w*h flat i64 array, row-major. 577// width, height: grid dimensions. 578// max_expected_relief: caller's expected maximum relief. 579// biome_map: parallel i64 array of biome IDs (0 = skip). 580// feature_hist: i64 array of per-kind counts (0 = skip). 581// n_feature_kinds: length of feature_hist. 582// out_verdict: 12 i64s receiving the verdict. 583func nx_world_quality_grade( 584 heightmap: *i64, 585 width: nx_int, 586 height: nx_int, 587 max_expected_relief: nx_int, 588 biome_map: *i64, 589 feature_hist: *i64, 590 n_feature_kinds: nx_int, 591 out_verdict: *i64 592) { 593 let q: nx_int = NX_WQG_Q 594 let n: nx_int = width * height 595 596 // Init output. 597 var k: nx_int = 0 598 while k < NX_WQG_OUT_STRIDE { out_verdict[k] = 0; k = k + 1 } 599 out_verdict[NX_WQG_OFF_GRADE] = NX_GRADE_F 600 out_verdict[NX_WQG_OFF_REFINE_AXIS] = NX_WQG_REFINE_MORE_RELIEF 601 602 if n <= 0 { return } 603 if max_expected_relief <= 0 { return } 604 605 // Pass 1: min / max. 606 var hmin: nx_int = heightmap[0] 607 var hmax: nx_int = heightmap[0] 608 var i: nx_int = 0 609 while i < n { 610 let v: nx_int = heightmap[i] 611 if v < hmin { hmin = v } 612 if v > hmax { hmax = v } 613 i = i + 1 614 } 615 let range: nx_int = hmax - hmin 616 out_verdict[NX_WQG_OFF_MIN_HEIGHT] = hmin 617 out_verdict[NX_WQG_OFF_MAX_HEIGHT] = hmax 618 619 // DIVERSITY. 620 var diversity: nx_int = (range * q) / max_expected_relief 621 if diversity > q { diversity = q } 622 if diversity < 0 { diversity = 0 } 623 out_verdict[NX_WQG_OFF_DIVERSITY] = diversity 624 625 // Pass 2: neighbour-diff + histogram. 626 let hist: *i64 = (sys_mmap(NX_WQG_HIST_BUCKETS * NX_SIZEOF_NX_INT)) as *i64 627 var b: nx_int = 0 628 while b < NX_WQG_HIST_BUCKETS { hist[b] = 0; b = b + 1 } 629 630 var sum_abs_diff: nx_int = 0 631 var n_diff: nx_int = 0 632 var hi: nx_int = 0 633 while hi < n { 634 if range > 0 { 635 var bb: nx_int = ((heightmap[hi] - hmin) * NX_WQG_HIST_BUCKETS) / (range + 1) 636 if bb < 0 { bb = 0 } 637 if bb >= NX_WQG_HIST_BUCKETS { bb = NX_WQG_HIST_BUCKETS - 1 } 638 hist[bb] = hist[bb] + 1 639 } 640 let xi: nx_int = hi % width 641 let yi: nx_int = hi / width 642 if xi + 1 < width { 643 let dr: nx_int = heightmap[hi + 1] - heightmap[hi] 644 var adr: nx_int = dr 645 if adr < 0 { adr = 0 - adr } 646 sum_abs_diff = sum_abs_diff + adr 647 n_diff = n_diff + 1 648 } 649 if yi + 1 < height { 650 let dd: nx_int = heightmap[hi + width] - heightmap[hi] 651 var add_: nx_int = dd 652 if add_ < 0 { add_ = 0 - add_ } 653 sum_abs_diff = sum_abs_diff + add_ 654 n_diff = n_diff + 1 655 } 656 hi = hi + 1 657 } 658 659 // LOCAL_VARIATION. 660 var local_var: nx_int = 0 661 if n_diff > 0 { 662 let mean_abs_diff: nx_int = sum_abs_diff / n_diff 663 local_var = (mean_abs_diff * 4 * q) / max_expected_relief 664 if local_var > q { local_var = q } 665 if local_var < 0 { local_var = 0 } 666 } 667 out_verdict[NX_WQG_OFF_LOCAL_VAR] = local_var 668 669 // FLATNESS. 670 var max_bucket: nx_int = 0 671 var bi: nx_int = 0 672 while bi < NX_WQG_HIST_BUCKETS { 673 if hist[bi] > max_bucket { max_bucket = hist[bi] } 674 bi = bi + 1 675 } 676 let flatness: nx_int = (max_bucket * q) / n 677 out_verdict[NX_WQG_OFF_FLATNESS_PENALTY] = flatness 678 679 // EXTREMES. 680 let top_t: nx_int = hmin + (range * 9) / 10 681 let bot_t: nx_int = hmin + range / 10 682 var n_extreme: nx_int = 0 683 var ei: nx_int = 0 684 while ei < n { 685 let v: nx_int = heightmap[ei] 686 if v >= top_t { n_extreme = n_extreme + 1 } 687 if v <= bot_t { n_extreme = n_extreme + 1 } 688 ei = ei + 1 689 } 690 var extremes: nx_int = (n_extreme * 5 * q) / n 691 if extremes > q { extremes = q } 692 out_verdict[NX_WQG_OFF_EXTREMES] = extremes 693 694 // FRACTAL_DIM. 695 let fractal: nx_int = _wqg_fractal_dim_score(heightmap, width, height) 696 out_verdict[NX_WQG_OFF_FRACTAL_DIM] = fractal 697 698 // BIOME_COHERENCE. 699 var biome_score: nx_int = 0 700 var biome_provided: nx_int = 0 701 if (biome_map as i64) != 0 { 702 biome_provided = 1 703 biome_score = _wqg_biome_coherence_score(heightmap, biome_map, width, height, hmin, hmax) 704 } 705 out_verdict[NX_WQG_OFF_BIOME_COHERENCE] = biome_score 706 707 // FEATURE_VARIETY. 708 var variety_score: nx_int = 0 709 var variety_provided: nx_int = 0 710 if (feature_hist as i64) != 0 { 711 if n_feature_kinds > 0 { 712 variety_provided = 1 713 variety_score = _wqg_feature_variety_score(feature_hist, n_feature_kinds) 714 } 715 } 716 out_verdict[NX_WQG_OFF_FEATURE_VARIETY] = variety_score 717 718 // READABILITY. 719 let readability: nx_int = _wqg_readability_score(heightmap, width, height, max_expected_relief) 720 out_verdict[NX_WQG_OFF_READABILITY] = readability 721 722 // Per-axis verdicts. 723 let v_div: nx_int = _wqg_axis_verdict(diversity, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 724 let v_loc: nx_int = _wqg_axis_verdict_banded(local_var, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR, NX_WQG_LOCAL_VAR_UPPER) 725 let v_ext: nx_int = _wqg_axis_verdict(extremes, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 726 var v_flat: nx_int = 0 - 1 727 if flatness <= NX_WQG_FLATNESS_OK_CEIL { v_flat = 1 } 728 if flatness > NX_WQG_FLATNESS_OK_CEIL { 729 if flatness <= NX_WQG_FLATNESS_BAD_CEIL { v_flat = 0 } 730 } 731 let v_frac: nx_int = _wqg_axis_verdict(fractal, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 732 733 // Optional axes -- MARGINAL when not provided. 734 var v_bio: nx_int = 0 735 if biome_provided == 1 { 736 v_bio = _wqg_axis_verdict(biome_score, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 737 } 738 var v_var: nx_int = 0 739 if variety_provided == 1 { 740 v_var = _wqg_axis_verdict(variety_score, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 741 } 742 let v_read: nx_int = _wqg_axis_verdict(readability, NX_WQG_AXIS_WIN_FLOOR, NX_WQG_AXIS_MARGINAL_FLOOR) 743 744 let grade: nx_int = _wqg_grade_from_8(v_div, v_loc, v_ext, v_flat, v_frac, v_bio, v_var, v_read) 745 out_verdict[NX_WQG_OFF_GRADE] = grade 746 out_verdict[NX_WQG_OFF_REFINE_AXIS] = _wqg_pick_refine_axis( 747 grade, diversity, local_var, extremes, flatness, 748 fractal, biome_score, variety_score, readability, 749 biome_provided, variety_provided 750 ) 751} 752 753// ===== Self-test ==================================================== 754func main() -> i64 { 755 let q: nx_int = NX_WQG_Q 756 let v: *i64 = (sys_mmap(NX_WQG_OUT_STRIDE * NX_SIZEOF_NX_INT)) as *i64 757 let null_ptr: *i64 = 0 as *i64 758 759 // T1: Validity predicates. 760 if nx_grade_is_valid(NX_GRADE_F) != 1 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 761 if nx_grade_is_valid(NX_GRADE_S) != 1 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 762 if nx_grade_is_valid(99) != 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 763 if nx_wqg_refine_is_valid(NX_WQG_REFINE_NONE) != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 764 if nx_wqg_refine_is_valid(NX_WQG_REFINE_IMPROVE_READ) != 1 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 765 if nx_wqg_refine_is_valid(99) != 0 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 766 767 // T2: Constant heightmap -> F (no diversity). 768 let w: nx_int = 8 769 let h: nx_int = 8 770 let n: nx_int = w * h 771 let map_flat: *i64 = (sys_mmap(n * NX_SIZEOF_NX_INT)) as *i64 772 var i: nx_int = 0 773 while i < n { map_flat[i] = 100; i = i + 1 } 774 nx_world_quality_grade(map_flat, w, h, 1000, null_ptr, null_ptr, 0, v) 775 if v[NX_WQG_OFF_GRADE] != NX_GRADE_F { return __syscall(93, 10, 0, 0, 0, 0, 0) } 776 if v[NX_WQG_OFF_DIVERSITY] != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 777 if v[NX_WQG_OFF_REFINE_AXIS] != NX_WQG_REFINE_MORE_RELIEF { return __syscall(93, 12, 0, 0, 0, 0, 0) } 778 779 // T3: Linear ramp -- good diversity. 780 let map_ramp: *i64 = (sys_mmap(n * NX_SIZEOF_NX_INT)) as *i64 781 var j: nx_int = 0 782 while j < n { 783 map_ramp[j] = j * 1000 / n 784 j = j + 1 785 } 786 nx_world_quality_grade(map_ramp, w, h, 1000, null_ptr, null_ptr, 0, v) 787 if v[NX_WQG_OFF_DIVERSITY] < NX_MAGIC_9000 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 788 if v[NX_WQG_OFF_MIN_HEIGHT] != 0 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 789 if v[NX_WQG_OFF_MAX_HEIGHT] < 900 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 790 if v[NX_WQG_OFF_GRADE] < NX_GRADE_D { return __syscall(93, 23, 0, 0, 0, 0, 0) } 791 792 // T4: 8x8 "mountain" map -- has extremes, varied. 793 let map_real: *i64 = (sys_mmap(n * NX_SIZEOF_NX_INT)) as *i64 794 var ry: nx_int = 0 795 while ry < h { 796 var rx: nx_int = 0 797 while rx < w { 798 let dx: nx_int = rx - 4 799 let dy: nx_int = ry - 4 800 let d2: nx_int = dx * dx + dy * dy 801 var h_val: nx_int = 1000 - d2 * 50 802 if h_val < 0 { h_val = 0 } 803 if (rx + ry) % 2 == 0 { h_val = h_val + 50 } 804 map_real[ry * w + rx] = h_val 805 rx = rx + 1 806 } 807 ry = ry + 1 808 } 809 nx_world_quality_grade(map_real, w, h, 1000, null_ptr, null_ptr, 0, v) 810 if v[NX_WQG_OFF_DIVERSITY] < NX_MAGIC_9000 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 811 if v[NX_WQG_OFF_EXTREMES] == 0 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 812 if v[NX_WQG_OFF_GRADE] < NX_GRADE_C { return __syscall(93, 32, 0, 0, 0, 0, 0) } 813 814 // T5: Refusal paths. 815 nx_world_quality_grade(map_flat, 0, h, 1000, null_ptr, null_ptr, 0, v) 816 if v[NX_WQG_OFF_GRADE] != NX_GRADE_F { return __syscall(93, 40, 0, 0, 0, 0, 0) } 817 nx_world_quality_grade(map_flat, w, h, 0, null_ptr, null_ptr, 0, v) 818 if v[NX_WQG_OFF_GRADE] != NX_GRADE_F { return __syscall(93, 41, 0, 0, 0, 0, 0) } 819 820 // T6: Grade-letter helper -- 8 wins -> S. 821 if _wqg_grade_from_8(1, 1, 1, 1, 1, 1, 1, 1) != NX_GRADE_S { return __syscall(93, 50, 0, 0, 0, 0, 0) } 822 // 7 wins -> A. 823 if _wqg_grade_from_8(1, 1, 1, 1, 1, 1, 1, 0) != NX_GRADE_A { return __syscall(93, 51, 0, 0, 0, 0, 0) } 824 // 6 wins, 0 losses -> B. 825 if _wqg_grade_from_8(1, 1, 1, 1, 1, 1, 0, 0) != NX_GRADE_B { return __syscall(93, 52, 0, 0, 0, 0, 0) } 826 // 4 losses -> F. 827 if _wqg_grade_from_8(0 - 1, 0 - 1, 0 - 1, 0 - 1, 1, 1, 1, 1) != NX_GRADE_F { return __syscall(93, 53, 0, 0, 0, 0, 0) } 828 // 3 wins, 2 losses, 3 marginal -> D. 829 if _wqg_grade_from_8(1, 1, 1, 0, 0, 0, 0 - 1, 0 - 1) != NX_GRADE_D { return __syscall(93, 54, 0, 0, 0, 0, 0) } 830 831 // T7: BIOME_COHERENCE -- map_real has hi peak at centre, low corners. 832 // Compute the band cutoffs the grader will use (quartiles of the 833 // observed range), then assign biomes that match those bands so 834 // BIOME_COHERENCE -> ~Q. Grader bands: 835 // v <= hmin + range/4 -> LOW 836 // v <= hmin + range*2/4 -> MID 837 // v <= hmin + range*3/4 -> HIGH 838 // else -> PEAK 839 let biome_map: *i64 = (sys_mmap(n * NX_SIZEOF_NX_INT)) as *i64 840 // map_real has hmin ~ 0 (some cells = 0), hmax ~ 1050 (centre + jitter). 841 // Re-scan to compute actual hmin/hmax for the test: 842 var th_min: nx_int = map_real[0] 843 var th_max: nx_int = map_real[0] 844 var th_i: nx_int = 0 845 while th_i < n { 846 let vv: nx_int = map_real[th_i] 847 if vv < th_min { th_min = vv } 848 if vv > th_max { th_max = vv } 849 th_i = th_i + 1 850 } 851 let th_range: nx_int = th_max - th_min 852 let band1_top: nx_int = th_min + th_range / 4 853 let band2_top: nx_int = th_min + (th_range * 2) / 4 854 let band3_top: nx_int = th_min + (th_range * 3) / 4 855 var bi2: nx_int = 0 856 while bi2 < n { 857 let hv: nx_int = map_real[bi2] 858 if hv <= band1_top { biome_map[bi2] = 8 } // DESERT (LOW) 859 if hv > band1_top { 860 if hv <= band2_top { biome_map[bi2] = 5 } // TEMPERATE_FOREST (MID) 861 } 862 if hv > band2_top { 863 if hv <= band3_top { biome_map[bi2] = 12 } // SNOW (HIGH) 864 } 865 if hv > band3_top { biome_map[bi2] = 13 } // ICE (PEAK) 866 bi2 = bi2 + 1 867 } 868 nx_world_quality_grade(map_real, w, h, 1000, biome_map, null_ptr, 0, v) 869 if v[NX_WQG_OFF_BIOME_COHERENCE] < NX_MAGIC_12000 { return __syscall(93, 60, 0, 0, 0, 0, 0) } // >= 0.73Q 870 871 // T8: BIOME_COHERENCE with WRONG biomes -> low score. 872 var bi3: nx_int = 0 873 while bi3 < n { biome_map[bi3] = 13; bi3 = bi3 + 1 } // all ICE everywhere 874 nx_world_quality_grade(map_real, w, h, 1000, biome_map, null_ptr, 0, v) 875 // ICE expects PEAK band only; most cells aren't there -> low score. 876 if v[NX_WQG_OFF_BIOME_COHERENCE] > NX_MAGIC_8000 { return __syscall(93, 70, 0, 0, 0, 0, 0) } 877 878 // T9: FEATURE_VARIETY -- uniform 4-kind distribution = max. 879 let hist4: *i64 = (sys_mmap(4 * NX_SIZEOF_NX_INT)) as *i64 880 hist4[0] = 10 881 hist4[1] = 10 882 hist4[2] = 10 883 hist4[3] = 10 884 nx_world_quality_grade(map_real, w, h, 1000, null_ptr, hist4, 4, v) 885 if v[NX_WQG_OFF_FEATURE_VARIETY] < NX_MAGIC_14000 { return __syscall(93, 80, 0, 0, 0, 0, 0) } // ~Q 886 // All-one-kind -> 0. 887 hist4[0] = 40 888 hist4[1] = 0 889 hist4[2] = 0 890 hist4[3] = 0 891 nx_world_quality_grade(map_real, w, h, 1000, null_ptr, hist4, 4, v) 892 if v[NX_WQG_OFF_FEATURE_VARIETY] != 0 { return __syscall(93, 81, 0, 0, 0, 0, 0) } 893 894 // T10: READABILITY non-zero on the mountain map (has structured 895 // local variation, not pure noise). 896 nx_world_quality_grade(map_real, w, h, 1000, null_ptr, null_ptr, 0, v) 897 if v[NX_WQG_OFF_READABILITY] <= 0 { return __syscall(93, 90, 0, 0, 0, 0, 0) } 898 899 // T11: FRACTAL_DIM ramp test -- pure ramp scales linearly, so 900 // mean4/mean1 ~ 4 -> H ~ 1.0 -> fractal score near 0 (too smooth). 901 nx_world_quality_grade(map_ramp, w, h, 1000, null_ptr, null_ptr, 0, v) 902 if v[NX_WQG_OFF_FRACTAL_DIM] > NX_MAGIC_6553 { return __syscall(93, 100, 0, 0, 0, 0, 0) } // < 0.4Q 903 904 return 0 905}