Skip to content

Commit 7de3a32

Browse files
aldoruizlunaclaude
andauthored
feat(ab-drive): graph twin of the script (graph format 1.1) (#174)
ab-drive.graph.json is a golden twin of main.py, declared as graph_file on the ab_drive mode beside the script (owner decision D5: graph twins coexist with scripts, parity-checked; main.py stays the rendered source and the oracle). Same parameters (declared in `parameters`), same derivations (the e_end clamp as a ternary pair; the belt levels and rear runs as ternaries on mirrored), same operations in the same order, with the three elements (K, Q, S) unrolled. The right (A) drive is body.mirror("YZ") as `reflect` YZ chosen by `select` on mirrored. Frozen values (coordinator-approved J1): the three roof arms (_bar) take their lengths and angles from hypot/atan2, which the expression dialect lacks. Their inputs are the script's conventions and mirrored only, so k_len/k_ang, q_len/q_ang and s_len/s_ang are computed from the script's conventions by hypot/atan2 and frozen as exact float64 literals (repr round-trip), a ternary on mirrored where they depend on it. Labelled in meta.notes and the README; a change to those conventions in main.py breaks parity at every preset. y4d-spec check --render --require-openscad --parity ./ab-drive: 6 renders, parity 3/3 agree to 0.000000 mm (defaults + left_b_350, right_a_350 (mirrored)), frames 48/48 ok, failures 0. Frames and presets unchanged. Assembly A still validates with --collision (235/235 placed, 292/292 mates, 23/23 poses, errors 0); its digest moves because the GOC-1 tree of ab-drive gains a file. Signed-off-by: Aldo Ruiz Luna <aldo.ruiz.luna@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 645aab2 commit 7de3a32

3 files changed

Lines changed: 176 additions & 0 deletions

File tree

‎ab-drive/ab-drive.graph.json‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
{
2+
"version": "1.1.0",
3+
"units": "mm",
4+
"meta": {
5+
"title": "A/B Drive (graph twin)",
6+
"notes": "Golden twin of main.py (graph format 1.1): the same parameters, the same derivations and the same operations in the same order. main.py stays the oracle; y4d-spec --parity compares the two at the defaults and at every preset. The e_end clamp is a ternary pair; the belt levels and rear runs are ternaries on mirrored; the right (A) drive is body.mirror('YZ') as reflect YZ chosen by select on mirrored. FROZEN VALUES: the three roof arms (_bar) take their lengths and angles from hypot/atan2, which the expression dialect does not have. Their inputs are the script's conventions (POST_K/POST_Q/POST_S centres, K_XY, U_Q, U_S, the rear runs) and mirrored only, so the derived values k_len, k_ang, q_len, q_ang, s_len, s_ang are computed from the script's conventions by hypot/atan2 and frozen as exact float64 literals (a ternary on mirrored where they depend on it). A change to those conventions in main.py breaks parity at every preset, so drift cannot pass unnoticed. Cited facts as main.py: NEMA 17 face 42.3, bolts 31, pilot 22 (keystone nema-17-48mm); GT2 20T belt mid-plane 8 above face A, seat 2; F695 stack 10 (DIN 988 + NSK); GT2 20T idler 9 (Motedis); 2020 profile 20, lip 2, pilot 4.2 (MISUMI HFS5); rail setback 25 (guide p. 88); M5 head 9.5 (ISO 7380-1). Every other number is a labelled convention of main.py."
7+
},
8+
"parameters": {
9+
"mirrored": {"default": false},
10+
"e_end": {"default": 34.0}
11+
},
12+
"derived": [
13+
{"id": "ee_cap", "expr": "e_end < 40 ? e_end : 40"},
14+
{"id": "ee", "expr": "30 > ee_cap ? 30 : ee_cap"},
15+
{"id": "z_own", "expr": "mirrored ? 70 : 54"},
16+
{"id": "z_oth", "expr": "mirrored ? 54 : 70"},
17+
{"id": "y_own", "expr": "mirrored ? -15 : -16"},
18+
{"id": "y_oth", "expr": "mirrored ? -16 : -15"},
19+
{"id": "z_face", "expr": "z_own - 8 - 2"},
20+
{"id": "q_y", "expr": "y_own + 6.5 + 0.506"},
21+
{"id": "s_y", "expr": "y_oth - 6.5 - 1.014"},
22+
{"id": "k_len", "expr": "12.128"},
23+
{"id": "k_ang", "expr": "0.0"},
24+
{"id": "q_len", "expr": "mirrored ? 11.045905847869609 : 11.000001636363514"},
25+
{"id": "q_ang", "expr": "mirrored ? 174.77457654216934 : 179.96874775972861"},
26+
{"id": "s_len", "expr": "mirrored ? 9.901217096902785 : 10.0501791028817"},
27+
{"id": "s_ang", "expr": "mirrored ? 174.284800444444 : 168.60285940532913"},
28+
{"id": "k_floor", "expr": "z_own - 9 / 2"},
29+
{"id": "k_roof", "expr": "k_floor + 9"},
30+
{"id": "k_tip", "expr": "k_roof + 3 - 16"},
31+
{"id": "q_floor", "expr": "z_own - 10 / 2"},
32+
{"id": "q_roof", "expr": "q_floor + 10"},
33+
{"id": "q_tip", "expr": "q_roof + 3 - 16"},
34+
{"id": "q_base", "expr": "z_face + 4 - 0.5"},
35+
{"id": "s_floor", "expr": "z_oth - 10 / 2"},
36+
{"id": "s_roof", "expr": "s_floor + 10"},
37+
{"id": "s_tip", "expr": "s_roof + 3 - 16"}
38+
],
39+
"nodes": [
40+
{"id": "base_blank", "type": "box", "params": {"w": 20.0, "d": 24.5, "h": 6.0}},
41+
{"id": "base", "type": "translate", "inputs": {"shape": "base_blank"}, "params": {"y": -12.25, "z": 3.0}},
42+
{"id": "c_tongue_blank", "type": "box", "params": {"w": 5.8, "d": 22.5, "h": 2.5}},
43+
{"id": "c_tongue", "type": "translate", "inputs": {"shape": "c_tongue_blank"}, "params": {"y": -12.25, "z": -0.75}},
44+
{"id": "body_1", "type": "union", "inputs": {"a": "base", "b": "c_tongue"}},
45+
{"id": "k_bridge_blank", "type": "box", "params": {"w": 20.0, "d": 8.0, "h": 6.0}},
46+
{"id": "k_bridge", "type": "translate", "inputs": {"shape": "k_bridge_blank"}, "params": {"x": -19.5, "y": -24.5, "z": 3.0}},
47+
{"id": "body_2", "type": "union", "inputs": {"a": "body_1", "b": "k_bridge"}},
48+
{"id": "e_arm_blank", "type": "box", "params": {"w": {"expr": "ee + 20 - (10.5)"}, "d": 18.5, "h": 6.0}},
49+
{"id": "e_arm", "type": "translate", "inputs": {"shape": "e_arm_blank"}, "params": {"x": {"expr": "((10.5) + (ee + 20)) / 2"}, "y": -33.75, "z": 3.0}},
50+
{"id": "body_3", "type": "union", "inputs": {"a": "body_2", "b": "e_arm"}},
51+
{"id": "e_corner_blank", "type": "box", "params": {"w": 6.5, "d": 1.0, "h": 6.0}},
52+
{"id": "e_corner", "type": "translate", "inputs": {"shape": "e_corner_blank"}, "params": {"x": 12.75, "y": -24.5, "z": 3.0}},
53+
{"id": "body_4", "type": "union", "inputs": {"a": "body_3", "b": "e_corner"}},
54+
{"id": "e_tongue_blank", "type": "box", "params": {"w": {"expr": "ee + 20 - 1.5 - (ee + 1.5)"}, "d": 5.799999999999997, "h": 2.5}},
55+
{"id": "e_tongue", "type": "translate", "inputs": {"shape": "e_tongue_blank"}, "params": {"x": {"expr": "((ee + 1.5) + (ee + 20 - 1.5)) / 2"}, "y": -34.0, "z": -0.75}},
56+
{"id": "body_5", "type": "union", "inputs": {"a": "body_4", "b": "e_tongue"}},
57+
{"id": "pillar_blank", "type": "box", "params": {"w": 8.5, "d": 12.149999999999999, "h": {"expr": "z_face + 0.5 - (0.0)"}}},
58+
{"id": "pillar", "type": "translate", "inputs": {"shape": "pillar_blank"}, "params": {"x": 6.25, "y": -8.075, "z": {"expr": "((0.0) + (z_face + 0.5)) / 2"}}},
59+
{"id": "body_6", "type": "union", "inputs": {"a": "body_5", "b": "pillar"}},
60+
{"id": "motor_plate_blank", "type": "box", "params": {"w": 48.15, "d": 43.3, "h": {"expr": "z_face + 4 - (z_face)"}}},
61+
{"id": "motor_plate", "type": "translate", "inputs": {"shape": "motor_plate_blank"}, "params": {"x": 34.925, "y": 7.5, "z": {"expr": "((z_face) + (z_face + 4)) / 2"}}},
62+
{"id": "body_7", "type": "union", "inputs": {"a": "body_6", "b": "motor_plate"}},
63+
{"id": "plate_web_blank", "type": "box", "params": {"w": 9.350000000000001, "d": 12.149999999999999, "h": {"expr": "z_face + 4 - (z_face)"}}},
64+
{"id": "plate_web", "type": "translate", "inputs": {"shape": "plate_web_blank"}, "params": {"x": 6.675000000000001, "y": -8.075, "z": {"expr": "((z_face) + (z_face + 4)) / 2"}}},
65+
{"id": "body_8", "type": "union", "inputs": {"a": "body_7", "b": "plate_web"}},
66+
{"id": "pilot_pass_blank", "type": "cylinder", "params": {"r": 12.0, "h": 6.0}},
67+
{"id": "pilot_pass", "type": "translate", "inputs": {"shape": "pilot_pass_blank"}, "params": {"x": 32.0, "y": 8.0, "z": {"expr": "(z_face - 1) + (6.0) / 2"}}},
68+
{"id": "body_9", "type": "cut", "inputs": {"a": "body_8", "b": "pilot_pass"}},
69+
{"id": "m3_1_blank", "type": "cylinder", "params": {"r": 1.7, "h": 6.0}},
70+
{"id": "m3_1", "type": "translate", "inputs": {"shape": "m3_1_blank"}, "params": {"x": 16.5, "y": -7.5, "z": {"expr": "(z_face - 1) + (6.0) / 2"}}},
71+
{"id": "body_10", "type": "cut", "inputs": {"a": "body_9", "b": "m3_1"}},
72+
{"id": "m3_2_blank", "type": "cylinder", "params": {"r": 1.7, "h": 6.0}},
73+
{"id": "m3_2", "type": "translate", "inputs": {"shape": "m3_2_blank"}, "params": {"x": 16.5, "y": 23.5, "z": {"expr": "(z_face - 1) + (6.0) / 2"}}},
74+
{"id": "body_11", "type": "cut", "inputs": {"a": "body_10", "b": "m3_2"}},
75+
{"id": "m3_3_blank", "type": "cylinder", "params": {"r": 1.7, "h": 6.0}},
76+
{"id": "m3_3", "type": "translate", "inputs": {"shape": "m3_3_blank"}, "params": {"x": 47.5, "y": -7.5, "z": {"expr": "(z_face - 1) + (6.0) / 2"}}},
77+
{"id": "body_12", "type": "cut", "inputs": {"a": "body_11", "b": "m3_3"}},
78+
{"id": "m3_4_blank", "type": "cylinder", "params": {"r": 1.7, "h": 6.0}},
79+
{"id": "m3_4", "type": "translate", "inputs": {"shape": "m3_4_blank"}, "params": {"x": 47.5, "y": 23.5, "z": {"expr": "(z_face - 1) + (6.0) / 2"}}},
80+
{"id": "body_13", "type": "cut", "inputs": {"a": "body_12", "b": "m3_4"}},
81+
{"id": "k_boss_blank", "type": "cylinder", "params": {"r": 4.5, "h": {"expr": "k_floor"}}},
82+
{"id": "k_boss", "type": "translate", "inputs": {"shape": "k_boss_blank"}, "params": {"x": -15.372, "y": -25.5, "z": {"expr": "(0.0) + (k_floor) / 2"}}},
83+
{"id": "body_14", "type": "union", "inputs": {"a": "body_13", "b": "k_boss"}},
84+
{"id": "k_post_blank", "type": "box", "params": {"w": 4.0, "d": 6.0, "h": {"expr": "k_roof + 3 - (0.0)"}}},
85+
{"id": "k_post", "type": "translate", "inputs": {"shape": "k_post_blank"}, "params": {"x": -27.5, "y": -25.5, "z": {"expr": "((0.0) + (k_roof + 3)) / 2"}}},
86+
{"id": "body_15", "type": "union", "inputs": {"a": "body_14", "b": "k_post"}},
87+
{"id": "k_arm_blank", "type": "box", "params": {"w": {"expr": "k_len"}, "d": 6.0, "h": 3.0}},
88+
{"id": "k_arm_laid", "type": "translate", "inputs": {"shape": "k_arm_blank"}, "params": {"x": {"expr": "k_len / 2"}, "z": 1.5}},
89+
{"id": "k_arm_turned", "type": "rotate", "inputs": {"shape": "k_arm_laid"}, "params": {"axis": "z", "angle": {"expr": "k_ang"}}},
90+
{"id": "k_arm", "type": "translate", "inputs": {"shape": "k_arm_turned"}, "params": {"x": -27.5, "y": -25.5, "z": {"expr": "k_roof"}}},
91+
{"id": "body_16", "type": "union", "inputs": {"a": "body_15", "b": "k_arm"}},
92+
{"id": "k_roof_pad_blank", "type": "cylinder", "params": {"r": 5.0, "h": 3.0}},
93+
{"id": "k_roof_pad", "type": "translate", "inputs": {"shape": "k_roof_pad_blank"}, "params": {"x": -15.372, "y": -25.5, "z": {"expr": "(k_roof) + (3.0) / 2"}}},
94+
{"id": "body_17", "type": "union", "inputs": {"a": "body_16", "b": "k_roof_pad"}},
95+
{"id": "k_axle_clear_blank", "type": "cylinder", "params": {"r": 2.75, "h": 4.0}},
96+
{"id": "k_axle_clear", "type": "translate", "inputs": {"shape": "k_axle_clear_blank"}, "params": {"x": -15.372, "y": -25.5, "z": {"expr": "(k_roof - 0.5) + (4.0) / 2"}}},
97+
{"id": "body_18", "type": "cut", "inputs": {"a": "body_17", "b": "k_axle_clear"}},
98+
{"id": "k_axle_pilot_blank", "type": "cylinder", "params": {"r": 2.1, "h": {"expr": "k_floor - k_tip + 1"}}},
99+
{"id": "k_axle_pilot", "type": "translate", "inputs": {"shape": "k_axle_pilot_blank"}, "params": {"x": -15.372, "y": -25.5, "z": {"expr": "(k_tip - 0.5) + (k_floor - k_tip + 1) / 2"}}},
100+
{"id": "body_19", "type": "cut", "inputs": {"a": "body_18", "b": "k_axle_pilot"}},
101+
{"id": "q_boss_blank", "type": "cylinder", "params": {"r": 4.5, "h": {"expr": "q_floor - q_base"}}},
102+
{"id": "q_boss", "type": "translate", "inputs": {"shape": "q_boss_blank"}, "params": {"x": 45.0, "y": {"expr": "q_y"}, "z": {"expr": "(q_base) + (q_floor - q_base) / 2"}}},
103+
{"id": "body_20", "type": "union", "inputs": {"a": "body_19", "b": "q_boss"}},
104+
{"id": "q_post_blank", "type": "box", "params": {"w": 4.0, "d": 6.0, "h": {"expr": "q_roof + 3 - (q_base)"}}},
105+
{"id": "q_post", "type": "translate", "inputs": {"shape": "q_post_blank"}, "params": {"x": 56.0, "y": -9.0, "z": {"expr": "((q_base) + (q_roof + 3)) / 2"}}},
106+
{"id": "body_21", "type": "union", "inputs": {"a": "body_20", "b": "q_post"}},
107+
{"id": "q_arm_blank", "type": "box", "params": {"w": {"expr": "q_len"}, "d": 6.0, "h": 3.0}},
108+
{"id": "q_arm_laid", "type": "translate", "inputs": {"shape": "q_arm_blank"}, "params": {"x": {"expr": "q_len / 2"}, "z": 1.5}},
109+
{"id": "q_arm_turned", "type": "rotate", "inputs": {"shape": "q_arm_laid"}, "params": {"axis": "z", "angle": {"expr": "q_ang"}}},
110+
{"id": "q_arm", "type": "translate", "inputs": {"shape": "q_arm_turned"}, "params": {"x": 56.0, "y": -9.0, "z": {"expr": "q_roof"}}},
111+
{"id": "body_22", "type": "union", "inputs": {"a": "body_21", "b": "q_arm"}},
112+
{"id": "q_roof_pad_blank", "type": "cylinder", "params": {"r": 5.0, "h": 3.0}},
113+
{"id": "q_roof_pad", "type": "translate", "inputs": {"shape": "q_roof_pad_blank"}, "params": {"x": 45.0, "y": {"expr": "q_y"}, "z": {"expr": "(q_roof) + (3.0) / 2"}}},
114+
{"id": "body_23", "type": "union", "inputs": {"a": "body_22", "b": "q_roof_pad"}},
115+
{"id": "q_axle_clear_blank", "type": "cylinder", "params": {"r": 2.75, "h": 4.0}},
116+
{"id": "q_axle_clear", "type": "translate", "inputs": {"shape": "q_axle_clear_blank"}, "params": {"x": 45.0, "y": {"expr": "q_y"}, "z": {"expr": "(q_roof - 0.5) + (4.0) / 2"}}},
117+
{"id": "body_24", "type": "cut", "inputs": {"a": "body_23", "b": "q_axle_clear"}},
118+
{"id": "q_axle_pilot_blank", "type": "cylinder", "params": {"r": 2.1, "h": {"expr": "q_floor - q_tip + 1"}}},
119+
{"id": "q_axle_pilot", "type": "translate", "inputs": {"shape": "q_axle_pilot_blank"}, "params": {"x": 45.0, "y": {"expr": "q_y"}, "z": {"expr": "(q_tip - 0.5) + (q_floor - q_tip + 1) / 2"}}},
120+
{"id": "body_25", "type": "cut", "inputs": {"a": "body_24", "b": "q_axle_pilot"}},
121+
{"id": "s_block_blank", "type": "box", "params": {"w": 16.352, "d": {"expr": "s_y + 4.5 - (-24.5)"}, "h": 7.5}},
122+
{"id": "s_block", "type": "translate", "inputs": {"shape": "s_block_blank"}, "params": {"x": 1.8239999999999998, "y": {"expr": "((-24.5) + (s_y + 4.5)) / 2"}, "z": 3.75}},
123+
{"id": "body_26", "type": "union", "inputs": {"a": "body_25", "b": "s_block"}},
124+
{"id": "s_boss_blank", "type": "cylinder", "params": {"r": 4.5, "h": {"expr": "s_floor - 7.0"}}},
125+
{"id": "s_boss", "type": "translate", "inputs": {"shape": "s_boss_blank"}, "params": {"x": -1.852, "y": {"expr": "s_y"}, "z": {"expr": "(7.0) + (s_floor - 7.0) / 2"}}},
126+
{"id": "body_27", "type": "union", "inputs": {"a": "body_26", "b": "s_boss"}},
127+
{"id": "s_post_blank", "type": "box", "params": {"w": 4.0, "d": 7.0, "h": {"expr": "s_roof + 3 - (7.0)"}}},
128+
{"id": "s_post", "type": "translate", "inputs": {"shape": "s_post_blank"}, "params": {"x": 8.0, "y": -24.5, "z": {"expr": "((7.0) + (s_roof + 3)) / 2"}}},
129+
{"id": "body_28", "type": "union", "inputs": {"a": "body_27", "b": "s_post"}},
130+
{"id": "s_arm_blank", "type": "box", "params": {"w": {"expr": "s_len"}, "d": 6.0, "h": 3.0}},
131+
{"id": "s_arm_laid", "type": "translate", "inputs": {"shape": "s_arm_blank"}, "params": {"x": {"expr": "s_len / 2"}, "z": 1.5}},
132+
{"id": "s_arm_turned", "type": "rotate", "inputs": {"shape": "s_arm_laid"}, "params": {"axis": "z", "angle": {"expr": "s_ang"}}},
133+
{"id": "s_arm", "type": "translate", "inputs": {"shape": "s_arm_turned"}, "params": {"x": 8.0, "y": -24.5, "z": {"expr": "s_roof"}}},
134+
{"id": "body_29", "type": "union", "inputs": {"a": "body_28", "b": "s_arm"}},
135+
{"id": "s_roof_pad_blank", "type": "cylinder", "params": {"r": 5.0, "h": 3.0}},
136+
{"id": "s_roof_pad", "type": "translate", "inputs": {"shape": "s_roof_pad_blank"}, "params": {"x": -1.852, "y": {"expr": "s_y"}, "z": {"expr": "(s_roof) + (3.0) / 2"}}},
137+
{"id": "body_30", "type": "union", "inputs": {"a": "body_29", "b": "s_roof_pad"}},
138+
{"id": "s_axle_clear_blank", "type": "cylinder", "params": {"r": 2.75, "h": 4.0}},
139+
{"id": "s_axle_clear", "type": "translate", "inputs": {"shape": "s_axle_clear_blank"}, "params": {"x": -1.852, "y": {"expr": "s_y"}, "z": {"expr": "(s_roof - 0.5) + (4.0) / 2"}}},
140+
{"id": "body_31", "type": "cut", "inputs": {"a": "body_30", "b": "s_axle_clear"}},
141+
{"id": "s_axle_pilot_blank", "type": "cylinder", "params": {"r": 2.1, "h": {"expr": "s_floor - s_tip + 1"}}},
142+
{"id": "s_axle_pilot", "type": "translate", "inputs": {"shape": "s_axle_pilot_blank"}, "params": {"x": -1.852, "y": {"expr": "s_y"}, "z": {"expr": "(s_tip - 0.5) + (s_floor - s_tip + 1) / 2"}}},
143+
{"id": "body_32", "type": "cut", "inputs": {"a": "body_31", "b": "s_axle_pilot"}},
144+
{"id": "c_screw_blank", "type": "cylinder", "params": {"r": 2.75, "h": 7.5}},
145+
{"id": "c_screw", "type": "translate", "inputs": {"shape": "c_screw_blank"}, "params": {"y": -10.0, "z": 0.75}},
146+
{"id": "body_33", "type": "cut", "inputs": {"a": "body_32", "b": "c_screw"}},
147+
{"id": "c_counterbore_blank", "type": "cylinder", "params": {"r": 5.25, "h": 10.0}},
148+
{"id": "c_counterbore", "type": "translate", "inputs": {"shape": "c_counterbore_blank"}, "params": {"y": -10.0, "z": 9.5}},
149+
{"id": "body_34", "type": "cut", "inputs": {"a": "body_33", "b": "c_counterbore"}},
150+
{"id": "e_screw_blank", "type": "cylinder", "params": {"r": 2.75, "h": 7.5}},
151+
{"id": "e_screw", "type": "translate", "inputs": {"shape": "e_screw_blank"}, "params": {"x": {"expr": "ee + 10"}, "y": -34.0, "z": 0.75}},
152+
{"id": "body_35", "type": "cut", "inputs": {"a": "body_34", "b": "e_screw"}},
153+
{"id": "e_counterbore_blank", "type": "cylinder", "params": {"r": 5.25, "h": 10.0}},
154+
{"id": "e_counterbore", "type": "translate", "inputs": {"shape": "e_counterbore_blank"}, "params": {"x": {"expr": "ee + 10"}, "y": -34.0, "z": 9.5}},
155+
{"id": "body_36", "type": "cut", "inputs": {"a": "body_35", "b": "e_counterbore"}},
156+
{"id": "right_a", "type": "reflect", "inputs": {"shape": "body_36"}, "params": {"plane": "YZ"}},
157+
{"id": "ab_drive_part", "type": "select", "inputs": {"if_true": "right_a", "if_false": "body_36"}, "params": {"when": {"expr": "mirrored"}}}
158+
],
159+
"outputs": {"ab_drive": "ab_drive_part"}
160+
}

‎ab-drive/docs/README.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ The bolts sit on each roof arm (roof + 3) and take `bhcs-m5x16`.
134134
- **Left (B) Drive, 350 Gantry:** the defaults.
135135
- **Right (A) Drive, 350 Gantry:** mirrored. The render check may note that this preset has the same volume as the defaults. That is expected: it compares volume only.
136136

137+
## Graph twin
138+
139+
`ab-drive.graph.json` is a node-graph twin of `main.py` (graph format 1.1). It has the same parameters, the same derivations (clamps as min/max ternary pairs) and the same operations in the same order.
140+
- **Declaration.** The mode declares it as `graph_file` next to the script.
141+
- **Hands.** The belt levels and rear runs are ternaries on `mirrored`. The right (A) drive is the script's `body.mirror("YZ")`: a `reflect` YZ chosen by `select` on `mirrored`.
142+
- **Frozen values.** The three roof arms (`_bar`) take their lengths and angles from `hypot`/`atan2`, which the expression dialect does not have. Their inputs are the script's conventions (the post centres, `K_XY`, `U_Q`, `U_S`, the rear runs) and `mirrored` only, so `k_len`, `k_ang`, `q_len`, `q_ang`, `s_len` and `s_ang` are computed from the script's conventions by hypot/atan2; frozen, as exact float64 literals (a ternary on `mirrored` where they depend on it). No user-editable parameter reaches them. If those conventions change in `main.py`, parity fails at every preset, so drift cannot pass unnoticed.
143+
- **Verification.** `y4d-spec check --render --parity` compares the two at the defaults and at every preset; they agree to 0.000000 mm.
144+
- **Which one renders.** `main.py` stays the source the platform renders and the oracle the graph is checked against. A script retires only after parity holds across the nightly sweep (owner decision D5, 2026-10-04).
145+
137146
## Hyperobject Profile
138147

139148
- **Domain:** industrial.
@@ -155,3 +164,9 @@ Unidad de tracción A/B impresa para un pórtico CoreXY con dos bandas apiladas
155164
- **Versión 2.0.0:** cambio incompatible.
156165

157166
Es un diseño original. La guía de armado de la Voron 2.4r2 (GPL-3.0) se cita solo por página (pp. 73–80, 85–87, 95, 125–127); no se copia su geometría. Licencia CERN-OHL-W-2.0.
167+
168+
**Gemelo de grafo.** `ab-drive.graph.json` es un gemelo en grafo de nodos de `main.py` (formato 1.1): los
169+
mismos parámetros, las mismas derivaciones y las mismas operaciones en el mismo orden. Las longitudes y ángulos de los tres brazos (`_bar`) se calculan a partir de las convenciones del script con hypot/atan2 y quedan congelados como literales float64 exactos (un ternario sobre `mirrored`); ningún parámetro editable los alcanza.
170+
`y4d-spec check --render --parity` los compara en los valores por defecto y en cada preset, y
171+
coinciden a 0.000000 mm. `main.py` sigue siendo la fuente que se renderiza y el oráculo
172+
(decisión D5).

‎ab-drive/project.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
},
4444
"scad_file": "main.py",
4545
"cq_file": "main.py",
46+
"graph_file": "ab-drive.graph.json",
4647
"parts": [
4748
"ab_drive"
4849
],

0 commit comments

Comments
 (0)