@tangle-network/agent-bench 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,359 @@
1
+ /**
2
+ * MCAD gold oracles — one OpenSCAD model per task in `mcad-tasks.ts`.
3
+ *
4
+ * These are CALIBRATION artifacts, not exemplar answers: their whole job is to
5
+ * prove the judge's accept direction fires, so they are written plain (no clever
6
+ * parametrics, `$fn=96` on round features) and every one of them has been run
7
+ * through `createMcadBenchAdapter().judge()` on this host. A task is marked
8
+ * `calibrated: true` in `mcad-tasks.ts` only when its gold below scores 1.0.
9
+ *
10
+ * Where a gold deviates from the prompt's literal dimensions, the deviation is
11
+ * named in a comment at the point of the deviation, with the reason. There is one
12
+ * such deviation (task 10's ring tooth-tip diameter — the prompt's own numbers
13
+ * make a coplanar 9-body assembly geometrically impossible; see the comment).
14
+ */
15
+
16
+ /** 01 — 100 x 60 x 20 block, four 8 mm through-holes, 2 mm top-perimeter chamfer. */
17
+ const CALIBRATION_BLOCK = `
18
+ $fn=96;
19
+
20
+ // The chamfer is the hull between the full-size body and a 2 mm inset top plate,
21
+ // so it lands on the top perimeter only and leaves the bore walls square.
22
+ module chamfered_block(l, w, h, c) {
23
+ hull() {
24
+ translate([-l/2, -w/2, 0]) cube([l, w, h - c]);
25
+ translate([-l/2 + c, -w/2 + c, h - c]) cube([l - 2*c, w - 2*c, c]);
26
+ }
27
+ }
28
+
29
+ difference() {
30
+ chamfered_block(100, 60, 20, 2);
31
+ for (x = [-35, 35]) for (y = [-20, 20])
32
+ translate([x, y, -1]) cylinder(h = 22, d = 8);
33
+ }
34
+ `.trim()
35
+
36
+ /** 02 — OD 80 x 10 flange, 30 mm central bore, six 6 mm holes on a 60 mm bolt circle. */
37
+ const CIRCULAR_FLANGE = `
38
+ $fn=96;
39
+
40
+ // Revolved section: a 15..40 mm x 0..10 mm rectangle whose corners are rounded by
41
+ // 1.5 mm (shrink then grow). That carries the required round on both outside
42
+ // circular edges; the same round lands on the bore edges, which the prompt does
43
+ // not ask for and does not forbid.
44
+ difference() {
45
+ rotate_extrude()
46
+ offset(r = 1.5) offset(delta = -1.5)
47
+ polygon([[15, 0], [40, 0], [40, 10], [15, 10]]);
48
+ for (i = [0:5]) rotate([0, 0, i * 60])
49
+ translate([30, 0, -1]) cylinder(h = 12, d = 6);
50
+ }
51
+ `.trim()
52
+
53
+ /** 03 — L bracket: 80 x 50 x 8 base, 80 x 8 x 50 back plate, 4 holes, 2 gussets. */
54
+ const L_BRACKET = `
55
+ $fn=96;
56
+
57
+ // The L is a (Y, Z) section extruded along X. rotate([90,0,90]) maps local
58
+ // (x, y, z) to global (z, x, y), so a 2D point (y, z) extrudes along +X.
59
+ r = 2; // round on the outside corner where the base and the back plate meet
60
+ arc = [for (i = [0:8]) [25 - r + r*cos(-90 + 90*i/8), r + r*sin(-90 + 90*i/8)]];
61
+ section = concat([[-25, 0], [25 - r, 0]], arc, [[25, 58], [17, 58], [17, 8], [-25, 8]]);
62
+
63
+ difference() {
64
+ union() {
65
+ translate([-40, 0, 0]) rotate([90, 0, 90]) linear_extrude(80) polygon(section);
66
+ // gussets: right triangle 30 tall x 30 deep, 8 mm thick in X, at X = +/-20
67
+ for (x = [-20, 20])
68
+ translate([x - 4, 0, 0]) rotate([90, 0, 90]) linear_extrude(8)
69
+ polygon([[17, 8], [-13, 8], [17, 38]]);
70
+ }
71
+ // base plate through-holes; the cutter stops flush at the base top (Z=8) so it
72
+ // cannot notch the gussets that stand on that face
73
+ for (x = [-25, 25]) translate([x, -10, -1]) cylinder(h = 9, d = 6);
74
+ // back plate through-holes, along +Y through the 8 mm thickness
75
+ for (x = [-25, 25]) translate([x, 17, 30]) rotate([-90, 0, 0]) cylinder(h = 9, d = 6);
76
+ }
77
+ `.trim()
78
+
79
+ /** 04 — stepped shaft along X with 1 mm end chamfers and a top keyway. */
80
+ const STEPPED_SHAFT_KEYWAY = `
81
+ $fn=96;
82
+
83
+ // Every section is a cylinder laid along +X; the two end chamfers are 1 mm frusta.
84
+ module seg(x, len, r1, r2) {
85
+ translate([x, 0, 0]) rotate([0, 90, 0]) cylinder(h = len, r1 = r1, r2 = r2);
86
+ }
87
+
88
+ difference() {
89
+ union() {
90
+ seg(0, 1, 9, 10);
91
+ seg(1, 29, 10, 10);
92
+ seg(30, 60, 15, 15);
93
+ seg(90, 29, 10, 10);
94
+ seg(119, 1, 10, 9);
95
+ }
96
+ // keyway: 6 mm wide in Y, 3 mm deep from the Z=15 top, X = 40..80
97
+ translate([40, -3, 12]) cube([40, 6, 5]);
98
+ }
99
+ `.trim()
100
+
101
+ /** 05 — open-top enclosure, 3 mm walls/floor, four standoffs with blind holes. */
102
+ const OPEN_TOP_ELECTRONICS_ENCLOSURE = `
103
+ $fn=96;
104
+
105
+ // Outer box with 2 mm rounds on the four vertical corners.
106
+ module rounded_box(l, w, h, r) {
107
+ hull() for (x = [-l/2 + r, l/2 - r]) for (y = [-w/2 + r, w/2 - r])
108
+ translate([x, y, 0]) cylinder(h = h, r = r);
109
+ }
110
+
111
+ union() {
112
+ difference() {
113
+ rounded_box(100, 70, 30, 2);
114
+ // interior cavity: 3 mm walls, 3 mm floor, open at the top
115
+ translate([-47, -32, 3]) cube([94, 64, 31]);
116
+ }
117
+ // standoffs rising from the inside floor, each with a 3 mm x 8 mm blind hole
118
+ for (x = [-35, 35]) for (y = [-25, 25])
119
+ translate([x, y, 3]) difference() {
120
+ cylinder(h = 12, d = 10);
121
+ translate([0, 0, 4]) cylinder(h = 9, d = 3);
122
+ }
123
+ }
124
+ `.trim()
125
+
126
+ /** 06 — clevis bracket: base plate, two lugs with a 14 mm pin bore, ribs, cutouts. */
127
+ const CLEVIS_BRACKET = `
128
+ $fn=96;
129
+
130
+ // Base plate, 3 mm rounds on the four vertical perimeter corners.
131
+ module base_plate() {
132
+ hull() for (x = [-57, 57]) for (y = [-27, 27])
133
+ translate([x, y, 0]) cylinder(h = 10, r = 3);
134
+ }
135
+
136
+ // One clevis lug: an (X, Z) side profile extruded 18 mm in Y. The straight part
137
+ // runs Z=10..34 and the semicircular cap is r=18 about Z=34, so the lug top is at
138
+ // Z=52 -- exactly 42 mm above the base, as the prompt states.
139
+ module lug() {
140
+ translate([0, 26, 0]) rotate([90, 0, 0]) linear_extrude(18)
141
+ union() {
142
+ translate([-18, 10]) square([36, 24]);
143
+ translate([0, 34]) circle(r = 18);
144
+ }
145
+ }
146
+
147
+ // 2 mm transition flare where the lug foot meets the base plate.
148
+ module lug_foot() {
149
+ hull() {
150
+ translate([-20, 6, 10]) cube([40, 22, 0.01]);
151
+ translate([-18, 8, 12]) cube([36, 18, 0.01]);
152
+ }
153
+ }
154
+
155
+ // Diagonal reinforcing rib, 6 mm thick in Y, lying against the lug's outer face.
156
+ module rib() {
157
+ translate([0, 26, 0]) rotate([90, 0, 0]) linear_extrude(6)
158
+ polygon([[18, 10], [40, 10], [18, 34]]);
159
+ }
160
+
161
+ difference() {
162
+ union() {
163
+ base_plate();
164
+ lug(); mirror([0, 1, 0]) lug();
165
+ lug_foot(); mirror([0, 1, 0]) lug_foot();
166
+ rib(); mirror([0, 1, 0]) rib();
167
+ }
168
+ // clevis pin bore, 14 mm through both lugs along Y at X=0, Z=34
169
+ translate([0, -27, 34]) rotate([-90, 0, 0]) cylinder(h = 54, d = 14);
170
+ // four 7 mm base mounting holes
171
+ for (x = [-45, 45]) for (y = [-20, 20])
172
+ translate([x, y, -1]) cylinder(h = 12, d = 7);
173
+ // triangular lightening cutouts through the base web, corners rounded 3 mm
174
+ for (x = [-31, 31]) translate([x, 0, -1]) linear_extrude(12)
175
+ offset(r = 3) polygon([[6, 0], [-3, 5.2], [-3, -5.2]]);
176
+ }
177
+ `.trim()
178
+
179
+ /** 07 — radial engine cylinder: barrel, 12 fins, base flange, top cap, plug boss. */
180
+ const RADIAL_ENGINE_CYLINDER = `
181
+ $fn=96;
182
+
183
+ // One cooling fin: a 2 mm disc whose outer edge carries the 1 mm round, so the
184
+ // fin outside diameter is 2*(30+1) = 62 mm. The round's own section is coarse
185
+ // ($fn=24): at 96 it puts ~18k triangles on each of the 12 fins and takes the
186
+ // CGAL union past two minutes for a 1 mm cosmetic edge.
187
+ module fin(z) {
188
+ translate([0, 0, z]) union() {
189
+ cylinder(h = 2, r = 30);
190
+ translate([0, 0, 1]) rotate_extrude() translate([30, 0]) circle(r = 1, $fn = 24);
191
+ }
192
+ }
193
+
194
+ difference() {
195
+ union() {
196
+ cylinder(h = 70, r = 18); // barrel
197
+ for (i = [0:11]) fin(10 + 5 * i); // 12 fins, Z = 10..67
198
+ // base flange OD 70 x 8, 1 mm rounds on its outer edges. The profile starts
199
+ // at x=1 (not 0) so the revolve never sees a negative radius; the barrel
200
+ // above fills that 1 mm core.
201
+ rotate_extrude() offset(r = 1) offset(delta = -1)
202
+ polygon([[1, 0], [35, 0], [35, 8], [1, 8]]);
203
+ translate([0, 0, 70]) cylinder(h = 8, r = 22); // top cap
204
+ // spark-plug boss: 12 mm dia, 24 mm long, 35 deg above horizontal, +X, rooted
205
+ // inside the top cap so it fuses with it
206
+ translate([10, 0, 72]) rotate([0, 55, 0]) cylinder(h = 24, d = 12);
207
+ }
208
+ // six 5 mm mounting holes on the 56 mm bolt circle of the base flange
209
+ for (i = [0:5]) rotate([0, 0, i * 60]) translate([28, 0, -1]) cylinder(h = 10, d = 5);
210
+ // 5 mm bore along the boss axis
211
+ translate([10, 0, 72]) rotate([0, 55, 0]) cylinder(h = 25, d = 5);
212
+ }
213
+ `.trim()
214
+
215
+ /** 08 — centrifugal impeller: backplate, hub, 12 backward-curved blades, bore. */
216
+ const CENTRIFUGAL_IMPELLER = `
217
+ $fn=96;
218
+
219
+ // Blade centreline: radius 18 -> 43 while sweeping 45 degrees backward. The blade
220
+ // is the swept 3 mm disc along that path, so its thickness is 3 mm everywhere.
221
+ function bp(t) = [(18 + 25*t) * cos(-45*t), (18 + 25*t) * sin(-45*t)];
222
+
223
+ module blade() {
224
+ for (i = [0:11]) hull() {
225
+ translate(bp(i/12)) circle(d = 3, $fn = 32);
226
+ translate(bp((i+1)/12)) circle(d = 3, $fn = 32);
227
+ }
228
+ }
229
+
230
+ difference() {
231
+ union() {
232
+ // backplate OD 90 x 6 with 1.5 mm rounds on both outer circular edges
233
+ rotate_extrude() offset(r = 1.5) offset(delta = -1.5)
234
+ polygon([[1, 0], [45, 0], [45, 6], [1, 6]]);
235
+ translate([0, 0, 6]) cylinder(h = 22, r = 13); // hub
236
+ // 12 blades, 16 mm tall above the backplate. The prompt's own radii leave a
237
+ // 5 mm gap between the blade roots (r=18) and the hub (r=13), so the stated
238
+ // blade-to-hub root fillet has nothing to fillet and is omitted.
239
+ for (i = [0:11]) rotate([0, 0, i * 30])
240
+ translate([0, 0, 6]) linear_extrude(16) blade();
241
+ }
242
+ translate([0, 0, -1]) cylinder(h = 30, d = 8); // through-bore
243
+ }
244
+ `.trim()
245
+
246
+ /** 09 — spiral staircase: column, 20 helical treads, helical handrail, balusters. */
247
+ const SPIRAL_STAIRCASE = `
248
+ $fn=96;
249
+
250
+ n = 20; // treads
251
+ z0 = 4; // first tread bottom
252
+ rise = 6; // Z step per tread
253
+ turn = 18; // degrees per tread
254
+ rail_r = 66; rail_z0 = 14; rail_z1 = 130;
255
+
256
+ // Handrail height at a given plan angle, measured counter-clockwise from +X.
257
+ function rail_z(a) = rail_z0 + (rail_z1 - rail_z0) * a / 360;
258
+
259
+ // A wedge tread: annulus sector from ri to ro over "ang" degrees.
260
+ module tread(ri, ro, ang, h) {
261
+ m = 24;
262
+ linear_extrude(h) polygon(concat(
263
+ [for (i = [0:m]) [ri * cos(ang*i/m), ri * sin(ang*i/m)]],
264
+ [for (i = [m:-1:0]) [ro * cos(ang*i/m), ro * sin(ang*i/m)]]));
265
+ }
266
+
267
+ union() {
268
+ cylinder(h = 140, r = 7); // central column
269
+ cylinder(h = 5, r = 45); // base disk, Z = 0..5 (overlaps the first tread)
270
+ for (k = [0:n-1]) {
271
+ rotate([0, 0, k * turn]) translate([0, 0, z0 + k * rise]) tread(10, 62, 24, 4);
272
+ // baluster at the tread's outer end, mid-width, reaching the handrail centre
273
+ rotate([0, 0, k * turn + 12]) translate([63, 0, z0 + k * rise])
274
+ cylinder(h = rail_z(k * turn + 12) - (z0 + k * rise), d = 3);
275
+ }
276
+ // helical handrail: a 5 mm circle at radius 66 swept one counter-clockwise turn
277
+ // (OpenSCAD's positive twist is clockwise) while rising Z=14 -> 130
278
+ translate([0, 0, rail_z0])
279
+ linear_extrude(height = rail_z1 - rail_z0, twist = -360, slices = 96)
280
+ translate([rail_r, 0]) circle(d = 5, $fn = 48);
281
+ }
282
+ `.trim()
283
+
284
+ /** 10 — planetary gear stage: 9 separate bodies (sun, 3 planets, ring, carrier, 3 pins). */
285
+ const PLANETARY_GEAR_STAGE = `
286
+ $fn=96;
287
+
288
+ // Straight-sided trapezoidal teeth: each tooth is a four-point polygon between the
289
+ // root and tip circles, unioned onto (external) or subtracted from (internal) the
290
+ // gear blank. Half-angles are given at the root and the tip.
291
+ module ext_gear(n, r_root, r_tip, ha_root, ha_tip, phase) {
292
+ union() {
293
+ circle(r = r_root);
294
+ for (i = [0:n-1]) rotate(phase + 360*i/n)
295
+ polygon([[r_root*cos(-ha_root), r_root*sin(-ha_root)],
296
+ [r_tip*cos(-ha_tip), r_tip*sin(-ha_tip)],
297
+ [r_tip*cos(ha_tip), r_tip*sin(ha_tip)],
298
+ [r_root*cos(ha_root), r_root*sin(ha_root)]]);
299
+ }
300
+ }
301
+
302
+ module int_gear(n, r_tip, r_root, r_out, ha_tip, ha_root, phase) {
303
+ difference() {
304
+ circle(r = r_out);
305
+ difference() {
306
+ circle(r = r_root);
307
+ for (i = [0:n-1]) rotate(phase + 360*i/n)
308
+ polygon([[r_root*cos(-ha_root), r_root*sin(-ha_root)],
309
+ [r_tip*cos(-ha_tip), r_tip*sin(-ha_tip)],
310
+ [r_tip*cos(ha_tip), r_tip*sin(ha_tip)],
311
+ [r_root*cos(ha_root), r_root*sin(ha_root)]]);
312
+ }
313
+ }
314
+ }
315
+
316
+ gz = 16; // gear underside; the pins (Z=0..14) stop 2 mm short of it
317
+ gt = 8; // gear thickness
318
+
319
+ // sun: 24 teeth, root d42, OD 54. Phase 7.5 puts a tooth SPACE on each of the
320
+ // three planet directions (0/120/240 deg), which is the only phase where the sun
321
+ // tip circle clears the planet root circle at all.
322
+ translate([0, 0, gz]) linear_extrude(gt) difference() {
323
+ ext_gear(24, 21, 27, 4, 0.2, 7.5);
324
+ circle(d = 10);
325
+ }
326
+
327
+ // three planets: 18 teeth, root d31, OD 41, centres on r=42 every 120 deg. Phase 0
328
+ // puts a tooth on the sun side (local 180) and on the ring side (local 0).
329
+ for (i = [0:2]) rotate(120*i) translate([42, 0, gz])
330
+ linear_extrude(gt) ext_gear(18, 15.5, 20.5, 5, 2, 0);
331
+
332
+ // ring: 60 internal teeth, OD 140, internal root d126.
333
+ // DEVIATION: internal tooth-tip diameter is 116, not the prompt's 114. At 114 the
334
+ // ring tooth tips sit at r=57 while the planet ROOT circles reach r=42+15.5=57.5,
335
+ // so every planet overlaps every ring tooth and the required nine separate bodies
336
+ // cannot exist at any phase or rotation. 116 restores 0.5 mm of tip clearance and
337
+ // leaves every other stated diameter untouched.
338
+ translate([0, 0, gz]) linear_extrude(gt) int_gear(60, 58, 63, 70, 1.0, 1.5, 3);
339
+
340
+ // carrier plate, Z = -5..-1
341
+ translate([0, 0, -5]) cylinder(h = 4, r = 52.5);
342
+
343
+ // three planet pins, Z = 0..14, clear of both the carrier and the gears
344
+ for (i = [0:2]) rotate(120*i) translate([42, 0, 0]) cylinder(h = 14, d = 6);
345
+ `.trim()
346
+
347
+ /** Gold OpenSCAD source per task id. */
348
+ export const MCAD_GOLDS: Record<string, string> = {
349
+ 'calibration-block': CALIBRATION_BLOCK,
350
+ 'circular-flange': CIRCULAR_FLANGE,
351
+ 'l-bracket': L_BRACKET,
352
+ 'stepped-shaft-keyway': STEPPED_SHAFT_KEYWAY,
353
+ 'open-top-electronics-enclosure': OPEN_TOP_ELECTRONICS_ENCLOSURE,
354
+ 'clevis-bracket-lightening-cutouts': CLEVIS_BRACKET,
355
+ 'radial-engine-cylinder': RADIAL_ENGINE_CYLINDER,
356
+ 'centrifugal-impeller': CENTRIFUGAL_IMPELLER,
357
+ 'spiral-staircase': SPIRAL_STAIRCASE,
358
+ 'planetary-gear-stage': PLANETARY_GEAR_STAGE,
359
+ }