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}