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}