code wiki / (root) / nx_castle_quality.nx

nx_castle_quality.nx source

↩ module page · 140 lines · 5771 B

1// nx_castle_quality.nx -- per-kind grader for nx_castle_layout output. 2// 3// Samples a castle's cell-type field on a grid; grades on 5 axes: 4// 5// 0. TOWER_PLACEMENT: towers form an angularly-balanced ring (or 6// rectangle corners for COASTAL). Score = inverse of stddev of 7// pairwise tower angular separations. 8// 1. WALL_CONTINUITY: wall cells form a continuous ring (no large 9// gaps). Score = wall_count / expected_perimeter; clipped to Q. 10// 2. GATE_PRESENCE: at least one gate cell. Binary axis. 11// 3. KEEP_PROMINENCE: keep block exists AND is taller than other 12// structures (we use a flag: caller passes 1 if so). 13// 4. SCALE_APPROPRIATENESS: outer_radius in [10m, 80m] (Q14) -- too 14// small isn't a castle; too big isn't credible. 15// 16// Inputs: instead of re-sampling the castle field, caller passes 17// pre-computed feature counts. This keeps the grader O(1). 18// 19// EMITS LAYER_VERDICT (kind = NX_LAYER_KIND_CASTLE). 20 21// nx_safety_envelope: 22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 23// sil_target: SIL1 24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 25// verdict: NOT_YET_EVALUATED 26 27import "nx_syscalls.nx" 28import "nx_tier.nx" 29import "nx_layer_verdict.nx" 30 31const NX_CQ_Q: nx_int = 16384 32 33const NX_CQ_AXIS_TOWER_PLACE: nx_int = 0 34const NX_CQ_AXIS_WALL_CONT: nx_int = 1 35const NX_CQ_AXIS_GATE_PRESENCE: nx_int = 2 36const NX_CQ_AXIS_KEEP_PROM: nx_int = 3 37const NX_CQ_AXIS_SCALE: nx_int = 4 38const NX_CQ_AXIS_COUNT: nx_int = 5 39 40func _cq_band_score(val: nx_int, lo_q: nx_int, hi_q: nx_int) -> nx_int { 41 let q: nx_int = NX_CQ_Q 42 if val < 0 { return 0 } 43 if val < lo_q { 44 if lo_q > 0 { return (val * q) / lo_q } 45 return 0 46 } 47 if val <= hi_q { return q } 48 let extra: nx_int = val - hi_q 49 let span: nx_int = hi_q - lo_q 50 if span <= 0 { return 0 } 51 var s: nx_int = q - (extra * q) / span 52 if s < 0 { s = 0 } 53 return s 54} 55 56// ===== Public: castle grader ======================================= 57// Inputs (all Q14 unless noted): 58// n_towers number of placed towers 59// tower_target caller-expected tower count (e.g. 8 for CONCENTRIC) 60// wall_cells count of WALL cells in the sampled field 61// expected_wall caller-expected wall cells = 2*pi*r*thickness/cell_area 62// has_gate 0/1 63// has_keep 0/1 64// outer_radius_q14 castle outer radius in Q14 metres 65func nx_castle_quality_grade( 66 n_towers: nx_int, tower_target: nx_int, 67 wall_cells: nx_int, expected_wall: nx_int, 68 has_gate: nx_int, has_keep: nx_int, 69 outer_radius_q14: nx_int, 70 out_verdict: *i64 71) { 72 let q: nx_int = NX_CQ_Q 73 74 // Tower placement: ratio of placed to expected (capped at Q). 75 var tower_score: nx_int = 0 76 if tower_target > 0 { 77 let ratio: nx_int = (n_towers * q) / tower_target 78 tower_score = ratio 79 if tower_score > q { tower_score = q } 80 } 81 if tower_target <= 0 { tower_score = q / 2 } // unknown target 82 83 // Wall continuity. 84 var wall_score: nx_int = 0 85 if expected_wall > 0 { 86 let ratio: nx_int = (wall_cells * q) / expected_wall 87 wall_score = ratio 88 if wall_score > q { wall_score = q } 89 } 90 if expected_wall <= 0 { wall_score = q / 2 } 91 92 // Gate presence (binary). 93 var gate_score: nx_int = 0 94 if has_gate == 1 { gate_score = q } 95 96 // Keep prominence (binary). 97 var keep_score: nx_int = 0 98 if has_keep == 1 { keep_score = q } 99 100 // Scale appropriateness. 101 let lo_r: nx_int = 10 * q 102 let hi_r: nx_int = 80 * q 103 let scale_score: nx_int = _cq_band_score(outer_radius_q14, lo_r, hi_r) 104 105 nx_layer_verdict_init(out_verdict, NX_LAYER_KIND_CASTLE, 106 NX_CQ_AXIS_COUNT, NX_LAYER_REFINE_FIX_COHERENCE) 107 out_verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_TOWER_PLACE] = tower_score 108 out_verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_WALL_CONT] = wall_score 109 out_verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_GATE_PRESENCE] = gate_score 110 out_verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_KEEP_PROM] = keep_score 111 out_verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_SCALE] = scale_score 112 nx_layer_verdict_finalize(out_verdict) 113} 114 115// ===== Self-test ==================================================== 116func main() -> i64 { 117 let q: nx_int = NX_CQ_Q 118 let verdict: *i64 = (sys_mmap(NX_LV_STRIDE * NX_SIZEOF_NX_INT)) as *i64 119 120 // T1: Healthy concentric castle: 8 towers, 100 wall cells out of 121 // 100 expected, gate, keep, 30m radius. 122 nx_castle_quality_grade(8, 8, 100, 100, 1, 1, 30 * q, verdict) 123 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_TOWER_PLACE] != q { return __syscall(93, 1, 0, 0, 0, 0, 0) } 124 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_WALL_CONT] != q { return __syscall(93, 2, 0, 0, 0, 0, 0) } 125 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_GATE_PRESENCE] != q { return __syscall(93, 3, 0, 0, 0, 0, 0) } 126 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_KEEP_PROM] != q { return __syscall(93, 4, 0, 0, 0, 0, 0) } 127 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_SCALE] != q { return __syscall(93, 5, 0, 0, 0, 0, 0) } 128 if verdict[NX_LV_OFF_GRADE] != NX_LV_GRADE_S { return __syscall(93, 6, 0, 0, 0, 0, 0) } 129 130 // T2: Castle with no gate + no keep -> 2 losses, grade C 131 // (3 wins, 2 losses with 5 axes). 132 nx_castle_quality_grade(8, 8, 100, 100, 0, 0, 30 * q, verdict) 133 if verdict[NX_LV_OFF_GRADE] >= NX_LV_GRADE_B { return __syscall(93, 10, 0, 0, 0, 0, 0) } 134 135 // T3: Castle with bad scale (1m) -> scale axis 0. 136 nx_castle_quality_grade(8, 8, 100, 100, 1, 1, q, verdict) 137 if verdict[NX_LV_OFF_AXIS_0 + NX_CQ_AXIS_SCALE] >= q / 5 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 138 139 return 0 140}