@tscircuit/fanout-solver 0.0.36 → 0.0.38

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/lib/types.ts CHANGED
@@ -124,9 +124,14 @@ export interface FanoutBusSpec extends SimpleRouteBus {
124
124
  * connection's electrical endpoint in SimpleRouteJson.
125
125
  *
126
126
  * A caller coordinating two fanouts can pass the paired fanout's selected
127
- * exit here so both sides aim toward the same track.
127
+ * exit here so both sides aim toward the same track. When every target also
128
+ * includes a layer, the solver may use the bus's other allowed layer as an
129
+ * internal crossover channel, then return every exit to one common bus layer
130
+ * in the supplied target order.
128
131
  */
129
- connectionExitTargets?: Readonly<Record<string, Point2D>>
132
+ connectionExitTargets?: Readonly<
133
+ Record<string, Point2D & { readonly layer?: string }>
134
+ >
130
135
  /**
131
136
  * Defaults to `{ type: "boundary" }`.
132
137
  *
@@ -245,6 +250,8 @@ export interface FanoutValidationIssue {
245
250
  | "different-net-trace-clearance"
246
251
  | "different-net-trace-via-clearance"
247
252
  | "different-net-via-clearance"
253
+ | "plan-length-mismatch"
254
+ | "bus-length-skew"
248
255
  message: string
249
256
  connectionName?: string
250
257
  otherConnectionName?: string
@@ -279,11 +286,15 @@ export interface PreparedConnection {
279
286
  sourceObstacle: Obstacle
280
287
  targetPoint: ConnectionPoint
281
288
  /** Preferred downstream point used to choose the boundary exit track. */
282
- exitTargetPoint?: Point2D
289
+ exitTargetPoint?: Point2D & { readonly layer?: string }
290
+ /** Whether the caller supplied the layered handoff metadata needed to coordinate winding. */
291
+ hasExplicitLayeredExitTarget?: boolean
283
292
  }
284
293
 
285
294
  export interface PreparedBus {
286
295
  busId: string
296
+ /** Maximum permitted routed copper length difference in millimeters. */
297
+ maxLengthSkew?: number
287
298
  direction: FanoutDirection
288
299
  preferredExit?: FanoutBorderTarget
289
300
  /** Explicit final boundary edge. Omitted for legacy direction-based exits. */
@@ -292,6 +303,8 @@ export interface PreparedBus {
292
303
  cornerBandConnectionCount?: number
293
304
  /** Layers to which this bus is allowed to escape. */
294
305
  allowedLayers?: readonly string[]
306
+ /** Bus layers that also survive the solver-wide escape-layer restriction. */
307
+ routableEscapeLayers?: readonly string[]
295
308
  termination: FanoutBusTermination
296
309
  connections: PreparedConnection[]
297
310
  componentId: string
@@ -341,6 +354,8 @@ export interface FanoutRoutePlan {
341
354
  trace: SimplifiedPcbTrace
342
355
  segments: RoutedSegment[]
343
356
  via?: RoutedVia
357
+ /** Additional layer transitions used by an explicit winding channel. */
358
+ additionalVias?: RoutedVia[]
344
359
  /** Optional capacitor-side dogbone reserved and emitted with a plane escape. */
345
360
  planeEndpointTrace?: SimplifiedPcbTrace
346
361
  planeEndpointSegments?: RoutedSegment[]
@@ -44,9 +44,11 @@ function getPlanSegments(plan: FanoutRoutePlan): RoutedSegment[] {
44
44
  }
45
45
 
46
46
  function getPlanVias(plan: FanoutRoutePlan) {
47
- return [plan.via, plan.planeEndpointVia].filter(
48
- (via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via),
49
- )
47
+ return [
48
+ plan.via,
49
+ ...(plan.additionalVias ?? []),
50
+ plan.planeEndpointVia,
51
+ ].filter((via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via))
50
52
  }
51
53
 
52
54
  function connectionPointsMatch(
@@ -341,13 +343,13 @@ function validatePlanStructure(params: {
341
343
  plan,
342
344
  )
343
345
  }
344
- if (
345
- previous.layer !== current.layer &&
346
- (!plan.via ||
347
- !pointsMatch(previous.end, plan.via.center) ||
348
- !plan.via.spanLayers.includes(previous.layer) ||
349
- !plan.via.spanLayers.includes(current.layer))
350
- ) {
346
+ const transitionVia = getPlanVias(plan).find(
347
+ (via) =>
348
+ pointsMatch(previous.end, via.center) &&
349
+ via.spanLayers.includes(previous.layer) &&
350
+ via.spanLayers.includes(current.layer),
351
+ )
352
+ if (previous.layer !== current.layer && !transitionVia) {
351
353
  addIssue(
352
354
  issues,
353
355
  "disconnected-trace",
@@ -358,6 +360,19 @@ function validatePlanStructure(params: {
358
360
  }
359
361
  }
360
362
 
363
+ const measuredLength = [
364
+ ...plan.segments,
365
+ ...(plan.planeEndpointSegments ?? []),
366
+ ].reduce((total, segment) => total + distance(segment.start, segment.end), 0)
367
+ if (Math.abs(measuredLength - plan.length) > 1e-6) {
368
+ addIssue(
369
+ issues,
370
+ "plan-length-mismatch",
371
+ `Plan ${plan.connectionName} declares ${plan.length.toFixed(6)}mm but contains ${measuredLength.toFixed(6)}mm of routed copper`,
372
+ plan,
373
+ )
374
+ }
375
+
361
376
  const traceSegments = extractTraceSegments({
362
377
  trace: plan.trace,
363
378
  plan,
@@ -919,6 +934,21 @@ export function validateFanoutSolution(params: {
919
934
  connectionPlans.push(plan)
920
935
  plansByConnection.set(plan.connectionName, connectionPlans)
921
936
  }
937
+ for (const bus of preparedBuses) {
938
+ if (bus.maxLengthSkew === undefined) continue
939
+ const busPlans = plans.filter((plan) => plan.busId === bus.busId)
940
+ if (busPlans.length < 2) continue
941
+ const lengths = busPlans.map((plan) => plan.length)
942
+ const skew = Math.max(...lengths) - Math.min(...lengths)
943
+ if (skew > bus.maxLengthSkew + 1e-6) {
944
+ addIssue(
945
+ issues,
946
+ "bus-length-skew",
947
+ `Bus ${bus.busId} has ${skew.toFixed(6)}mm routed-length skew; ${bus.maxLengthSkew.toFixed(6)}mm is allowed`,
948
+ busPlans[0],
949
+ )
950
+ }
951
+ }
922
952
 
923
953
  for (const connection of inputSrj.connections) {
924
954
  const connectionPlans = plansByConnection.get(connection.name) ?? []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",