@willcgage/module-schematic 0.80.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,7 +1454,15 @@ 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
- kind: "turnout" | "wye" | "curved-turnout" | "crossing" | "flex";
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. */
1465
+ kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1458
1466
  /** Manufacturer part numbers by hand, where the part has a hand. */
1459
1467
  partNumbers?: {
1460
1468
  left?: string;
@@ -1499,6 +1507,14 @@ interface TrackPart {
1499
1507
  buildable?: boolean;
1500
1508
  /** The shortest the part can be built. Buildable parts only. */
1501
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;
1502
1518
  /** The radius of plain curve this turnout can stand in for, as a
1503
1519
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1504
1520
  * with it — it is here because it is a real published dimension and dropping
@@ -1521,6 +1537,20 @@ interface TrackPart {
1521
1537
  innerRadius?: PartDimension;
1522
1538
  /** Crossing angle, degrees. */
1523
1539
  crossingAngleDeg?: number;
1540
+ /** Centre-to-centre distance of the two parallel tracks a {@link kind}
1541
+ * `crossover` joins. A crossover fixture is BUILT for one spacing — it is not
1542
+ * adjustable — so this decides whether the part suits a given standard at all.
1543
+ *
1544
+ * ⚠️ READ {@link FREEMO_TRACK_SPACING_INCHES} ALONGSIDE THIS. Free-moN §2.0
1545
+ * fixes double-track spacing at exactly 1.125″; the Fast Tracks N crossovers
1546
+ * are built to 1.09″. That 0.035″ is small but it is REAL and it is not a
1547
+ * tolerance — the fixture cannot be built to another spacing. Recorded rather
1548
+ * than reconciled: it is a fact about the product, and an owner deciding what
1549
+ * to buy is better served by the true number than by a convenient one. */
1550
+ trackSpacing?: PartDimension;
1551
+ /** The SECOND frog angle on a part that has two — the diamond where the
1552
+ * diagonals of a scissors crossover cross. Published in degrees. */
1553
+ secondaryFrogAngle?: PartAngle;
1524
1554
  /** The part's drawn geometry in its own frame, when it came from a library
1525
1555
  * file. This is the payload worth importing — real outlines we can draw
1526
1556
  * instead of deriving a turnout's shape from a frog number. */
@@ -1663,6 +1693,70 @@ declare const FLEX_TRACK_PARTS: TrackPart[];
1663
1693
  * here — the public product pages say the same thing and can be cited.
1664
1694
  */
1665
1695
  declare const FAST_TRACKS_N_ME55: TrackPart[];
1696
+ /**
1697
+ * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1698
+ *
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.
1725
+ *
1726
+ * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1727
+ * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
1728
+ * crossover hand-built on either of these puts the parallel track 0.035″ closer
1729
+ * than the standard, and that difference has to go somewhere — the tracks pinch
1730
+ * through the crossover and open back out to meet the endplates, which is fixed
1731
+ * at both ends by {@link FREEMO_TRACK_SPACING_INCHES}.
1732
+ *
1733
+ * Recorded, NOT reconciled. It is 0.9 mm and most builders will absorb it, but
1734
+ * it is a property of the product rather than a tolerance, and someone choosing
1735
+ * what to buy for a double-track Free-moN module is better served by the true
1736
+ * number than a convenient one. Nothing here warns or blocks — that would be a
1737
+ * decision for the app, not for a data table.
1738
+ *
1739
+ * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1740
+ * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
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.
1746
+ *
1747
+ * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1748
+ * turnout size lookups must never see it: `kind` is `"crossover"`, and every
1749
+ * one of them filters `kind === "turnout"`.
1750
+ *
1751
+ * Atlas make crossover parts too — Will has no figures for them as of
1752
+ * 2026-07-26, so there are deliberately no Atlas entries here rather than
1753
+ * guessed ones.
1754
+ *
1755
+ * Source: handlaidtrack.com product pages, read 2026-07-26. Their tables also
1756
+ * carry tie size and tie spacing; those describe the QuickSticks tie strip's
1757
+ * appearance rather than the track's geometry, so they are not carried here.
1758
+ */
1759
+ declare const FAST_TRACKS_N_ME55_CROSSOVERS: TrackPart[];
1666
1760
  /** What a track is laid with when nobody has said — the commonest N-scale flex. */
1667
1761
  declare const DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
1668
1762
  /** Every built-in part, across manufacturers. */
@@ -1984,7 +2078,12 @@ interface StoredTrackPart {
1984
2078
  minimumLengthSource?: string | null;
1985
2079
  substitutionRadiusInches?: number | null;
1986
2080
  substitutionRadiusSource?: string | null;
2081
+ trackSpacingInches?: number | null;
2082
+ trackSpacingSource?: string | null;
2083
+ secondaryFrogAngleDeg?: number | null;
2084
+ secondaryFrogAngleSource?: string | null;
1987
2085
  buildable?: boolean | null;
2086
+ piecesPerAssembly?: number | null;
1988
2087
  leadInches?: number | null;
1989
2088
  leadSource?: string | null;
1990
2089
  outerRadiusInches?: number | null;
@@ -2327,4 +2426,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2327
2426
  heading: number;
2328
2427
  }>;
2329
2428
 
2330
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2429
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
package/dist/index.d.ts CHANGED
@@ -1454,7 +1454,15 @@ 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
- kind: "turnout" | "wye" | "curved-turnout" | "crossing" | "flex";
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. */
1465
+ kind: "turnout" | "wye" | "curved-turnout" | "crossover" | "crossing" | "flex";
1458
1466
  /** Manufacturer part numbers by hand, where the part has a hand. */
1459
1467
  partNumbers?: {
1460
1468
  left?: string;
@@ -1499,6 +1507,14 @@ interface TrackPart {
1499
1507
  buildable?: boolean;
1500
1508
  /** The shortest the part can be built. Buildable parts only. */
1501
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;
1502
1518
  /** The radius of plain curve this turnout can stand in for, as a
1503
1519
  * layout-planning figure. Fast Tracks publish it; Atlas do not. Nothing draws
1504
1520
  * with it — it is here because it is a real published dimension and dropping
@@ -1521,6 +1537,20 @@ interface TrackPart {
1521
1537
  innerRadius?: PartDimension;
1522
1538
  /** Crossing angle, degrees. */
1523
1539
  crossingAngleDeg?: number;
1540
+ /** Centre-to-centre distance of the two parallel tracks a {@link kind}
1541
+ * `crossover` joins. A crossover fixture is BUILT for one spacing — it is not
1542
+ * adjustable — so this decides whether the part suits a given standard at all.
1543
+ *
1544
+ * ⚠️ READ {@link FREEMO_TRACK_SPACING_INCHES} ALONGSIDE THIS. Free-moN §2.0
1545
+ * fixes double-track spacing at exactly 1.125″; the Fast Tracks N crossovers
1546
+ * are built to 1.09″. That 0.035″ is small but it is REAL and it is not a
1547
+ * tolerance — the fixture cannot be built to another spacing. Recorded rather
1548
+ * than reconciled: it is a fact about the product, and an owner deciding what
1549
+ * to buy is better served by the true number than by a convenient one. */
1550
+ trackSpacing?: PartDimension;
1551
+ /** The SECOND frog angle on a part that has two — the diamond where the
1552
+ * diagonals of a scissors crossover cross. Published in degrees. */
1553
+ secondaryFrogAngle?: PartAngle;
1524
1554
  /** The part's drawn geometry in its own frame, when it came from a library
1525
1555
  * file. This is the payload worth importing — real outlines we can draw
1526
1556
  * instead of deriving a turnout's shape from a frog number. */
@@ -1663,6 +1693,70 @@ declare const FLEX_TRACK_PARTS: TrackPart[];
1663
1693
  * here — the public product pages say the same thing and can be cited.
1664
1694
  */
1665
1695
  declare const FAST_TRACKS_N_ME55: TrackPart[];
1696
+ /**
1697
+ * Fast Tracks N-scale CROSSOVER fixtures, ME Code 55.
1698
+ *
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.
1725
+ *
1726
+ * ⚠️⚠️ **THE TRACK SPACING IS 1.09″, AND FREE-moN §2.0 REQUIRES 1.125″.**
1727
+ * A crossover fixture is machined for ONE spacing; it is not adjustable. So a
1728
+ * crossover hand-built on either of these puts the parallel track 0.035″ closer
1729
+ * than the standard, and that difference has to go somewhere — the tracks pinch
1730
+ * through the crossover and open back out to meet the endplates, which is fixed
1731
+ * at both ends by {@link FREEMO_TRACK_SPACING_INCHES}.
1732
+ *
1733
+ * Recorded, NOT reconciled. It is 0.9 mm and most builders will absorb it, but
1734
+ * it is a property of the product rather than a tolerance, and someone choosing
1735
+ * what to buy for a double-track Free-moN module is better served by the true
1736
+ * number than a convenient one. Nothing here warns or blocks — that would be a
1737
+ * decision for the app, not for a data table.
1738
+ *
1739
+ * ⭐ THE SECOND FROG IS EXACTLY TWICE THE FIRST — 19° against 2 × 9.46 = 18.92,
1740
+ * and 14.3° against 2 × 7.13 = 14.26, both inside the published rounding. That
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.
1746
+ *
1747
+ * ⚠️ A CROSSOVER IS AN ASSEMBLY, so `partExtent` means nothing for it and the
1748
+ * turnout size lookups must never see it: `kind` is `"crossover"`, and every
1749
+ * one of them filters `kind === "turnout"`.
1750
+ *
1751
+ * Atlas make crossover parts too — Will has no figures for them as of
1752
+ * 2026-07-26, so there are deliberately no Atlas entries here rather than
1753
+ * guessed ones.
1754
+ *
1755
+ * Source: handlaidtrack.com product pages, read 2026-07-26. Their tables also
1756
+ * carry tie size and tie spacing; those describe the QuickSticks tie strip's
1757
+ * appearance rather than the track's geometry, so they are not carried here.
1758
+ */
1759
+ declare const FAST_TRACKS_N_ME55_CROSSOVERS: TrackPart[];
1666
1760
  /** What a track is laid with when nobody has said — the commonest N-scale flex. */
1667
1761
  declare const DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
1668
1762
  /** Every built-in part, across manufacturers. */
@@ -1984,7 +2078,12 @@ interface StoredTrackPart {
1984
2078
  minimumLengthSource?: string | null;
1985
2079
  substitutionRadiusInches?: number | null;
1986
2080
  substitutionRadiusSource?: string | null;
2081
+ trackSpacingInches?: number | null;
2082
+ trackSpacingSource?: string | null;
2083
+ secondaryFrogAngleDeg?: number | null;
2084
+ secondaryFrogAngleSource?: string | null;
1987
2085
  buildable?: boolean | null;
2086
+ piecesPerAssembly?: number | null;
1988
2087
  leadInches?: number | null;
1989
2088
  leadSource?: string | null;
1990
2089
  outerRadiusInches?: number | null;
@@ -2327,4 +2426,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2327
2426
  heading: number;
2328
2427
  }>;
2329
2428
 
2330
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2429
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, type DimensionSource, type DrawCrossing, type DrawCrossover, type DrawIndustry, type DrawSignal, type DrawTrack, type DrawTurnout, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, type EditorBranch, type EditorControlPoint, type EditorCpSignal, type EditorCrossing, type EditorIndustry, type EditorState, type EditorTrack, type EditorTurnout, type EndplateBConfig, type EndplatePose, type EndplateTrackIssue, type EndplateWidthIssue, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OutlineFace, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartSegment, RAIL_GAUGE_INCHES, type ReturnLoopGeometry, type ReturnLoopShape, type SchematicBlock, type SchematicControlPoint, type SchematicCrossing, type SchematicEndplate, type SchematicEndplateTrack, type SchematicIndustry, type SchematicSection, type SchematicSignal, type SchematicTrack, type SchematicTurnout, type SectionAdjacency, type SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, type SectionRelativePos, type SignalFacing, type SignalSide, type SpanOverhang, type StoredTrackPart, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackPart, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
package/dist/index.js CHANGED
@@ -1574,10 +1574,52 @@ var FAST_TRACKS_N_ME55 = [
1574
1574
  substitutionRadius: { inches: subR, source: manufacturer, note: spec }
1575
1575
  };
1576
1576
  });
1577
+ var FAST_TRACKS_N_ME55_CROSSOVERS = [
1578
+ // [N, angle°, secondFrog°, defaultLength, minLength, trackSpacing]
1579
+ [6, 9.46, 19, 10.07, 9.31, 1.09],
1580
+ [8, 7.13, 14.3, 13.61, 13.07, 1.09]
1581
+ ].map(([n, deg, second, dflt, min, spacing]) => {
1582
+ const spec = "handlaidtrack.com Detailed Specifications, read 2026-07-26";
1583
+ const manufacturer = "manufacturer";
1584
+ return {
1585
+ id: `fast-tracks-n-me55-c-${n}`,
1586
+ manufacturer: "Fast Tracks",
1587
+ line: "Code 55",
1588
+ scale: "N",
1589
+ name: `#${n} Double Crossover`,
1590
+ kind: "crossover",
1591
+ partNumbers: { single: `AF-N-C-${n}-ME55` },
1592
+ frogNumber: n,
1593
+ buildable: true,
1594
+ piecesPerAssembly: 2,
1595
+ actualAngle: { deg, source: manufacturer, note: `${spec}. atan(1/${n}) exactly.` },
1596
+ secondaryFrogAngle: {
1597
+ deg: second,
1598
+ source: manufacturer,
1599
+ note: `${spec}. The diamond of a scissors crossover \u2014 2 \xD7 ${deg}\xB0 within the published rounding.`
1600
+ },
1601
+ overallLength: {
1602
+ inches: dflt,
1603
+ source: manufacturer,
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.`
1610
+ },
1611
+ trackSpacing: {
1612
+ inches: spacing,
1613
+ source: manufacturer,
1614
+ note: `${spec}. \u26A0\uFE0F Free-moN \xA72.0 requires ${FREEMO_TRACK_SPACING_INCHES}\u2033 \u2014 this fixture is built to ${spacing}\u2033, ${(FREEMO_TRACK_SPACING_INCHES - spacing).toFixed(3)}\u2033 tighter, and cannot be built to another spacing.`
1615
+ }
1616
+ };
1617
+ });
1577
1618
  var DEFAULT_FLEX_PART_ID = "atlas-c55-n-flex";
1578
1619
  var BUILT_IN_TRACK_PARTS = [
1579
1620
  ...ATLAS_CODE55_N,
1580
1621
  ...FAST_TRACKS_N_ME55,
1622
+ ...FAST_TRACKS_N_ME55_CROSSOVERS,
1581
1623
  ...FLEX_TRACK_PARTS
1582
1624
  ];
1583
1625
  function flexParts(library = BUILT_IN_TRACK_PARTS) {
@@ -1905,7 +1947,17 @@ function storedPartToTrackPart(row) {
1905
1947
  if (minimum) part.minimumLength = minimum;
1906
1948
  const substitution = dim(row.substitutionRadiusInches, row.substitutionRadiusSource);
1907
1949
  if (substitution) part.substitutionRadius = substitution;
1950
+ const spacing = dim(row.trackSpacingInches, row.trackSpacingSource);
1951
+ if (spacing) part.trackSpacing = spacing;
1952
+ if (typeof row.secondaryFrogAngleDeg === "number")
1953
+ part.secondaryFrogAngle = {
1954
+ deg: row.secondaryFrogAngleDeg,
1955
+ source: asSource(row.secondaryFrogAngleSource),
1956
+ ...note ? { note } : {}
1957
+ };
1908
1958
  if (row.buildable) part.buildable = true;
1959
+ if (typeof row.piecesPerAssembly === "number" && row.piecesPerAssembly > 1)
1960
+ part.piecesPerAssembly = row.piecesPerAssembly;
1909
1961
  if (lead) part.lead = lead;
1910
1962
  const outer = dim(row.outerRadiusInches, row.radiusSource);
1911
1963
  const inner = dim(row.innerRadiusInches, row.radiusSource);
@@ -2600,6 +2652,6 @@ function poseOverridesFromDoc(doc) {
2600
2652
  return out;
2601
2653
  }
2602
2654
 
2603
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2655
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, FAST_TRACKS_N_ME55, FAST_TRACKS_N_ME55_CROSSOVERS, FLEX_TRACK_PARTS, FREEMO_ENDPLATE_TRACK_FASCIA_CLEARANCE_INCHES, FREEMO_ENDPLATE_WIDTH_MIN_INCHES, FREEMO_ENDPLATE_WIDTH_RECOMMENDED_INCHES, FREEMO_TRACK_SPACING_INCHES, MAIN2_TRACK_ID, MAIN_TRACK_ID, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, RAIL_GAUGE_INCHES, TIE_HALF_LENGTH_INCHES, TURNOUT_LEAD_INCHES_PER_FROG, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partOutlineAtFrog, pastFrogInchesForSize, pathLengthInches, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionedCenterline, sectionedEndPose, sliceCenterline, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity };
2604
2656
  //# sourceMappingURL=index.js.map
2605
2657
  //# sourceMappingURL=index.js.map