@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.d.cts CHANGED
@@ -131,6 +131,10 @@ interface SchematicTrack {
131
131
  * product's maximum piece length. Present = these are the ONLY joints, so an
132
132
  * owner's deliberate cut survives a change elsewhere on the module. */
133
133
  flexCuts?: number[] | null;
134
+ /** The owner's MEASURED usable length, real inches (#20) — for what the
135
+ * drawing can't know: a bumper post short of the drawn end, a structure
136
+ * fouling the track. Absent = derive it from the clearance points (#19). */
137
+ measuredUsableInches?: number | null;
134
138
  }
135
139
  interface SchematicTurnout {
136
140
  id: string;
@@ -771,6 +775,74 @@ declare function scaleFeetToInches(feet: number, ratio?: number): number;
771
775
  * ~3.0″; ~3.3″ over the couplers — the real spacing a cut of cars takes. The
772
776
  * single constant every repo reads so a track's car count matches everywhere. */
773
777
  declare const N_CAR_LENGTH_INCHES = 3.3;
778
+ /**
779
+ * How far the two routes must separate before a car on one clears equipment on
780
+ * the other — the CLEARANCE POINT's defining distance (#19).
781
+ *
782
+ * ⭐ Deliberately the **Free-moN track spacing**, not a second number. §2.0 uses
783
+ * 1.125″ as the distance at which two parallel tracks coexist, so "a car here
784
+ * clears a car there" is the same statement the standard already makes. It's
785
+ * 15 scale feet centre to centre, inside the prototype's usual 13–15 ft range.
786
+ *
787
+ * The alternative — a separate prototype constant — would leave the app holding
788
+ * two spacings that disagree about what "clear" means.
789
+ */
790
+ declare const CLEARANCE_SPACING_INCHES = 1.125;
791
+ /**
792
+ * How far past the FROG a turnout's diverging route reaches the clearance point
793
+ * (#19) — the point from which usable capacity is measured.
794
+ *
795
+ * Not a measurement and not a guess: it's solved from the closure the drawing
796
+ * already uses, by asking where `offsetAt` first reaches
797
+ * {@link CLEARANCE_SPACING_INCHES}. So it follows the part's real lead, and a
798
+ * better-measured part moves it automatically.
799
+ *
800
+ * Returns the distance PAST THE FROG because `pos` means the frog (#132) — add
801
+ * it to the turnout's position, in the direction the turnout faces.
802
+ */
803
+ declare function clearancePointPastFrogInches(size: number, library?: TrackPart[], clearanceInches?: number): number;
804
+ /** A track's usable capacity, measured to the clearance-point standard (#19). */
805
+ interface UsableCapacity {
806
+ /** The stretch a car can actually stand on, inches along the run. */
807
+ fromPos: number;
808
+ toPos: number;
809
+ /** Its length — what capacity is computed from. */
810
+ usableInches: number;
811
+ /** The run's full drawn length, for comparison. */
812
+ drawnInches: number;
813
+ /** How much each end gives up to its governing turnout's clearance point. */
814
+ givenUpInches: number;
815
+ /** Cars that fit in the usable length. */
816
+ cars: number;
817
+ /** Usable length as scale feet. */
818
+ scaleFeet: number;
819
+ }
820
+ /**
821
+ * A track's USABLE capacity — measured from the governing turnout's clearance
822
+ * point, not from the rail ends (#19/#20).
823
+ *
824
+ * A spur runs clearance point → end of track; a siding, clearance point →
825
+ * clearance point. The difference is not small: FMN-0040's 70″ passing siding
826
+ * has two #7s on it, and loses 5.4″ at each end — 21 cars drawn, 17 usable.
827
+ *
828
+ * `measuredUsableInches` is the owner's override for what the drawing can't
829
+ * know — a bumper post short of the drawn end, a structure fouling the track.
830
+ * Given, it wins outright and the clearance points aren't applied to it: they
831
+ * measured the usable length itself.
832
+ */
833
+ declare function usableCapacity(input: {
834
+ fromPos: number;
835
+ toPos: number;
836
+ /** Turnouts that DIVERGE ONTO this track — the ones that govern its ends. */
837
+ governing?: {
838
+ pos: number;
839
+ size?: number | null;
840
+ }[] | null;
841
+ /** The owner's measured usable length, inches. Wins when present. */
842
+ measuredUsableInches?: number | null;
843
+ library?: TrackPart[];
844
+ carLengthInches?: number;
845
+ }): UsableCapacity;
774
846
  /** How much of a car-spot span has no rail under it (#194). */
775
847
  interface SpanOverhang {
776
848
  /** Inches the span runs past the track's near end (0 = it starts on track). */
@@ -822,6 +894,8 @@ interface EditorTrack {
822
894
  /** Authored 2-D path (module-local inches) — a bent/rotated spur's real
823
895
  * shape. Absent = derive from the main + lane (#2d-track). */
824
896
  path?: BenchworkPoint[];
897
+ /** Measured usable length, real inches (#20). Absent = derived (#19). */
898
+ measuredUsableInches?: number;
825
899
  }
826
900
  /** A module_tracks row as loaded for the editor. */
827
901
  interface ModuleTrackRow {
@@ -2051,4 +2125,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2051
2125
  heading: number;
2052
2126
  }>;
2053
2127
 
2054
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, 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, 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 SectionFootprint, 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, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, 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 };
2128
+ 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, 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 SectionFootprint, 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, 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
@@ -131,6 +131,10 @@ interface SchematicTrack {
131
131
  * product's maximum piece length. Present = these are the ONLY joints, so an
132
132
  * owner's deliberate cut survives a change elsewhere on the module. */
133
133
  flexCuts?: number[] | null;
134
+ /** The owner's MEASURED usable length, real inches (#20) — for what the
135
+ * drawing can't know: a bumper post short of the drawn end, a structure
136
+ * fouling the track. Absent = derive it from the clearance points (#19). */
137
+ measuredUsableInches?: number | null;
134
138
  }
135
139
  interface SchematicTurnout {
136
140
  id: string;
@@ -771,6 +775,74 @@ declare function scaleFeetToInches(feet: number, ratio?: number): number;
771
775
  * ~3.0″; ~3.3″ over the couplers — the real spacing a cut of cars takes. The
772
776
  * single constant every repo reads so a track's car count matches everywhere. */
773
777
  declare const N_CAR_LENGTH_INCHES = 3.3;
778
+ /**
779
+ * How far the two routes must separate before a car on one clears equipment on
780
+ * the other — the CLEARANCE POINT's defining distance (#19).
781
+ *
782
+ * ⭐ Deliberately the **Free-moN track spacing**, not a second number. §2.0 uses
783
+ * 1.125″ as the distance at which two parallel tracks coexist, so "a car here
784
+ * clears a car there" is the same statement the standard already makes. It's
785
+ * 15 scale feet centre to centre, inside the prototype's usual 13–15 ft range.
786
+ *
787
+ * The alternative — a separate prototype constant — would leave the app holding
788
+ * two spacings that disagree about what "clear" means.
789
+ */
790
+ declare const CLEARANCE_SPACING_INCHES = 1.125;
791
+ /**
792
+ * How far past the FROG a turnout's diverging route reaches the clearance point
793
+ * (#19) — the point from which usable capacity is measured.
794
+ *
795
+ * Not a measurement and not a guess: it's solved from the closure the drawing
796
+ * already uses, by asking where `offsetAt` first reaches
797
+ * {@link CLEARANCE_SPACING_INCHES}. So it follows the part's real lead, and a
798
+ * better-measured part moves it automatically.
799
+ *
800
+ * Returns the distance PAST THE FROG because `pos` means the frog (#132) — add
801
+ * it to the turnout's position, in the direction the turnout faces.
802
+ */
803
+ declare function clearancePointPastFrogInches(size: number, library?: TrackPart[], clearanceInches?: number): number;
804
+ /** A track's usable capacity, measured to the clearance-point standard (#19). */
805
+ interface UsableCapacity {
806
+ /** The stretch a car can actually stand on, inches along the run. */
807
+ fromPos: number;
808
+ toPos: number;
809
+ /** Its length — what capacity is computed from. */
810
+ usableInches: number;
811
+ /** The run's full drawn length, for comparison. */
812
+ drawnInches: number;
813
+ /** How much each end gives up to its governing turnout's clearance point. */
814
+ givenUpInches: number;
815
+ /** Cars that fit in the usable length. */
816
+ cars: number;
817
+ /** Usable length as scale feet. */
818
+ scaleFeet: number;
819
+ }
820
+ /**
821
+ * A track's USABLE capacity — measured from the governing turnout's clearance
822
+ * point, not from the rail ends (#19/#20).
823
+ *
824
+ * A spur runs clearance point → end of track; a siding, clearance point →
825
+ * clearance point. The difference is not small: FMN-0040's 70″ passing siding
826
+ * has two #7s on it, and loses 5.4″ at each end — 21 cars drawn, 17 usable.
827
+ *
828
+ * `measuredUsableInches` is the owner's override for what the drawing can't
829
+ * know — a bumper post short of the drawn end, a structure fouling the track.
830
+ * Given, it wins outright and the clearance points aren't applied to it: they
831
+ * measured the usable length itself.
832
+ */
833
+ declare function usableCapacity(input: {
834
+ fromPos: number;
835
+ toPos: number;
836
+ /** Turnouts that DIVERGE ONTO this track — the ones that govern its ends. */
837
+ governing?: {
838
+ pos: number;
839
+ size?: number | null;
840
+ }[] | null;
841
+ /** The owner's measured usable length, inches. Wins when present. */
842
+ measuredUsableInches?: number | null;
843
+ library?: TrackPart[];
844
+ carLengthInches?: number;
845
+ }): UsableCapacity;
774
846
  /** How much of a car-spot span has no rail under it (#194). */
775
847
  interface SpanOverhang {
776
848
  /** Inches the span runs past the track's near end (0 = it starts on track). */
@@ -822,6 +894,8 @@ interface EditorTrack {
822
894
  /** Authored 2-D path (module-local inches) — a bent/rotated spur's real
823
895
  * shape. Absent = derive from the main + lane (#2d-track). */
824
896
  path?: BenchworkPoint[];
897
+ /** Measured usable length, real inches (#20). Absent = derived (#19). */
898
+ measuredUsableInches?: number;
825
899
  }
826
900
  /** A module_tracks row as loaded for the editor. */
827
901
  interface ModuleTrackRow {
@@ -2051,4 +2125,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2051
2125
  heading: number;
2052
2126
  }>;
2053
2127
 
2054
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, type BenchworkPoint, type BranchConnector, 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, 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 SectionFootprint, 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, WHOLE_MODULE_SECTION_ID, asModuleSchematic, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, 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 };
2128
+ 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, 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 SectionFootprint, 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, 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
@@ -575,6 +575,56 @@ function scaleFeetToInches(feet, ratio = N_SCALE_RATIO) {
575
575
  return feet * 12 / ratio;
576
576
  }
577
577
  var N_CAR_LENGTH_INCHES = 3.3;
578
+ var CLEARANCE_SPACING_INCHES = FREEMO_TRACK_SPACING_INCHES;
579
+ function clearancePointPastFrogInches(size, library = BUILT_IN_TRACK_PARTS, clearanceInches = CLEARANCE_SPACING_INCHES) {
580
+ const N = size > 0 ? size : 6;
581
+ const lead = leadInchesForSize(N, library);
582
+ const cl = turnoutClosure(N, { leadInches: lead });
583
+ let lo = 0;
584
+ let hi = Math.max(4 * lead, 4 * clearanceInches * N);
585
+ if (cl.offsetAt(hi) < clearanceInches) return hi - lead;
586
+ for (let i = 0; i < 60; i++) {
587
+ const mid = (lo + hi) / 2;
588
+ if (cl.offsetAt(mid) < clearanceInches) lo = mid;
589
+ else hi = mid;
590
+ }
591
+ return Math.max(0, hi - lead);
592
+ }
593
+ function usableCapacity(input) {
594
+ const lo = Math.min(input.fromPos, input.toPos);
595
+ const hi = Math.max(input.fromPos, input.toPos);
596
+ const drawn = hi - lo;
597
+ const carLen = input.carLengthInches ?? N_CAR_LENGTH_INCHES;
598
+ if (typeof input.measuredUsableInches === "number" && input.measuredUsableInches >= 0) {
599
+ const m = input.measuredUsableInches;
600
+ return {
601
+ fromPos: lo,
602
+ toPos: lo + m,
603
+ usableInches: m,
604
+ drawnInches: drawn,
605
+ givenUpInches: Math.max(0, drawn - m),
606
+ cars: carCapacity(0, m, carLen),
607
+ scaleFeet: Math.round(inchesToScaleFeet(m))
608
+ };
609
+ }
610
+ let a = lo;
611
+ let b = hi;
612
+ for (const sw of input.governing ?? []) {
613
+ const c = clearancePointPastFrogInches(sw.size ?? 6, input.library);
614
+ if (Math.abs(sw.pos - lo) <= Math.abs(sw.pos - hi)) a = Math.max(a, lo + c);
615
+ else b = Math.min(b, hi - c);
616
+ }
617
+ const usable = Math.max(0, b - a);
618
+ return {
619
+ fromPos: a,
620
+ toPos: Math.max(a, b),
621
+ usableInches: usable,
622
+ drawnInches: drawn,
623
+ givenUpInches: Math.max(0, drawn - usable),
624
+ cars: carCapacity(0, usable, carLen),
625
+ scaleFeet: Math.round(inchesToScaleFeet(usable))
626
+ };
627
+ }
578
628
  function spanOverhang(input) {
579
629
  const lo = Math.min(input.fromPos, input.toPos);
580
630
  const hi = Math.max(input.fromPos, input.toPos);
@@ -825,7 +875,17 @@ function stateToDoc(state, recordNumber) {
825
875
  toPos: t.toPos,
826
876
  moduleTrackId: t.moduleTrackId,
827
877
  trackName: t.trackName || void 0,
828
- capacityFeet: Math.round(inchesToScaleFeet(Math.abs(t.toPos - t.fromPos))),
878
+ // ⚠️ USABLE capacity, measured from the governing turnouts' CLEARANCE
879
+ // POINTS (#19) — not the drawn rail-to-rail length, which counts track
880
+ // a car can't stand on without fouling the route it diverged from.
881
+ // FMN-0040's 70″ siding is 21 cars drawn and 17 usable.
882
+ capacityFeet: usableCapacity({
883
+ fromPos: t.fromPos,
884
+ toPos: t.toPos,
885
+ governing: state.turnouts.filter((sw) => sw.divergeTrack === t.id),
886
+ measuredUsableInches: t.measuredUsableInches
887
+ }).scaleFeet,
888
+ ...typeof t.measuredUsableInches === "number" && t.measuredUsableInches >= 0 ? { measuredUsableInches: t.measuredUsableInches } : {},
829
889
  ...state.loop && t.inLoop ? { inLoop: true } : {},
830
890
  ...t.path && t.path.length >= 2 ? { path: t.path } : {}
831
891
  }))
@@ -924,7 +984,10 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
924
984
  trackName: t.trackName ?? nameOf(moduleTrackId),
925
985
  ...t.inLoop ? { inLoop: true } : {},
926
986
  // Authored path kept as-drawn (a physical shape, not rescaled with length).
927
- ...trackPath(t.path) ? { path: trackPath(t.path) } : {}
987
+ ...trackPath(t.path) ? { path: trackPath(t.path) } : {},
988
+ // A MEASURED length is a real-world fact about the physical track, so it
989
+ // rescales with the module exactly as its positions do (#20).
990
+ ...typeof t.measuredUsableInches === "number" && Number.isFinite(t.measuredUsableInches) ? { measuredUsableInches: sc(t.measuredUsableInches) } : {}
928
991
  });
929
992
  }
930
993
  }
@@ -2360,6 +2423,6 @@ function poseOverridesFromDoc(doc) {
2360
2423
  return out;
2361
2424
  }
2362
2425
 
2363
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, CODE55_RAIL_HEIGHT_INCHES, DEFAULT_FLEX_PART_ID, ENDPLATE_FASCIA_CLEAR_INCHES, ENDPLATE_LEAD_INCHES, 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, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, 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 };
2426
+ 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, 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, 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 };
2364
2427
  //# sourceMappingURL=index.js.map
2365
2428
  //# sourceMappingURL=index.js.map