code wiki / (root) / nx_town_quality.nx

nx_town_quality.nx source

↩ module page · 156 lines · 6375 B

1// nx_town_quality.nx -- per-kind grader for nx_town_layout output. 2// 3// Samples a town's cell-type field on a grid and grades on 5 axes: 4// 5// 0. ROAD_CONNECTIVITY: fraction of cells that are ROAD; target band 6// [10%, 25%] for healthy circulation. 7// 1. BUILDING_DENSITY: fraction of cells that are BUILDING; target 8// [30%, 60%]. 9// 2. PLAZA_PRESENCE: ratio of plaza cells / total; target [2%, 10%]. 10// 3. WALL_INTEGRITY: for walled types, wall cells / perimeter 11// estimate. Skipped for unwalled. 12// 4. ZONE_BALANCE: building_count / (road + plaza + 1) -- catches 13// over-built towns with no public space. 14// 15// EMITS LAYER_VERDICT (kind = NX_LAYER_KIND_TOWN). 16// 17// genealogy_id: morris_1979_urban_form + sjoberg_1960 18// lineage_id: nx_town_quality_5axis_v1 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "nx_syscalls.nx" 27import "nx_tier.nx" 28import "nx_layer_verdict.nx" 29 30// Cell-type IDs match nx_town_layout convention. 31const NX_TQ_CELL_VOID: nx_int = 0 32const NX_TQ_CELL_BUILDING: nx_int = 1 33const NX_TQ_CELL_ROAD: nx_int = 2 34const NX_TQ_CELL_PLAZA: nx_int = 3 35const NX_TQ_CELL_WALL: nx_int = 4 36 37const NX_TQ_Q: nx_int = 16384 38 39const NX_TQ_AXIS_ROAD_CONN: nx_int = 0 40const NX_TQ_AXIS_BUILDING_DENS: nx_int = 1 41const NX_TQ_AXIS_PLAZA: nx_int = 2 42const NX_TQ_AXIS_WALL_INTEG: nx_int = 3 43const NX_TQ_AXIS_ZONE_BAL: nx_int = 4 44const NX_TQ_AXIS_COUNT: nx_int = 5 45 46// ===== Target-band scorer (smooth tent function) ================== 47// score = 0 below lo; ramps to Q at lo; full Q in [lo, hi]; ramps 48// down to 0 past 2*hi-lo. 49func _tq_band_score(val: nx_int, lo_q: nx_int, hi_q: nx_int) -> nx_int { 50 let q: nx_int = NX_TQ_Q 51 if val < 0 { return 0 } 52 if val < lo_q { 53 if lo_q > 0 { return (val * q) / lo_q } 54 return 0 55 } 56 if val <= hi_q { return q } 57 let extra: nx_int = val - hi_q 58 let span: nx_int = hi_q - lo_q 59 if span <= 0 { return 0 } 60 var score: nx_int = q - (extra * q) / span 61 if score < 0 { score = 0 } 62 return score 63} 64 65// ===== Public: town grader on a pre-tabulated cell-count histogram 66// cell_hist[i] = count of cells with type i (5 entries). 67func nx_town_quality_grade( 68 cell_hist: *i64, total_cells: nx_int, is_walled: nx_int, 69 out_verdict: *i64 70) { 71 let q: nx_int = NX_TQ_Q 72 if total_cells <= 0 { 73 nx_layer_verdict_init(out_verdict, NX_LAYER_KIND_TOWN, 74 NX_TQ_AXIS_COUNT, NX_LAYER_REFINE_FIX_COHERENCE) 75 nx_layer_verdict_finalize(out_verdict) 76 return 77 } 78 let road_frac: nx_int = (cell_hist[NX_TQ_CELL_ROAD] * q) / total_cells 79 let build_frac: nx_int = (cell_hist[NX_TQ_CELL_BUILDING] * q) / total_cells 80 let plaza_frac: nx_int = (cell_hist[NX_TQ_CELL_PLAZA] * q) / total_cells 81 let wall_frac: nx_int = (cell_hist[NX_TQ_CELL_WALL] * q) / total_cells 82 83 let road_score: nx_int = _tq_band_score(road_frac, q / 10, q / 4) // [10%, 25%] 84 let build_score: nx_int = _tq_band_score(build_frac, q * 3 / 10, q * 6 / 10) // [30%, 60%] 85 let plaza_score: nx_int = _tq_band_score(plaza_frac, q / 50, q / 10) // [2%, 10%] 86 var wall_score: nx_int = q / 2 // MARGINAL default when unwalled 87 if is_walled == 1 { 88 // Walled: target [3%, 12%] -- a 1-cell-thick ring on the perimeter. 89 wall_score = _tq_band_score(wall_frac, q * 3 / 100, q * 12 / 100) 90 } 91 var zone_bal_score: nx_int = 0 92 let total_built: nx_int = cell_hist[NX_TQ_CELL_BUILDING] 93 let total_public: nx_int = cell_hist[NX_TQ_CELL_ROAD] + cell_hist[NX_TQ_CELL_PLAZA] + 1 94 let ratio_q: nx_int = (total_built * q) / total_public 95 // Target ratio [1.5Q, 4Q] -- healthy: a few buildings per public-space cell. 96 zone_bal_score = _tq_band_score(ratio_q, q * 3 / 2, q * 4) 97 98 nx_layer_verdict_init(out_verdict, NX_LAYER_KIND_TOWN, 99 NX_TQ_AXIS_COUNT, NX_LAYER_REFINE_FIX_COHERENCE) 100 out_verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_ROAD_CONN] = road_score 101 out_verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_BUILDING_DENS] = build_score 102 out_verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_PLAZA] = plaza_score 103 out_verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_WALL_INTEG] = wall_score 104 out_verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_ZONE_BAL] = zone_bal_score 105 nx_layer_verdict_finalize(out_verdict) 106} 107 108// ===== Self-test ==================================================== 109func main() -> i64 { 110 let q: nx_int = NX_TQ_Q 111 let verdict: *i64 = (sys_mmap(NX_LV_STRIDE * NX_SIZEOF_NX_INT)) as *i64 112 let hist: *i64 = (sys_mmap(8 * NX_SIZEOF_NX_INT)) as *i64 113 114 // T1: Balanced town -- 100 buildings, 30 roads, 5 plaza, 8 walls, 115 // total 200 cells (rest VOID). 116 hist[NX_TQ_CELL_VOID] = 57 117 hist[NX_TQ_CELL_BUILDING] = 100 118 hist[NX_TQ_CELL_ROAD] = 30 119 hist[NX_TQ_CELL_PLAZA] = 5 120 hist[NX_TQ_CELL_WALL] = 8 121 nx_town_quality_grade(hist, 200, 1, verdict) 122 // Building density = 50% -> in target band -> Q. 123 if verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_BUILDING_DENS] != q { 124 return __syscall(93, 1, 0, 0, 0, 0, 0) 125 } 126 // Road = 15% -> in band -> Q. 127 if verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_ROAD_CONN] != q { 128 return __syscall(93, 2, 0, 0, 0, 0, 0) 129 } 130 // Plaza = 2.5% -> in band -> Q. 131 if verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_PLAZA] != q { 132 return __syscall(93, 3, 0, 0, 0, 0, 0) 133 } 134 135 // T2: Over-built town with no roads -> low road + low zone-balance. 136 hist[NX_TQ_CELL_VOID] = 0 137 hist[NX_TQ_CELL_BUILDING] = 195 138 hist[NX_TQ_CELL_ROAD] = 5 139 hist[NX_TQ_CELL_PLAZA] = 0 140 hist[NX_TQ_CELL_WALL] = 0 141 nx_town_quality_grade(hist, 200, 0, verdict) 142 if verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_ROAD_CONN] * 10 >= q * 9 { 143 return __syscall(93, 10, 0, 0, 0, 0, 0) 144 } 145 if verdict[NX_LV_OFF_AXIS_0 + NX_TQ_AXIS_ZONE_BAL] >= q { 146 return __syscall(93, 11, 0, 0, 0, 0, 0) 147 } 148 149 // T3: Empty input -> safe init. 150 nx_town_quality_grade(hist, 0, 0, verdict) 151 if nx_lv_grade_is_valid(verdict[NX_LV_OFF_GRADE]) != 1 { 152 return __syscall(93, 20, 0, 0, 0, 0, 0) 153 } 154 155 return 0 156}