code wiki / (root) / nx_christus_integration_test.nx

nx_christus_integration_test.nx source

↩ module page · 146 lines · 5998 B

1// nx_christus_integration_test.nx -- Phase B2 of S-class hardening. 2// 3// Real-input integration test on a Christus-class geometry: tall 4// narrow cone-tetrahedron (10mm × 10mm base × 40mm tall apex). 5// Three slanted faces; base 5mm radius × 40mm tall -> tipover 6// ratio 8.0 >> Qidi tipover threshold 0.98. 7// 8// HONEST GEOMETRY NOTE: this cone-tet has APEX-UP orientation, so 9// each Z slice shrinks toward the apex. Layers are STRICTLY INSIDE 10// the previous, meaning auto-supports legitimately do NOT fire (no 11// overhang by geometry; the slant goes inward as z rises). A real 12// Christus STL with cantilever arms would have growing-layer regions 13// where supports DO fire -- queued as Phase B2.1. 14// 15// What this test composes end-to-end: 16// 1. Build the cone-tet mesh manually (substrate primitives) 17// 2. nx_slice_pipe_run_v2 produces G-code on real geometry 18// 3. nx_klipper_gcode_validate confirms output is syntactically 19// valid Klipper 20// 4. G-code contains expected preamble + skirt + postamble markers 21// 5. Sensible byte count + layer count 22// 23// This validates the FULL stack the operator would use to print 24// a Christus statue, exercised on a tall-narrow-but-shrinking 25// geometry that's a real graceful-handling test for the substrate. 26// 27// expect_exit: 0 28// license_tier: ORIGINAL 29 30import "nx_syscalls.nx" 31import "nx_mesh.nx" 32import "nx_machine_graph.nx" 33import "nx_material_profile.nx" 34import "nx_gcode_emit.nx" 35import "nx_slice_pipeline.nx" 36import "nx_klipper_gcode_validator.nx" 37 38const Q14: i64 = 16384 39 40func smoke_count(buf: *u8, len: i64, needle: *u8) -> i64 { 41 var nlen: i64 = 0 42 while needle[nlen] != 0 { nlen = nlen + 1 } 43 if len < nlen { return 0 } 44 var count: i64 = 0 45 var i: i64 = 0 46 let last: i64 = len - nlen 47 while i <= last { 48 var j: i64 = 0 49 var matched: i64 = 1 50 while j < nlen { 51 if buf[i + j] != needle[j] { matched = 0; j = nlen } 52 j = j + 1 53 } 54 if matched == 1 { count = count + 1 } 55 i = i + 1 56 } 57 return count 58} 59 60// Build a Christus-class cone-tetrahedron: 61// v0 = (0, 0, 0) base corner 62// v1 = (10, 0, 0) base corner 63// v2 = (5, 9, 0) base corner (forms an ~equilateral triangle in XY) 64// v3 = (5, 3, 40) APEX (tall, off-centre for asymmetric slant) 65// 66// 4 triangles total (1 base + 3 slanted sides). Every side is a 67// real overhang relative to the layer below it (the apex pulls each 68// face inward as Z rises). 69 70func build_christus_cone() -> *NxMesh { 71 let m: *NxMesh = nx_mesh_alloc(4, 4, 0) 72 if (m as i64) == 0 { return m } 73 nx_mesh_set_vertex(m, 0, 0, 0, 0, 0) 74 nx_mesh_set_vertex(m, 1, 10 * Q14, 0, 0, 0) 75 nx_mesh_set_vertex(m, 2, 5 * Q14, 9 * Q14, 0, 0) 76 nx_mesh_set_vertex(m, 3, 5 * Q14, 3 * Q14, 40 * Q14, 0) 77 // CCW outward winding when viewed from outside 78 nx_mesh_set_triangle(m, 0, 0, 2, 1) // base (z=0), normal pointing DOWN 79 nx_mesh_set_triangle(m, 1, 0, 1, 3) // y=0 face 80 nx_mesh_set_triangle(m, 2, 1, 2, 3) // diagonal face 81 nx_mesh_set_triangle(m, 3, 2, 0, 3) // x=0 face 82 return m 83} 84 85func main() -> i64 { 86 let qidi: *NxMachineGraph = nx_machine_graph_qidi_xmax3() 87 let pla: *NxMaterialProfile = nx_material_profile_generic_pla() 88 let lh: i64 = 3277 // 0.2 mm layer height 89 let lw: i64 = 6554 // 0.4 mm line width 90 91 // ===== Build the Christus-cone mesh ===== 92 let mesh: *NxMesh = build_christus_cone() 93 if (mesh as i64) == 0 { return 10 } 94 if mesh.n_tris != 4 { return 11 } 95 if mesh.n_verts != 4 { return 12 } 96 97 // ===== Slice via the integrated pipeline ===== 98 let e: *NxGcodeEmitter = nx_gemit_new(qidi, pla, lh, lw, 2097152) 99 if (e as i64) == 0 { return 20 } 100 nx_gemit_preamble(e) 101 let n_layers: i64 = nx_slice_pipe_run_v2(e, mesh, 20, lh) 102 if n_layers <= 0 { return 30 } 103 104 // ===== Real-physics expectations ===== 105 // 40mm tall at 0.2mm layer height -> ~200 layers. 106 // 3 slanted faces converging on apex -> auto-supports MUST fire. 107 // Base 10mm × 9mm -> base_radius ~ 4.5mm. Tipover ratio ~ 8.9 108 // on Qidi. (Tipover is operator's responsibility to read from 109 // the simulator; this test exercises the slicer/validator.) 110 if n_layers < 100 { return 31 } 111 if n_layers > 300 { return 32 } 112 113 // ===== Validate emitted G-code via Klipper grammar ===== 114 let line_box: *i64 = sys_mmap(8) as *i64 115 let v: i64 = nx_klipper_gcode_validate(e.buf, e.len, line_box) 116 if v != NX_KGV_OK { 117 // Don't paper over: if the slicer emits malformed G-code on 118 // this real geometry, that's a real bug to surface. 119 return 40 120 } 121 122 // ===== Structural markers ===== 123 // M104 / M84 / SKIRT / BED_MESH_CALIBRATE all required. 124 // ;SUPPORT_START NOT required on this shrinking-cone geometry 125 // (apex up = no overhang by design). Documented in file 126 // header per the SUPERIOR-CAPABILITY cardinal: don't paper 127 // over what's honestly absent. 128 if smoke_count(e.buf, e.len, "M104 S") < 1 { return 50 } 129 if smoke_count(e.buf, e.len, "M84") < 1 { return 51 } 130 if smoke_count(e.buf, e.len, ";SKIRT") < 1 { return 52 } 131 if smoke_count(e.buf, e.len, "BED_MESH_CALIBRATE") < 1 { return 53 } 132 133 // ===== G1 move count -- honest range for shrinking cone ===== 134 // Cone tip-up shrinks each layer. Top layers may have ZERO 135 // contour (apex slice = degenerate single point). Effective 136 // printable layer range much smaller than the height suggests. 137 // Real expectation: at least 30 G1 moves (a few layers worth). 138 let n_g1: i64 = smoke_count(e.buf, e.len, "G1 X") 139 if n_g1 < 30 { return 60 } 140 141 // ===== Sensible byte count -- shrinking cone produces small file ===== 142 if e.len < 2000 { return 70 } // preamble + at least some moves 143 if e.len > 1500000 { return 71 } 144 145 return 0 146}