@willcgage/module-schematic 0.58.0 → 0.59.0

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/dist/index.d.cts CHANGED
@@ -1354,10 +1354,34 @@ interface TurnoutClosure {
1354
1354
  switchSlope: number;
1355
1355
  /** Slope at and beyond the frog — the frog angle, 1/N. */
1356
1356
  frogSlope: number;
1357
+ /** Total run from the points to where the route arrives PARALLEL at
1358
+ * `arriveAtInches`. Infinity when no target was given (the legacy
1359
+ * straight-forever behaviour). Use this rather than solving for the offset:
1360
+ * solving finds where the route REACHES the lane, not where it arrives
1361
+ * parallel, and the difference is a visible kink. */
1362
+ span: number;
1363
+ /** Length of the easing curve, 0 when not easing. */
1364
+ easeInches: number;
1357
1365
  }
1358
1366
  declare function turnoutClosure(size: number, opts?: {
1359
1367
  leadInches?: number;
1360
1368
  gaugeInches?: number;
1369
+ /**
1370
+ * Lateral offset the diverging route must ARRIVE AT, PARALLEL — normally
1371
+ * one lane spacing, i.e. the track it feeds.
1372
+ *
1373
+ * ⚠️ Without this the route runs straight at the frog angle forever, and a
1374
+ * caller can only solve for where it first REACHES the offset. Reaching is
1375
+ * not arriving: it gets there still climbing at 1/N while the track it
1376
+ * joins runs parallel, so the two meet with an instantaneous change of
1377
+ * direction. That KINK is what reads as "the rails don't line up" — each
1378
+ * rail is offset perpendicular to its own heading, so at a kink the two
1379
+ * rails meet at different points.
1380
+ */
1381
+ arriveAtInches?: number;
1382
+ /** Length of the easing curve. Defaults to one lead, which puts the radius
1383
+ * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
1384
+ easeInches?: number;
1361
1385
  }): TurnoutClosure;
1362
1386
  declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number,
1363
1387
  /** The turnout is installed the other way round — the points face the far
package/dist/index.d.ts CHANGED
@@ -1354,10 +1354,34 @@ interface TurnoutClosure {
1354
1354
  switchSlope: number;
1355
1355
  /** Slope at and beyond the frog — the frog angle, 1/N. */
1356
1356
  frogSlope: number;
1357
+ /** Total run from the points to where the route arrives PARALLEL at
1358
+ * `arriveAtInches`. Infinity when no target was given (the legacy
1359
+ * straight-forever behaviour). Use this rather than solving for the offset:
1360
+ * solving finds where the route REACHES the lane, not where it arrives
1361
+ * parallel, and the difference is a visible kink. */
1362
+ span: number;
1363
+ /** Length of the easing curve, 0 when not easing. */
1364
+ easeInches: number;
1357
1365
  }
1358
1366
  declare function turnoutClosure(size: number, opts?: {
1359
1367
  leadInches?: number;
1360
1368
  gaugeInches?: number;
1369
+ /**
1370
+ * Lateral offset the diverging route must ARRIVE AT, PARALLEL — normally
1371
+ * one lane spacing, i.e. the track it feeds.
1372
+ *
1373
+ * ⚠️ Without this the route runs straight at the frog angle forever, and a
1374
+ * caller can only solve for where it first REACHES the offset. Reaching is
1375
+ * not arriving: it gets there still climbing at 1/N while the track it
1376
+ * joins runs parallel, so the two meet with an instantaneous change of
1377
+ * direction. That KINK is what reads as "the rails don't line up" — each
1378
+ * rail is offset perpendicular to its own heading, so at a kink the two
1379
+ * rails meet at different points.
1380
+ */
1381
+ arriveAtInches?: number;
1382
+ /** Length of the easing curve. Defaults to one lead, which puts the radius
1383
+ * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
1384
+ easeInches?: number;
1361
1385
  }): TurnoutClosure;
1362
1386
  declare function divergeSideForHand(kind: TurnoutKind | undefined, stubDir: number,
1363
1387
  /** The turnout is installed the other way round — the points face the far
package/dist/index.js CHANGED
@@ -1444,13 +1444,33 @@ function turnoutClosure(size, opts = {}) {
1444
1444
  const frogSlope = 1 / N;
1445
1445
  const k = (lead / N - g) / (lead * lead);
1446
1446
  const switchSlope = Math.max(0, frogSlope - 2 * k * lead);
1447
+ const target = opts.arriveAtInches;
1448
+ let a = Infinity;
1449
+ let b = 0;
1450
+ if (target != null && target > g) {
1451
+ b = Math.max(0.01, opts.easeInches ?? lead);
1452
+ a = (target - g) / frogSlope - b / 2;
1453
+ if (a < 0) {
1454
+ a = 0;
1455
+ b = 2 * (target - g) / frogSlope;
1456
+ }
1457
+ }
1458
+ const span = target != null && target > g ? lead + a + b : Infinity;
1447
1459
  return {
1448
1460
  lead,
1449
1461
  switchSlope,
1450
1462
  frogSlope,
1451
- // Past the frog the route is straight at the frog angle — the closure curve
1452
- // has done its work, so don't keep bending (that's the tangent Option 1).
1453
- offsetAt: (s) => s <= lead ? switchSlope * s + k * s * s : g + frogSlope * (s - lead)
1463
+ span,
1464
+ easeInches: b,
1465
+ offsetAt: (s) => {
1466
+ if (s <= lead) return switchSlope * s + k * s * s;
1467
+ const past = s - lead;
1468
+ if (past <= a) return g + frogSlope * past;
1469
+ if (target == null) return g + frogSlope * past;
1470
+ const u = Math.min(past - a, b);
1471
+ const eased = g + frogSlope * a + frogSlope * u - frogSlope * u * u / (2 * b);
1472
+ return past - a >= b ? target : eased;
1473
+ }
1454
1474
  };
1455
1475
  }
1456
1476
  function divergeSideForHand(kind, stubDir, flipped) {