@willcgage/module-schematic 0.73.0 → 0.75.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
@@ -577,6 +577,56 @@ function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
577
577
  return feet * 12 / ratio;
578
578
  }
579
579
  var N_CAR_LENGTH_INCHES = 3.3;
580
+ var CLEARANCE_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
581
+ function clearancePointPastFrogInches(size, library = BUILT_IN_TRACK_PARTS, clearanceInches = CLEARANCE_SPACING_INCHES) {
582
+ const N = size > 0 ? size : 6;
583
+ const lead = leadInchesForSize(N, library);
584
+ const cl = turnoutClosure(N, { leadInches: lead });
585
+ let lo = 0;
586
+ let hi = Math.max(4 * lead, 4 * clearanceInches * N);
587
+ if (cl.offsetAt(hi) < clearanceInches) return hi - lead;
588
+ for (let i = 0; i < 60; i++) {
589
+ const mid = (lo + hi) / 2;
590
+ if (cl.offsetAt(mid) < clearanceInches) lo = mid;
591
+ else hi = mid;
592
+ }
593
+ return Math.max(0, hi - lead);
594
+ }
595
+ function usableCapacity(input) {
596
+ const lo = Math.min(input.fromPos, input.toPos);
597
+ const hi = Math.max(input.fromPos, input.toPos);
598
+ const drawn = hi - lo;
599
+ const carLen = input.carLengthInches ?? N_CAR_LENGTH_INCHES;
600
+ if (typeof input.measuredUsableInches === "number" && input.measuredUsableInches >= 0) {
601
+ const m = input.measuredUsableInches;
602
+ return {
603
+ fromPos: lo,
604
+ toPos: lo + m,
605
+ usableInches: m,
606
+ drawnInches: drawn,
607
+ givenUpInches: Math.max(0, drawn - m),
608
+ cars: carCapacity(0, m, carLen),
609
+ scaleFeet: Math.round(inchesToScaleFeet(m))
610
+ };
611
+ }
612
+ let a = lo;
613
+ let b = hi;
614
+ for (const sw of input.governing ?? []) {
615
+ const c = clearancePointPastFrogInches(sw.size ?? 6, input.library);
616
+ if (Math.abs(sw.pos - lo) <= Math.abs(sw.pos - hi)) a = Math.max(a, lo + c);
617
+ else b = Math.min(b, hi - c);
618
+ }
619
+ const usable = Math.max(0, b - a);
620
+ return {
621
+ fromPos: a,
622
+ toPos: Math.max(a, b),
623
+ usableInches: usable,
624
+ drawnInches: drawn,
625
+ givenUpInches: Math.max(0, drawn - usable),
626
+ cars: carCapacity(0, usable, carLen),
627
+ scaleFeet: Math.round(inchesToScaleFeet(usable))
628
+ };
629
+ }
580
630
  function spanOverhang(input) {
581
631
  const lo = Math.min(input.fromPos, input.toPos);
582
632
  const hi = Math.max(input.fromPos, input.toPos);
@@ -827,7 +877,17 @@ function stateToDoc(state, recordNumber) {
827
877
  toPos: t.toPos,
828
878
  moduleTrackId: t.moduleTrackId,
829
879
  trackName: t.trackName || void 0,
830
- capacityFeet: Math.round(inchesToScaleFeet(Math.abs(t.toPos - t.fromPos))),
880
+ // ⚠️ USABLE capacity, measured from the governing turnouts' CLEARANCE
881
+ // POINTS (#19) — not the drawn rail-to-rail length, which counts track
882
+ // a car can't stand on without fouling the route it diverged from.
883
+ // FMN-0040's 70″ siding is 21 cars drawn and 17 usable.
884
+ capacityFeet: usableCapacity({
885
+ fromPos: t.fromPos,
886
+ toPos: t.toPos,
887
+ governing: state.turnouts.filter((sw) => sw.divergeTrack === t.id),
888
+ measuredUsableInches: t.measuredUsableInches
889
+ }).scaleFeet,
890
+ ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
831
891
  ...state.loop && t.inLoop ? { inLoop: true } : {},
832
892
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
833
893
  }))
@@ -926,7 +986,10 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
926
986
  trackName: t.trackName ?? nameOf(moduleTrackId),
927
987
  ...t.inLoop ? { inLoop: true } : {},
928
988
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
929
- ...trackPath(t.path) ? { path: trackPath(t.path) } : {}
989
+ ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
990
+ // A MEASURED length is a real-world fact about the physical track, so it
991
+ // rescales with the module exactly as its positions do (#20).
992
+ ...typeof t.measuredUsableInches === "number" && Number.isFinite(t.measuredUsableInches) ? { measuredUsableInches: sc(t.measuredUsableInches) } : {}
930
993
  });
931
994
  }
932
995
  }
@@ -1301,6 +1364,25 @@ var ATLAS_CODE55_N = [
1301
1364
  note: "scaled from the measured #7 by frog number \u2014 not measured"
1302
1365
  }
1303
1366
  },
1367
+ {
1368
+ // Will Gage, 2026-07-26: "2057 is 3.5, 2056 is 2.5" — the two Atlas Code 55
1369
+ // wyes are different FROG NUMBERS, not a left/right pair of one part. The
1370
+ // 2056 half of that confirms the entry above; this is the one we didn't have.
1371
+ //
1372
+ // No lead. The #2.5's is `derived` from a per-frog rule its own note records
1373
+ // as refuted, so it rests on nothing — repeating that for a second part
1374
+ // would be exactly how a number nobody checked becomes two. Without one,
1375
+ // `leadInchesForSize` interpolates across the MEASURED parts at the wye's
1376
+ // effective frog (3.5 × 2 = 7), which lands on the measured #7.
1377
+ id: "atlas-c55-n-wye-35",
1378
+ manufacturer: "Atlas",
1379
+ line: "Code 55",
1380
+ scale: "N",
1381
+ name: "#3.5 Wye",
1382
+ kind: "wye",
1383
+ partNumbers: { single: "2057" },
1384
+ frogNumber: 3.5
1385
+ },
1304
1386
  {
1305
1387
  id: "atlas-c55-n-curved-21-15",
1306
1388
  manufacturer: "Atlas",
@@ -2345,6 +2427,7 @@ function poseOverridesFromDoc(doc) {
2345
2427
 
2346
2428
  exports.ATLAS_CODE55_N = ATLAS_CODE55_N;
2347
2429
  exports.BUILT_IN_TRACK_PARTS = BUILT_IN_TRACK_PARTS;
2430
+ exports.CLEARANCE_SPACING_INCHES = CLEARANCE_SPACING_INCHES;
2348
2431
  exports.CODE55_RAIL_HEIGHT_INCHES = CODE55_RAIL_HEIGHT_INCHES;
2349
2432
  exports.DEFAULT_FLEX_PART_ID = DEFAULT_FLEX_PART_ID;
2350
2433
  exports.ENDPLATE_FASCIA_CLEAR_INCHES = ENDPLATE_FASCIA_CLEAR_INCHES;
@@ -2370,6 +2453,7 @@ exports.buildPassingSiding = buildPassingSiding;
2370
2453
  exports.buildTransition = buildTransition;
2371
2454
  exports.carCapacity = carCapacity;
2372
2455
  exports.checkEndplateWidth = checkEndplateWidth;
2456
+ exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
2373
2457
  exports.deriveEndplatePoses = deriveEndplatePoses;
2374
2458
  exports.divergeSideForHand = divergeSideForHand;
2375
2459
  exports.docToState = docToState;
@@ -2439,5 +2523,6 @@ exports.turnoutClosure = turnoutClosure;
2439
2523
  exports.turnoutFacing = turnoutFacing;
2440
2524
  exports.turnoutOccupiedSpan = turnoutOccupiedSpan;
2441
2525
  exports.turnoutPartForSize = turnoutPartForSize;
2526
+ exports.usableCapacity = usableCapacity;
2442
2527
  //# sourceMappingURL=index.cjs.map
2443
2528
  //# sourceMappingURL=index.cjs.map