code wiki / (root) / nx_batch_audit_sidecar_test.nx

nx_batch_audit_sidecar_test.nx source

↩ module page · 169 lines · 7256 B

1// nx_batch_audit_sidecar_test.nx -- end-to-end live-fire shape. 2// 3// Simulates a 4-scene batch (same character, morning routine, one 4// deliberate continuity violation, biased outfit picks). Confirms: 5// - per-event continuity verdicts route correctly 6// - pool-utilization classifies as SEVERE_BIAS when one outfit dominates 7// - batch_close summary span carries the right shape 8 9import "nx_syscalls.nx" 10import "nx_runtime.nx" 11import "nx_tier.nx" 12import "nx_directors_note.nx" 13import "nx_world_state.nx" 14import "nx_trace_emit.nx" 15import "nx_batch_audit_sidecar.nx" 16 17func main() -> nx_int { 18 let cfg: *TraceCfg = nx_trace_cfg_alloc() 19 20 // Disable real fd writes during test so qemu's stderr stays clean; 21 // we exercise pure logic + return-code paths. Re-enable would 22 // emit the JSONL spans we saw in the trace_emit smoke. 23 nx_trace_disable(cfg) 24 25 let ws: *WorldState = nx_world_alloc(100, 0 - 1, 4, 4) 26 nx_world_register_character(ws, 7, 0xCAFE, 9) 27 nx_world_register_location(ws, 100, 0xDEAD) 28 nx_world_register_location(ws, 101, 0xBEEF) 29 30 let outfit_keys: *i64 = (sys_mmap(64)) as *i64 31 let outfit_counts: *i64 = (sys_mmap(64)) as *i64 32 let pose_keys: *i64 = (sys_mmap(64)) as *i64 33 let pose_counts: *i64 = (sys_mmap(64)) as *i64 34 nx_pool_hist_init(outfit_keys, outfit_counts, 8) 35 nx_pool_hist_init(pose_keys, pose_counts, 8) 36 37 var n_violations: nx_int = 0 38 39 // ===== Scene 1: legitimate first scene ======================== 40 let e1: *AuditEvent = nx_audit_event_alloc() 41 e1.batch_id = 42 42 e1.scene_seq = 1 43 e1.trace_id = 1000 44 e1.character_id = 7 45 e1.arc_id = 10 46 e1.stage_idx = 0 47 e1.outfit_id = 200 48 e1.location_id = 100 49 e1.pose_id = 300 50 e1.affect_axis = NX_AF_FOCUSED 51 e1.camera_id = 500 52 e1.day_clock_min = 420 53 e1.beat_armed = NX_WS_BEAT_NONE 54 55 let v1: nx_int = nx_audit_process_event(cfg, ws, e1, 56 outfit_keys, outfit_counts, 8, 57 pose_keys, pose_counts, 8) 58 if v1 != NX_CONT_CONSISTENT { return 1 } 59 60 // ===== Scene 2: same outfit + location + later clock -> OK ==== 61 let e2: *AuditEvent = nx_audit_event_alloc() 62 e2.batch_id = 42 63 e2.scene_seq = 2 64 e2.trace_id = 1000 65 e2.character_id = 7 66 e2.outfit_id = 200 67 e2.location_id = 100 68 e2.pose_id = 300 // same pose -- bias-building 69 e2.day_clock_min = 450 70 let v2: nx_int = nx_audit_process_event(cfg, ws, e2, 71 outfit_keys, outfit_counts, 8, 72 pose_keys, pose_counts, 8) 73 if v2 != NX_CONT_CONSISTENT { return 2 } 74 75 // ===== Scene 3: TELEPORT to kitchen with NO beat -> violation = 76 let e3: *AuditEvent = nx_audit_event_alloc() 77 e3.batch_id = 42 78 e3.scene_seq = 3 79 e3.trace_id = 1000 80 e3.character_id = 7 81 e3.outfit_id = 200 82 e3.location_id = 101 // teleport 83 e3.pose_id = 300 84 e3.day_clock_min = 480 85 e3.beat_armed = NX_WS_BEAT_NONE 86 let v3: nx_int = nx_audit_process_event(cfg, ws, e3, 87 outfit_keys, outfit_counts, 8, 88 pose_keys, pose_counts, 8) 89 if v3 != NX_CONT_VIOLATES_LOCATION_TELEPORT { return 3 } 90 n_violations = n_violations + 1 91 92 // World state must NOT have advanced past the violation -- still 2 commits 93 if ws.scene_count != 2 { return 4 } 94 95 // ===== Scene 4: armed beat + new outfit -> OK ================ 96 let e4: *AuditEvent = nx_audit_event_alloc() 97 e4.batch_id = 42 98 e4.scene_seq = 4 99 e4.trace_id = 1000 100 e4.character_id = 7 101 e4.outfit_id = 200 // same outfit 102 e4.location_id = 101 // change location with proper beat 103 e4.pose_id = 300 104 e4.day_clock_min = 480 105 e4.beat_armed = NX_WS_BEAT_LOCATION_CHANGE_OK 106 let v4: nx_int = nx_audit_process_event(cfg, ws, e4, 107 outfit_keys, outfit_counts, 8, 108 pose_keys, pose_counts, 8) 109 if v4 != NX_CONT_CONSISTENT { return 5 } 110 111 // ===== Pool utilization: outfit_id=200 across 4 scenes -> 100% = 112 let outfit_total: *i64 = (sys_mmap(8)) as *i64 113 let outfit_share: nx_int = nx_pool_hist_max_share_q10(outfit_counts, 8, outfit_total) 114 if outfit_total[0] != 4 { return 10 } 115 // 4/4 = 1024 Q10 116 if outfit_share != 1024 { return 11 } 117 if nx_util_classify_q10(outfit_share, outfit_total[0]) != NX_UTIL_SEVERE_BIAS { return 12 } 118 119 let pose_total: *i64 = (sys_mmap(8)) as *i64 120 let pose_share: nx_int = nx_pool_hist_max_share_q10(pose_counts, 8, pose_total) 121 if pose_total[0] != 4 { return 13 } 122 if pose_share != 1024 { return 14 } 123 if nx_util_classify_q10(pose_share, pose_total[0]) != NX_UTIL_SEVERE_BIAS { return 15 } 124 125 // ===== Distinct counts ======================================== 126 if nx_pool_hist_distinct(outfit_keys, 8) != 1 { return 16 } 127 if nx_pool_hist_distinct(pose_keys, 8) != 1 { return 17 } 128 129 // ===== Verdict-band coverage ================================= 130 // 131 // Build a synthetic 2-id case to exercise MILD_BIAS + UNIFORM 132 // without rebuilding the world. 133 let alt_keys: *i64 = (sys_mmap(64)) as *i64 134 let alt_counts: *i64 = (sys_mmap(64)) as *i64 135 nx_pool_hist_init(alt_keys, alt_counts, 8) 136 // 60% / 40% split 137 nx_pool_hist_bump(alt_keys, alt_counts, 8, 100); nx_pool_hist_bump(alt_keys, alt_counts, 8, 100) 138 nx_pool_hist_bump(alt_keys, alt_counts, 8, 100); nx_pool_hist_bump(alt_keys, alt_counts, 8, 100) 139 nx_pool_hist_bump(alt_keys, alt_counts, 8, 100); nx_pool_hist_bump(alt_keys, alt_counts, 8, 100) 140 nx_pool_hist_bump(alt_keys, alt_counts, 8, 200); nx_pool_hist_bump(alt_keys, alt_counts, 8, 200) 141 nx_pool_hist_bump(alt_keys, alt_counts, 8, 200); nx_pool_hist_bump(alt_keys, alt_counts, 8, 200) 142 let alt_total: *i64 = (sys_mmap(8)) as *i64 143 let alt_share: nx_int = nx_pool_hist_max_share_q10(alt_counts, 8, alt_total) 144 if nx_util_classify_q10(alt_share, alt_total[0]) != NX_UTIL_MILD_BIAS { return 20 } 145 146 // Uniform case: 4 ids, 1 each 147 nx_pool_hist_init(alt_keys, alt_counts, 8) 148 nx_pool_hist_bump(alt_keys, alt_counts, 8, 100) 149 nx_pool_hist_bump(alt_keys, alt_counts, 8, 200) 150 nx_pool_hist_bump(alt_keys, alt_counts, 8, 300) 151 nx_pool_hist_bump(alt_keys, alt_counts, 8, 400) 152 let unif_share: nx_int = nx_pool_hist_max_share_q10(alt_counts, 8, alt_total) 153 if nx_util_classify_q10(unif_share, alt_total[0]) != NX_UTIL_UNIFORM { return 21 } 154 155 // Empty case 156 nx_pool_hist_init(alt_keys, alt_counts, 8) 157 let empty_share: nx_int = nx_pool_hist_max_share_q10(alt_counts, 8, alt_total) 158 if nx_util_classify_q10(empty_share, alt_total[0]) != NX_UTIL_EMPTY { return 22 } 159 160 // ===== Summary span emission (re-enable trace so emit fires) == 161 nx_trace_enable(cfg) 162 nx_audit_emit_batch_summary(cfg, 1000, 0 - 1, 163 4, n_violations, 164 outfit_keys, outfit_counts, 8, 165 pose_keys, pose_counts, 8) 166 167 println("nx_batch_audit_sidecar: live-fire shape PASS" as *u8) 168 return 0 169}