@willcgage/module-schematic 0.81.0 → 0.82.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
@@ -1454,10 +1454,14 @@ interface TrackPart {
1454
1454
  /** `flex` is track sold by the length rather than as a fixed geometry — its
1455
1455
  * {@link overallLength} is the LONGEST piece you can lay from it, not a shape
1456
1456
  * (#193). */
1457
- /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout two turnouts plus the
1458
- * diagonal between two parallel tracks, sold as one fixture. It therefore
1459
- * carries {@link trackSpacing}, which no single turnout has, and its
1460
- * `frogNumber` describes the two turnouts in it. */
1457
+ /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout. A Fast Tracks
1458
+ * crossover fixture builds ONE SYMMETRICAL HALF; you build a second, rotate it
1459
+ * 180° and butt the two together at the through routes and the diamond to get
1460
+ * a complete DOUBLE (scissors) crossover — four turnouts, two diagonals and
1461
+ * the X where they cross. So it carries {@link trackSpacing} and
1462
+ * {@link secondaryFrogAngle}, which no single turnout has, and
1463
+ * {@link piecesPerAssembly} — because its lengths describe the HALF, not the
1464
+ * finished crossover. */
1461
1465
  kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1462
1466
  /** Manufacturer part numbers by hand, where the part has a hand. */
1463
1467
  partNumbers?: {
@@ -1503,6 +1507,14 @@ interface TrackPart {
1503
1507
  buildable?: boolean;
1504
1508
  /** The shortest the part can be built. Buildable parts only. */
1505
1509
  minimumLength?: PartDimension;
1510
+ /** How many identical pieces built on this fixture make ONE finished part.
1511
+ *
1512
+ * ⚠️ WHEN THIS IS SET, `overallLength` AND `minimumLength` DESCRIBE ONE PIECE,
1513
+ * NOT THE FINISHED ITEM. A Fast Tracks crossover fixture builds one half of a
1514
+ * double crossover: you build it twice, rotate the second 180°, and butt them
1515
+ * together. Its 10.07″ is the half. Absent or 1 means the fixture builds the
1516
+ * whole part in one go, which is the case for every turnout and wye here. */
1517
+ piecesPerAssembly?: number;
1506
1518
  /** The radius of plain curve this turnout can stand in for, as a
1507
1519
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1508
1520
  * with it — it is here because it is a real published dimension and dropping
@@ -1684,9 +1696,32 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1684
1696
  /**
1685
1697
  * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1686
1698
  *
1687
- * crossover angle 2nd frog default minimum track spacing
1688
- * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1689
- * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1699
+ * crossover angle 2nd frog HALF: default minimum track spacing
1700
+ * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1701
+ * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1702
+ *
1703
+ * ⚠️⚠️ **THE LENGTHS ABOVE ARE ONE HALF, NOT THE FINISHED CROSSOVER.** Will
1704
+ * Gage, 2026-07-26: *"crossovers are two pieces. the pdf shows half, then you
1705
+ * would duplicate this same piece and flip it 180 and butt it up to the through
1706
+ * and X."* Fast Tracks say the same — *"Crossovers are constructed by building
1707
+ * two symmetrical halves of a crossover in the Assembly Fixture and then joining
1708
+ * them to form a complete double crossover"* — and their own gloss on the
1709
+ * length is "the length of the turnout on the QuickSticks", i.e. the piece the
1710
+ * fixture holds. `piecesPerAssembly: 2` records this so the number cannot be
1711
+ * read as the assembly's.
1712
+ *
1713
+ * ⚠️ THE FINISHED LENGTH IS NOT PUBLISHED, and is deliberately not stored. The
1714
+ * two halves are related by a 180° rotation about the diamond, so they cover the
1715
+ * same longitudinal span and the finished crossover is plausibly also ~10.07″ —
1716
+ * but that is an inference from the symmetry, not a reading, and this library
1717
+ * has been burned four times by exactly that kind of plausible reconstruction.
1718
+ * If it matters, measure a built one.
1719
+ *
1720
+ * ⚠️ THESE MAKE A DOUBLE (SCISSORS) CROSSOVER — four turnouts, two diagonals,
1721
+ * and the X where the diagonals cross. A half carries one full 9.46° frog and
1722
+ * HALF of the 19° diamond, which is why the diamond only exists once the second
1723
+ * piece is butted up. Not a single crossover: half a diamond is not usable on
1724
+ * its own.
1690
1725
  *
1691
1726
  * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1692
1727
  * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
@@ -1703,9 +1738,11 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1703
1738
  *
1704
1739
  * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1705
1740
  * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
1706
- * is the diamond in a scissors crossover, where two diagonals each leaving at
1707
- * the frog angle meet each other. It is a free cross-check on the pair, and it
1708
- * passes.
1741
+ * is the diamond, where the two halves' diagonals each leaving its main at the
1742
+ * frog angle, and pointing opposite ways because the second piece is turned
1743
+ * 180° — cross each other. It is a free cross-check on the pair, it passes, and
1744
+ * it independently corroborates the two-piece build: a part with one frog would
1745
+ * have no second angle to publish.
1709
1746
  *
1710
1747
  * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1711
1748
  * turnout size lookups must never see it: `kind` is `"crossover"`, and every
@@ -2046,6 +2083,7 @@ interface StoredTrackPart {
2046
2083
  secondaryFrogAngleDeg?: number | null;
2047
2084
  secondaryFrogAngleSource?: string | null;
2048
2085
  buildable?: boolean | null;
2086
+ piecesPerAssembly?: number | null;
2049
2087
  leadInches?: number | null;
2050
2088
  leadSource?: string | null;
2051
2089
  outerRadiusInches?: number | null;
package/dist/index.d.ts CHANGED
@@ -1454,10 +1454,14 @@ interface TrackPart {
1454
1454
  /** `flex` is track sold by the length rather than as a fixed geometry — its
1455
1455
  * {@link overallLength} is the LONGEST piece you can lay from it, not a shape
1456
1456
  * (#193). */
1457
- /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout two turnouts plus the
1458
- * diagonal between two parallel tracks, sold as one fixture. It therefore
1459
- * carries {@link trackSpacing}, which no single turnout has, and its
1460
- * `frogNumber` describes the two turnouts in it. */
1457
+ /** ⚠️ `crossover` is an ASSEMBLY, not a single turnout. A Fast Tracks
1458
+ * crossover fixture builds ONE SYMMETRICAL HALF; you build a second, rotate it
1459
+ * 180° and butt the two together at the through routes and the diamond to get
1460
+ * a complete DOUBLE (scissors) crossover — four turnouts, two diagonals and
1461
+ * the X where they cross. So it carries {@link trackSpacing} and
1462
+ * {@link secondaryFrogAngle}, which no single turnout has, and
1463
+ * {@link piecesPerAssembly} — because its lengths describe the HALF, not the
1464
+ * finished crossover. */
1461
1465
  kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1462
1466
  /** Manufacturer part numbers by hand, where the part has a hand. */
1463
1467
  partNumbers?: {
@@ -1503,6 +1507,14 @@ interface TrackPart {
1503
1507
  buildable?: boolean;
1504
1508
  /** The shortest the part can be built. Buildable parts only. */
1505
1509
  minimumLength?: PartDimension;
1510
+ /** How many identical pieces built on this fixture make ONE finished part.
1511
+ *
1512
+ * ⚠️ WHEN THIS IS SET, `overallLength` AND `minimumLength` DESCRIBE ONE PIECE,
1513
+ * NOT THE FINISHED ITEM. A Fast Tracks crossover fixture builds one half of a
1514
+ * double crossover: you build it twice, rotate the second 180°, and butt them
1515
+ * together. Its 10.07″ is the half. Absent or 1 means the fixture builds the
1516
+ * whole part in one go, which is the case for every turnout and wye here. */
1517
+ piecesPerAssembly?: number;
1506
1518
  /** The radius of plain curve this turnout can stand in for, as a
1507
1519
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1508
1520
  * with it — it is here because it is a real published dimension and dropping
@@ -1684,9 +1696,32 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1684
1696
  /**
1685
1697
  * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1686
1698
  *
1687
- * crossover angle 2nd frog default minimum track spacing
1688
- * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1689
- * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1699
+ * crossover angle 2nd frog HALF: default minimum track spacing
1700
+ * #6 9.46° 19° 10.07″ 9.31″ 1.09″
1701
+ * #8 7.13° 14.3° 13.61″ 13.07″ 1.09″
1702
+ *
1703
+ * ⚠️⚠️ **THE LENGTHS ABOVE ARE ONE HALF, NOT THE FINISHED CROSSOVER.** Will
1704
+ * Gage, 2026-07-26: *"crossovers are two pieces. the pdf shows half, then you
1705
+ * would duplicate this same piece and flip it 180 and butt it up to the through
1706
+ * and X."* Fast Tracks say the same — *"Crossovers are constructed by building
1707
+ * two symmetrical halves of a crossover in the Assembly Fixture and then joining
1708
+ * them to form a complete double crossover"* — and their own gloss on the
1709
+ * length is "the length of the turnout on the QuickSticks", i.e. the piece the
1710
+ * fixture holds. `piecesPerAssembly: 2` records this so the number cannot be
1711
+ * read as the assembly's.
1712
+ *
1713
+ * ⚠️ THE FINISHED LENGTH IS NOT PUBLISHED, and is deliberately not stored. The
1714
+ * two halves are related by a 180° rotation about the diamond, so they cover the
1715
+ * same longitudinal span and the finished crossover is plausibly also ~10.07″ —
1716
+ * but that is an inference from the symmetry, not a reading, and this library
1717
+ * has been burned four times by exactly that kind of plausible reconstruction.
1718
+ * If it matters, measure a built one.
1719
+ *
1720
+ * ⚠️ THESE MAKE A DOUBLE (SCISSORS) CROSSOVER — four turnouts, two diagonals,
1721
+ * and the X where the diagonals cross. A half carries one full 9.46° frog and
1722
+ * HALF of the 19° diamond, which is why the diamond only exists once the second
1723
+ * piece is butted up. Not a single crossover: half a diamond is not usable on
1724
+ * its own.
1690
1725
  *
1691
1726
  * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1692
1727
  * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
@@ -1703,9 +1738,11 @@ declare const FAST_TRACKS_N_ME55: TrackPart[];
1703
1738
  *
1704
1739
  * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1705
1740
  * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
1706
- * is the diamond in a scissors crossover, where two diagonals each leaving at
1707
- * the frog angle meet each other. It is a free cross-check on the pair, and it
1708
- * passes.
1741
+ * is the diamond, where the two halves' diagonals each leaving its main at the
1742
+ * frog angle, and pointing opposite ways because the second piece is turned
1743
+ * 180° — cross each other. It is a free cross-check on the pair, it passes, and
1744
+ * it independently corroborates the two-piece build: a part with one frog would
1745
+ * have no second angle to publish.
1709
1746
  *
1710
1747
  * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1711
1748
  * turnout size lookups must never see it: `kind` is `"crossover"`, and every
@@ -2046,6 +2083,7 @@ interface StoredTrackPart {
2046
2083
  secondaryFrogAngleDeg?: number | null;
2047
2084
  secondaryFrogAngleSource?: string | null;
2048
2085
  buildable?: boolean | null;
2086
+ piecesPerAssembly?: number | null;
2049
2087
  leadInches?: number | null;
2050
2088
  leadSource?: string | null;
2051
2089
  outerRadiusInches?: number | null;
package/dist/index.js CHANGED
@@ -1586,11 +1586,12 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1586
1586
  manufacturer: "Fast Tracks",
1587
1587
  line: "Code 55",
1588
1588
  scale: "N",
1589
- name: `#${n} Crossover`,
1589
+ name: `#${n} Double Crossover`,
1590
1590
  kind: "crossover",
1591
1591
  partNumbers: { single: `AF-N-C-${n}-ME55` },
1592
1592
  frogNumber: n,
1593
1593
  buildable: true,
1594
+ piecesPerAssembly: 2,
1594
1595
  actualAngle: { deg, source: manufacturer, note: `${spec}. atan(1/${n}) exactly.` },
1595
1596
  secondaryFrogAngle: {
1596
1597
  deg: second,
@@ -1600,9 +1601,13 @@ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1600
1601
  overallLength: {
1601
1602
  inches: dflt,
1602
1603
  source: manufacturer,
1603
- note: `${spec}. The DEFAULT length of the whole assembly \u2014 a fixture, so the builder chooses.`
1604
+ note: `${spec}. \u26A0\uFE0F ONE HALF, NOT THE FINISHED CROSSOVER \u2014 the fixture builds a symmetrical half, which you build twice and butt together after turning the second 180\xB0. Fast Tracks gloss it as "the length of the turnout on the QuickSticks", i.e. the piece in the jig. The DEFAULT for that piece; it is a fixture, so the builder chooses.`
1605
+ },
1606
+ minimumLength: {
1607
+ inches: min,
1608
+ source: manufacturer,
1609
+ note: `${spec}. The shortest ONE HALF can be built \u2014 see the overall length's note.`
1604
1610
  },
1605
- minimumLength: { inches: min, source: manufacturer, note: spec },
1606
1611
  trackSpacing: {
1607
1612
  inches: spacing,
1608
1613
  source: manufacturer,
@@ -1951,6 +1956,8 @@ function storedPartToTrackPart(row) {
1951
1956
  ...note ? { note } : {}
1952
1957
  };
1953
1958
  if (row.buildable) part.buildable = true;
1959
+ if (typeof row.piecesPerAssembly === "number" && row.piecesPerAssembly > 1)
1960
+ part.piecesPerAssembly = row.piecesPerAssembly;
1954
1961
  if (lead) part.lead = lead;
1955
1962
  const outer = dim(row.outerRadiusInches, row.radiusSource);
1956
1963
  const inner = dim(row.innerRadiusInches, row.radiusSource);