@willcgage/module-schematic 0.49.5 → 0.50.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.cjs CHANGED
@@ -541,12 +541,23 @@ function carCapacity(fromPos, toPos, carLengthInches = N_CAR_LENGTH_INCHES) {
541
541
  if (!(carLengthInches > 0)) return 0;
542
542
  return Math.max(0, Math.floor(Math.abs(toPos - fromPos) / carLengthInches));
543
543
  }
544
+ function healSelfDivergingTurnouts(turnouts) {
545
+ if (!turnouts?.some((t) => t.onTrack === t.divergeTrack)) return turnouts;
546
+ return turnouts.map((t) => {
547
+ if (t.onTrack !== t.divergeTrack) return t;
548
+ if (t.onTrack === MAIN_TRACK_ID) return { ...t, divergeTrack: MAIN2_TRACK_ID };
549
+ if (t.onTrack === MAIN2_TRACK_ID) return { ...t, divergeTrack: MAIN_TRACK_ID };
550
+ return t;
551
+ });
552
+ }
544
553
  function asModuleSchematic(x) {
545
554
  if (!x || typeof x !== "object") return null;
546
555
  const d = x;
547
556
  if (typeof d.version !== "number") return null;
548
557
  if (!Array.isArray(d.endplates) || !Array.isArray(d.tracks)) return null;
549
- return d;
558
+ const doc = d;
559
+ const healed = healSelfDivergingTurnouts(doc.turnouts);
560
+ return healed === doc.turnouts ? doc : { ...doc, turnouts: healed };
550
561
  }
551
562
  function emptyEditorState(lengthInches) {
552
563
  return {
@@ -935,24 +946,19 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
935
946
  trackB: x.tracks?.[1] ?? MAIN_TRACK_ID
936
947
  })),
937
948
  extraTracks,
938
- turnouts: (d.turnouts ?? []).map((t) => {
939
- let divergeTrack = t.divergeTrack;
940
- if (divergeTrack === t.onTrack) {
941
- if (t.onTrack === MAIN_TRACK_ID) divergeTrack = MAIN2_TRACK_ID;
942
- else if (t.onTrack === MAIN2_TRACK_ID) divergeTrack = MAIN_TRACK_ID;
943
- }
944
- return {
945
- id: t.id,
946
- name: t.name ?? "",
947
- pos: sc(t.pos),
948
- onTrack: t.onTrack,
949
- divergeTrack,
950
- kind: t.kind ?? "right",
951
- ...t.size ? { size: t.size } : {},
952
- ...t.curved ? { curved: true } : {},
953
- ...t.flipped ? { flipped: true } : {}
954
- };
955
- }),
949
+ // Heal a turnout that diverges into the track it sits on (#172) with the
950
+ // same helper the read path uses, so the editor and every renderer agree.
951
+ turnouts: (healSelfDivergingTurnouts(d.turnouts) ?? []).map((t) => ({
952
+ id: t.id,
953
+ name: t.name ?? "",
954
+ pos: sc(t.pos),
955
+ onTrack: t.onTrack,
956
+ divergeTrack: t.divergeTrack,
957
+ kind: t.kind ?? "right",
958
+ ...t.size ? { size: t.size } : {},
959
+ ...t.curved ? { curved: true } : {},
960
+ ...t.flipped ? { flipped: true } : {}
961
+ })),
956
962
  controlPoints: readControlPoints(d, sc),
957
963
  industries: (d.industries ?? []).map((ind) => ({
958
964
  id: ind.id,
@@ -1084,6 +1090,24 @@ function buildCrossover(state) {
1084
1090
  ];
1085
1091
  return { track, turnouts };
1086
1092
  }
1093
+ var RAIL_GAUGE_INCHES = 0.354;
1094
+ var TURNOUT_LEAD_INCHES_PER_FROG = 0.482;
1095
+ function turnoutClosure(size, opts = {}) {
1096
+ const N = size > 0 ? size : 6;
1097
+ const g = opts.gaugeInches ?? RAIL_GAUGE_INCHES;
1098
+ const lead = Math.max(0.01, opts.leadInches ?? N * TURNOUT_LEAD_INCHES_PER_FROG);
1099
+ const frogSlope = 1 / N;
1100
+ const k = (lead / N - g) / (lead * lead);
1101
+ const switchSlope = Math.max(0, frogSlope - 2 * k * lead);
1102
+ return {
1103
+ lead,
1104
+ switchSlope,
1105
+ frogSlope,
1106
+ // Past the frog the route is straight at the frog angle — the closure curve
1107
+ // has done its work, so don't keep bending (that's the tangent Option 1).
1108
+ offsetAt: (s) => s <= lead ? switchSlope * s + k * s * s : g + frogSlope * (s - lead)
1109
+ };
1110
+ }
1087
1111
  function divergeSideForHand(kind, stubDir, flipped) {
1088
1112
  if (kind !== "left" && kind !== "right") return 0;
1089
1113
  const s = (stubDir >= 0 ? 1 : -1) * (flipped ? -1 : 1);
@@ -1607,6 +1631,8 @@ exports.MAIN2_TRACK_ID = MAIN2_TRACK_ID;
1607
1631
  exports.MAIN_TRACK_ID = MAIN_TRACK_ID;
1608
1632
  exports.N_CAR_LENGTH_INCHES = N_CAR_LENGTH_INCHES;
1609
1633
  exports.N_SCALE_RATIO = N_SCALE_RATIO;
1634
+ exports.RAIL_GAUGE_INCHES = RAIL_GAUGE_INCHES;
1635
+ exports.TURNOUT_LEAD_INCHES_PER_FROG = TURNOUT_LEAD_INCHES_PER_FROG;
1610
1636
  exports.WHOLE_MODULE_SECTION_ID = WHOLE_MODULE_SECTION_ID;
1611
1637
  exports.asModuleSchematic = asModuleSchematic;
1612
1638
  exports.benchworkBand = benchworkBand;
@@ -1658,5 +1684,6 @@ exports.stateToDoc = stateToDoc;
1658
1684
  exports.toSectionRelative = toSectionRelative;
1659
1685
  exports.trackMeetsEndplateIssues = trackMeetsEndplateIssues;
1660
1686
  exports.trackPath = trackPath;
1687
+ exports.turnoutClosure = turnoutClosure;
1661
1688
  //# sourceMappingURL=index.cjs.map
1662
1689
  //# sourceMappingURL=index.cjs.map