@willcgage/module-schematic 0.74.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
  }
@@ -2364,6 +2427,7 @@ function poseOverridesFromDoc(doc) {
2364
2427
 
2365
2428
  exports.ATLAS_CODE55_N = ATLAS_CODE55_N;
2366
2429
  exports.BUILT_IN_TRACK_PARTS = BUILT_IN_TRACK_PARTS;
2430
+ exports.CLEARANCE_SPACING_INCHES = CLEARANCE_SPACING_INCHES;
2367
2431
  exports.CODE55_RAIL_HEIGHT_INCHES = CODE55_RAIL_HEIGHT_INCHES;
2368
2432
  exports.DEFAULT_FLEX_PART_ID = DEFAULT_FLEX_PART_ID;
2369
2433
  exports.ENDPLATE_FASCIA_CLEAR_INCHES = ENDPLATE_FASCIA_CLEAR_INCHES;
@@ -2389,6 +2453,7 @@ exports.buildPassingSiding = buildPassingSiding;
2389
2453
  exports.buildTransition = buildTransition;
2390
2454
  exports.carCapacity = carCapacity;
2391
2455
  exports.checkEndplateWidth = checkEndplateWidth;
2456
+ exports.clearancePointPastFrogInches = clearancePointPastFrogInches;
2392
2457
  exports.deriveEndplatePoses = deriveEndplatePoses;
2393
2458
  exports.divergeSideForHand = divergeSideForHand;
2394
2459
  exports.docToState = docToState;
@@ -2458,5 +2523,6 @@ exports.turnoutClosure = turnoutClosure;
2458
2523
  exports.turnoutFacing = turnoutFacing;
2459
2524
  exports.turnoutOccupiedSpan = turnoutOccupiedSpan;
2460
2525
  exports.turnoutPartForSize = turnoutPartForSize;
2526
+ exports.usableCapacity = usableCapacity;
2461
2527
  //# sourceMappingURL=index.cjs.map
2462
2528
  //# sourceMappingURL=index.cjs.map