@vcad/mcp 0.9.4-main.27 → 0.9.4-main.29

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.
package/README.md CHANGED
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
6
6
  { "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
7
7
  ```
8
8
 
9
- Built from cfda229f9a317556e3c9f64a43700549cd5265ef at 2026-07-25T23:24:45.817Z. Source: https://github.com/ecto/vcad
9
+ Built from d80a65e336d1c8b8ebf75dd1bebc553b41f7ca93 at 2026-07-26T00:45:54.095Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -113,6 +113,7 @@ __export(vcad_kernel_wasm_exports, {
113
113
  documentToLoonChecked: () => documentToLoonChecked,
114
114
  drawingSheetToPdf: () => drawingSheetToPdf,
115
115
  ecadAirgapFluxDensity: () => ecadAirgapFluxDensity,
116
+ ecadAirgapSolve: () => ecadAirgapSolve,
116
117
  ecadBuildReceipt: () => ecadBuildReceipt,
117
118
  ecadBuiltinSymbols: () => ecadBuiltinSymbols,
118
119
  ecadCheckDrc: () => ecadCheckDrc,
@@ -1142,6 +1143,15 @@ function ecadAirgapFluxDensity(spec_json) {
1142
1143
  }
1143
1144
  return ret[0];
1144
1145
  }
1146
+ function ecadAirgapSolve(spec_json) {
1147
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1148
+ const len0 = WASM_VECTOR_LEN;
1149
+ const ret = wasm.ecadAirgapSolve(ptr0, len0);
1150
+ if (ret[2]) {
1151
+ throw takeFromExternrefTable0(ret[1]);
1152
+ }
1153
+ return takeFromExternrefTable0(ret[0]);
1154
+ }
1145
1155
  function ecadBuildReceipt(pcb_json) {
1146
1156
  const ptr0 = passStringToWasm0(pcb_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1147
1157
  const len0 = WASM_VECTOR_LEN;
@@ -10367,6 +10377,20 @@ async function airgapFluxDensity(spec) {
10367
10377
  return null;
10368
10378
  }
10369
10379
  }
10380
+ async function airgapSolve(spec) {
10381
+ const wasm3 = await loadEcadWasm();
10382
+ if (!wasm3)
10383
+ return null;
10384
+ try {
10385
+ const fn = wasm3.ecadAirgapSolve;
10386
+ if (typeof fn !== "function")
10387
+ return null;
10388
+ return fn(JSON.stringify(spec));
10389
+ } catch (e) {
10390
+ console.warn("[ECAD] airgapSolve failed:", e);
10391
+ return null;
10392
+ }
10393
+ }
10370
10394
  async function generateNetlist(sheet) {
10371
10395
  const wasm3 = await loadEcadWasm();
10372
10396
  if (!wasm3)
@@ -11229,6 +11253,7 @@ __export(dist_exports, {
11229
11253
  PhysicsEnv: () => PhysicsEnv,
11230
11254
  SolidCache: () => SolidCache,
11231
11255
  airgapFluxDensity: () => airgapFluxDensity,
11256
+ airgapSolve: () => airgapSolve,
11232
11257
  applyForwardKinematics: () => applyForwardKinematics,
11233
11258
  buildMoleculeReceipt: () => buildReceipt2,
11234
11259
  buildPartDocument: () => buildPartDocument,
@@ -40898,6 +40923,26 @@ var init_CHANGELOG = __esm({
40898
40923
  "sheet_metal_create"
40899
40924
  ]
40900
40925
  },
40926
+ {
40927
+ id: "2026-07-25-shell-and-booleans-on-filleted-solids",
40928
+ version: "0.9.4",
40929
+ date: "2026-07-25",
40930
+ category: "fix",
40931
+ title: "Shelling and cutting a filleted solid no longer leaks",
40932
+ summary: "Hollowing a filleted part scattered its cavity and a pocket cut collapsed the neighboring blends flat; both are now exact and watertight, and the kernel is reproducible run to run.",
40933
+ features: [
40934
+ "kernel",
40935
+ "fillet",
40936
+ "shell",
40937
+ "booleans",
40938
+ "tessellation"
40939
+ ],
40940
+ mcpTools: [
40941
+ "create_cad_loon",
40942
+ "inspect_cad",
40943
+ "export_cad"
40944
+ ]
40945
+ },
40901
40946
  {
40902
40947
  id: "2026-07-25-router-window-repair-commit-parity",
40903
40948
  version: "0.9.4",
@@ -41169,6 +41214,57 @@ var init_CHANGELOG = __esm({
41169
41214
  "quote_manufacturing"
41170
41215
  ]
41171
41216
  },
41217
+ {
41218
+ id: "2026-07-25-em-saturated-picard",
41219
+ version: "0.9.4",
41220
+ date: "2026-07-25",
41221
+ category: "fix",
41222
+ title: "simulate_em solves strongly saturating magnetostatics",
41223
+ summary: "Saturated motor cross-sections no longer fail with an unfixable 'Picard not converged': the nonlinear loop's cap, tolerance and relaxation are now caller-settable, and the default cap was raised.",
41224
+ features: [
41225
+ "em",
41226
+ "magnetostatics",
41227
+ "motors"
41228
+ ],
41229
+ mcpTools: [
41230
+ "simulate_em"
41231
+ ]
41232
+ },
41233
+ {
41234
+ id: "2026-07-25-curved-trim-seams-weld",
41235
+ version: "0.9.4",
41236
+ date: "2026-07-25",
41237
+ category: "fix",
41238
+ title: "Bores and cut curved faces close their seams",
41239
+ summary: "A through-hole left the shell open along the bore wall \u2014 visible as hairline cracks and unusable for 3D print or STL export; bores, holed plates and cut fillet corners now come out watertight.",
41240
+ features: [
41241
+ "kernel",
41242
+ "booleans",
41243
+ "tessellation",
41244
+ "export"
41245
+ ],
41246
+ mcpTools: [
41247
+ "create_cad_loon",
41248
+ "inspect_cad",
41249
+ "export_cad"
41250
+ ]
41251
+ },
41252
+ {
41253
+ id: "2026-07-25-calc-motor-tooth-saturation",
41254
+ version: "0.9.4",
41255
+ date: "2026-07-25",
41256
+ category: "fix",
41257
+ title: "calc_motor sees saturating stator teeth",
41258
+ summary: "calc_motor's MEC model treated iron as linear and silently over-predicted Kt on machines whose teeth saturate; it now reports tooth flux, warns past the knee, and can solve the saturating network.",
41259
+ features: [
41260
+ "motor",
41261
+ "magnetics",
41262
+ "simulation"
41263
+ ],
41264
+ mcpTools: [
41265
+ "calc_motor"
41266
+ ]
41267
+ },
41172
41268
  {
41173
41269
  id: "2026-07-24-visionos-app",
41174
41270
  version: "0.9.4",
@@ -64125,6 +64221,7 @@ async function calcMotor(args) {
64125
64221
  let bGap = num2(args.airgap_flux_tesla);
64126
64222
  let bGapSource = "supplied";
64127
64223
  let magnetSpec;
64224
+ let solution = null;
64128
64225
  let fringing;
64129
64226
  if (!Number.isFinite(bGap)) {
64130
64227
  const m = args.magnet ?? {};
@@ -64138,15 +64235,55 @@ async function calcMotor(args) {
64138
64235
  gapAreaMm2: mnum(m.gap_area_mm2, 1),
64139
64236
  ironMuRel: typeof m.iron_mu_rel === "number" ? m.iron_mu_rel : null,
64140
64237
  ironPathMm: mnum(m.iron_path_mm, 0),
64141
- ironAreaMm2: mnum(m.iron_area_mm2, 1)
64142
- };
64143
- const computed = await airgapFluxDensity(magnetSpec);
64144
- if (computed == null) {
64145
- return fail2(
64146
- "air-gap flux is required: pass airgap_flux_tesla, or `magnet` params (ECAD WASM must be available to compute B_gap)."
64147
- );
64238
+ ironAreaMm2: mnum(m.iron_area_mm2, 1),
64239
+ ironJsT: typeof m.iron_js_t === "number" ? m.iron_js_t : null
64240
+ };
64241
+ if (m.slots !== void 0) {
64242
+ const slots = num2(m.slots);
64243
+ if (!(slots > 0)) return fail2("magnet.slots must be > 0");
64244
+ const meanR = Number.isFinite(num2(m.mean_radius_mm)) ? num2(m.mean_radius_mm) : (innerR + outerR) / 2;
64245
+ const pitch = 2 * Math.PI * meanR / slots;
64246
+ let toothW = num2(m.tooth_width_mm);
64247
+ if (!Number.isFinite(toothW)) {
64248
+ const frac = num2(m.tooth_fraction);
64249
+ if (!Number.isFinite(frac)) {
64250
+ return fail2("magnet.slots requires magnet.tooth_width_mm or magnet.tooth_fraction");
64251
+ }
64252
+ if (!(frac > 0 && frac <= 1)) return fail2("magnet.tooth_fraction must be in (0, 1]");
64253
+ toothW = frac * pitch;
64254
+ }
64255
+ if (!(toothW > 0)) return fail2("magnet.tooth_width_mm must be > 0");
64256
+ magnetSpec.teeth = {
64257
+ slots,
64258
+ toothWidthMm: toothW,
64259
+ meanRadiusMm: meanR,
64260
+ toothPathMm: mnum(m.tooth_path_mm, 0)
64261
+ };
64262
+ }
64263
+ solution = await airgapSolve(magnetSpec);
64264
+ if (solution == null) {
64265
+ const computed = await airgapFluxDensity(magnetSpec);
64266
+ if (computed == null) {
64267
+ return fail2(
64268
+ "air-gap flux is required: pass airgap_flux_tesla, or `magnet` params (ECAD WASM must be available to compute B_gap)."
64269
+ );
64270
+ }
64271
+ const t2 = magnetSpec.teeth;
64272
+ const k = t2 ? Math.max(1, 2 * Math.PI * t2.meanRadiusMm / t2.slots / t2.toothWidthMm) : null;
64273
+ solution = {
64274
+ bGapTesla: computed,
64275
+ bToothTesla: k == null ? null : computed * k,
64276
+ bIronTesla: null,
64277
+ toothConcentration: k,
64278
+ nonlinear: false,
64279
+ iterations: 0,
64280
+ converged: true,
64281
+ warnings: k != null && computed * k > SILICON_STEEL_KNEE_T ? [
64282
+ `tooth flux density ${(computed * k).toFixed(2)} T exceeds the ${SILICON_STEEL_KNEE_T.toFixed(2)} T knee \u2014 the LINEAR iron model over-predicts B_gap and Kt here; pass magnet.iron_js_t to solve the saturating network`
64283
+ ] : []
64284
+ };
64148
64285
  }
64149
- bGap = computed;
64286
+ bGap = solution.bGapTesla;
64150
64287
  bGapSource = "computed";
64151
64288
  if (m.pole_width_mm !== void 0) {
64152
64289
  const poleW = num2(m.pole_width_mm);
@@ -64218,6 +64355,24 @@ async function calcMotor(args) {
64218
64355
  })
64219
64356
  );
64220
64357
  }
64358
+ if (solution?.bToothTesla != null && magnetSpec.teeth) {
64359
+ claims.push(
64360
+ emClaim(
64361
+ "tooth_flux_density",
64362
+ r4(solution.bToothTesla),
64363
+ "T",
64364
+ solution.nonlinear ? "mec-saturating-iron" : "mec-tooth-concentration",
64365
+ {
64366
+ ...mecInputs,
64367
+ slots: magnetSpec.teeth.slots,
64368
+ tooth_width_mm: r4(magnetSpec.teeth.toothWidthMm),
64369
+ mean_radius_mm: r4(magnetSpec.teeth.meanRadiusMm),
64370
+ tooth_concentration: r4(solution.toothConcentration ?? 1),
64371
+ ...magnetSpec.ironJsT != null ? { iron_js_t: magnetSpec.ironJsT } : {}
64372
+ }
64373
+ )
64374
+ );
64375
+ }
64221
64376
  }
64222
64377
  return {
64223
64378
  content: [
@@ -64233,6 +64388,16 @@ async function calcMotor(args) {
64233
64388
  fringing_derate: r4(fringing.derate),
64234
64389
  fringing_note: "Carter-like first-order derate: B under the pole drops by w/(w+2g) as flux fringes ~one gap length past each pole edge. Kt/Ke and the curve use the DERATED flux. Honest for pole width \u2273 2\xD7 gap; below that treat it as a lower bound."
64235
64390
  } : {},
64391
+ ...solution?.bToothTesla != null ? {
64392
+ tooth_flux_tesla: r4(solution.bToothTesla),
64393
+ tooth_concentration: r4(solution.toothConcentration ?? 1),
64394
+ // Fringing redistributes flux under the pole; it does not remove
64395
+ // it, so the tooth carries the RAW gap flux either way.
64396
+ tooth_note: "B_tooth = raw B_gap \xB7 (tooth pitch / tooth width) \u2014 the gap flux of a whole tooth pitch funnels into one tooth body. Compare against your steel's knee (~1.5-1.7 T for M19-class silicon steel)."
64397
+ } : {},
64398
+ ...solution?.bIronTesla != null ? { yoke_flux_tesla: r4(solution.bIronTesla) } : {},
64399
+ ...solution?.nonlinear ? { iron_model: "saturating (arctangent B-H)" } : {},
64400
+ ...solution && solution.warnings.length > 0 ? { warnings: solution.warnings } : {},
64236
64401
  winding_factor: windingFactor,
64237
64402
  kt_nm_per_a: r4(perf.ktNmPerA),
64238
64403
  ke_v_s_per_rad: r4(perf.keVSPerRad),
@@ -64243,7 +64408,7 @@ async function calcMotor(args) {
64243
64408
  speed_rad_s: r4(p.speedRadS),
64244
64409
  torque_nm: r4(p.torqueNm)
64245
64410
  })),
64246
- note: fringing ? "First-order steady-state estimate (no slotting/saturation/losses; pole-edge fringing derated via magnet.pole_width_mm)." : "First-order steady-state estimate (no slotting/fringing/saturation/losses; pass magnet.pole_width_mm to derate for fringing).",
64411
+ note: "First-order steady-state estimate (no slotting, no losses)" + (fringing ? "; pole-edge fringing derated via magnet.pole_width_mm" : "; pass magnet.pole_width_mm to derate for fringing") + (solution?.nonlinear ? "; iron solved with its saturating B-H law" : "; iron is LINEAR, so Kt is OPTIMISTIC on a machine whose teeth saturate \u2014 " + (solution?.bToothTesla != null ? "see tooth_flux_tesla, and pass magnet.iron_js_t to solve the saturating network" : "pass magnet.slots + tooth_width_mm to see the tooth field")) + ".",
64247
64412
  claims
64248
64413
  })
64249
64414
  }
@@ -65715,7 +65880,7 @@ async function fixDrc(args) {
65715
65880
  };
65716
65881
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
65717
65882
  }
65718
- var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, fabPrepSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d, round2, sumSq, round33, vec2Schema, setBoardOutlineSchema, FOOTPRINT_FAMILIES, listFootprintsSchema, searchFootprintsSchema, getPadPositionsSchema, getFootprintSchema, ROTATION_CONVENTION, describePcbSchema, DESCRIBE_NET_NAME_CAP, addTraceSchema, addViaSchema, OZ_TO_MM, setStackupSchema, setPlacementSchema, addZoneSchema, deleteElementProps, deleteZoneSchema, deleteTraceSchema, deleteViaSchema, COPPER_KINDS, GET_COPPER_CAP, getCopperSchema, TIE_POSITION_TOL, addNetTieSchema, deleteNetTieSchema, undoSchema, setDesignRulesSchema, sizeTraceForCurrentSchema, addViaArraySchema, MAX_VIA_ARRAY, addMotorWindingSchema, calcMotorSchema, sig6, BEARING_FRICTION_MNM, checkSelfStartSchema, solidFromBoardSchema, FR4_DENSITY_KG_M3, COPPER_DENSITY_KG_M3, DEFAULT_COPPER_THICKNESS_MM, loopArea, MOUNTING_FP_RE, KEEPOUT_ECHO_CAP, INLINE_DOC_ECHO_CAP, searchElectronicPartsSchema, resolvePartSchema, findAlternativesSchema, verifySubstitutionSchema, buildReceiptSchema, verifyReceiptSchema, FIX_EPS, FIX_DRC_NEVER, fixDrcSchema, toolDefs20;
65883
+ var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, fabPrepSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d, round2, sumSq, round33, vec2Schema, setBoardOutlineSchema, FOOTPRINT_FAMILIES, listFootprintsSchema, searchFootprintsSchema, getPadPositionsSchema, getFootprintSchema, ROTATION_CONVENTION, describePcbSchema, DESCRIBE_NET_NAME_CAP, addTraceSchema, addViaSchema, OZ_TO_MM, setStackupSchema, setPlacementSchema, addZoneSchema, deleteElementProps, deleteZoneSchema, deleteTraceSchema, deleteViaSchema, COPPER_KINDS, GET_COPPER_CAP, getCopperSchema, TIE_POSITION_TOL, addNetTieSchema, deleteNetTieSchema, undoSchema, setDesignRulesSchema, sizeTraceForCurrentSchema, addViaArraySchema, MAX_VIA_ARRAY, addMotorWindingSchema, calcMotorSchema, SILICON_STEEL_KNEE_T, sig6, BEARING_FRICTION_MNM, checkSelfStartSchema, solidFromBoardSchema, FR4_DENSITY_KG_M3, COPPER_DENSITY_KG_M3, DEFAULT_COPPER_THICKNESS_MM, loopArea, MOUNTING_FP_RE, KEEPOUT_ECHO_CAP, INLINE_DOC_ECHO_CAP, searchElectronicPartsSchema, resolvePartSchema, findAlternativesSchema, verifySubstitutionSchema, buildReceiptSchema, verifyReceiptSchema, FIX_EPS, FIX_DRC_NEVER, fixDrcSchema, toolDefs20;
65719
65884
  var init_ecad2 = __esm({
65720
65885
  "src/tools/ecad.ts"() {
65721
65886
  "use strict";
@@ -67127,7 +67292,7 @@ var init_ecad2 = __esm({
67127
67292
  },
67128
67293
  magnet: {
67129
67294
  type: "object",
67130
- description: "PM mode: optional magnet/geometry to compute B_gap when airgap_flux_tesla is omitted (NdFeB defaults). Fields: remanence_tesla, magnet_thickness_mm, airgap_mm, recoil_mu_rel, magnet_area_mm2, gap_area_mm2, iron_mu_rel. Add pole_width_mm (magnet pole face width across the fringing direction) to also apply the first-order Carter-like fringing derate w/(w+2g) \u2014 the tool then reports raw AND derated B and uses the derated value for Kt."
67295
+ description: "PM mode: optional magnet/geometry to compute B_gap when airgap_flux_tesla is omitted (NdFeB defaults). Fields: remanence_tesla, magnet_thickness_mm, airgap_mm, recoil_mu_rel, magnet_area_mm2, gap_area_mm2, iron_mu_rel. Add pole_width_mm (magnet pole face width across the fringing direction) to also apply the first-order Carter-like fringing derate w/(w+2g) \u2014 the tool then reports raw AND derated B and uses the derated value for Kt. SATURATION: the MEC iron is LINEAR unless you say otherwise, so it over-predicts Kt on a machine whose teeth saturate. Pass slots + (tooth_width_mm or tooth_fraction) to make the teeth visible \u2014 the tool then reports tooth_flux_density (B_gap \xB7 pitch/width, routinely 2x B_gap) and warns when it passes the ~1.5 T silicon-steel knee. Add iron_js_t (saturation polarization, silicon steel \u2248 2.0 T) with iron_mu_rel to actually SOLVE the saturating network instead of merely warning; tooth_path_mm puts the teeth in the reluctance loop."
67131
67296
  },
67132
67297
  phase_current_a: { type: "number", description: "Induction mode (required): phase current, A RMS (balanced 3-phase drive)." },
67133
67298
  electrical_freq_hz: { type: "number", description: "Induction mode (required): electrical drive frequency, Hz." },
@@ -67146,6 +67311,7 @@ var init_ecad2 = __esm({
67146
67311
  },
67147
67312
  required: ["pole_pairs", "turns_per_phase"]
67148
67313
  };
67314
+ SILICON_STEEL_KNEE_T = 1.5;
67149
67315
  sig6 = (v) => v === 0 || !Number.isFinite(v) ? v : Number(v.toPrecision(6));
67150
67316
  BEARING_FRICTION_MNM = {
67151
67317
  "608-2RS": {
@@ -71400,6 +71566,22 @@ var init_em = __esm({
71400
71566
  type: "number",
71401
71567
  description: "SOR sweep cap. Default 200000."
71402
71568
  },
71569
+ picard_max_iters: {
71570
+ type: "number",
71571
+ description: "Saturable iron only: cap on the NONLINEAR outer loop over the B-H law. Default 300. Separate from max_sweeps, which caps the inner SOR solve and does nothing for a non-converging material state. A deeply saturated motor section contracts steadily but needs 150-250 iterations; raise this when the error says the residual is still falling."
71572
+ },
71573
+ picard_tol: {
71574
+ type: "number",
71575
+ description: "Saturable iron only: nonlinear convergence tolerance on the largest relative reluctivity update. Default 1e-4. Raise it to accept a looser material state deliberately."
71576
+ },
71577
+ picard_relax: {
71578
+ type: "number",
71579
+ description: "Saturable iron only: under-relaxation of the nonlinear loop, in (0, 1]. Default 0.7. Lower it ONLY when the error reports the residual has stopped falling (a limit cycle); when the residual is still falling, lowering this makes convergence strictly slower \u2014 raise picard_max_iters instead."
71580
+ },
71581
+ picard_adaptive: {
71582
+ type: "boolean",
71583
+ description: "Saturable iron only: back the relaxation off automatically when the nonlinear residual makes no progress over a 10-iteration window. Default FALSE \u2014 on a healthy saturated solve it reacts to ordinary residual jitter and costs ~1.7x the iterations. Turn it on for a device that is demonstrably oscillating."
71584
+ },
71403
71585
  drive_coil: {
71404
71586
  type: "number",
71405
71587
  description: "Axisym: coil index the inductance claim is priced for. Default 0."
@@ -78869,8 +79051,8 @@ var init_server3 = __esm({
78869
79051
  init_order_feed();
78870
79052
  init_animate();
78871
79053
  PKG_VERSION = (() => {
78872
- if ("0.9.4-main.27") {
78873
- return "0.9.4-main.27";
79054
+ if ("0.9.4-main.29") {
79055
+ return "0.9.4-main.29";
78874
79056
  }
78875
79057
  try {
78876
79058
  const req = createRequire2(import.meta.url);
@@ -78879,8 +79061,8 @@ var init_server3 = __esm({
78879
79061
  return "0.0.0";
78880
79062
  }
78881
79063
  })();
78882
- BUILD_SHA = "cfda229f9a317556e3c9f64a43700549cd5265ef";
78883
- BUILD_TIME = "2026-07-25T23:24:45.817Z";
79064
+ BUILD_SHA = "d80a65e336d1c8b8ebf75dd1bebc553b41f7ca93";
79065
+ BUILD_TIME = "2026-07-26T00:45:54.095Z";
78884
79066
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
78885
79067
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
78886
79068
  INSTANCE_ID = randomUUID6().slice(0, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.27",
3
+ "version": "0.9.4-main.29",
4
4
  "description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "homepage": "https://vcad.io",
21
21
  "vcadBuild": {
22
- "sha": "cfda229f9a317556e3c9f64a43700549cd5265ef",
23
- "builtAt": "2026-07-25T23:24:45.817Z"
22
+ "sha": "d80a65e336d1c8b8ebf75dd1bebc553b41f7ca93",
23
+ "builtAt": "2026-07-26T00:45:54.095Z"
24
24
  }
25
25
  }
Binary file