@willcgage/module-schematic 0.98.0 → 0.101.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
@@ -2157,6 +2157,19 @@ interface PartExtent {
2157
2157
  aheadOfPoints: number;
2158
2158
  /** Frog → the far end. How much turnout there still is past the frog. */
2159
2159
  pastFrog: number;
2160
+ /**
2161
+ * Frog → the NEAR end of the tie strip.
2162
+ *
2163
+ * ⭐⭐ **THIS IS THE ONE A BODY IS MEASURED BACK FROM.** A document's `pos` is
2164
+ * the FROG (Will, 2026-07-27), so a turnout occupies
2165
+ * `[pos − behindFrog, pos + pastFrog]`. Anchoring on the points instead put
2166
+ * every turnout body `lead` too far along — 3.59″ on an Atlas #7.
2167
+ *
2168
+ * ⚠️ Falls back to `behindPoints` when the frog was never measured: without a
2169
+ * frog reading there is nowhere to anchor, and the points is the only landmark
2170
+ * left. `pastFrog` degrades the same way, so the pair stays consistent.
2171
+ */
2172
+ behindFrog: number;
2160
2173
  }
2161
2174
  /**
2162
2175
  * A part's real extent, or **null when it hasn't been measured**.
@@ -2928,6 +2941,177 @@ declare function deriveGraphDoc(doc: ModuleSchematicDoc, library?: TrackPart[]):
2928
2941
  doc: ModuleSchematicDoc;
2929
2942
  warnings: string[];
2930
2943
  };
2944
+ /**
2945
+ * What a turnout in a 1-D document would become as a piece — or what it is
2946
+ * missing before it can become anything.
2947
+ *
2948
+ * ⭐⭐ **IDENTITY IS THE GATE, NOT GEOMETRY.** This is the thing that surprised
2949
+ * us and it is worth stating plainly: converting a drawn module to pieces is
2950
+ * blocked far more often by not knowing WHICH TURNOUT IT IS than by any missing
2951
+ * measurement. Across the production database not one turnout names a part, and
2952
+ * most state no frog number either — so for those there is nothing to look up.
2953
+ * No amount of measuring fixes that; only the owner knows what they laid, which
2954
+ * is exactly why conversion asks instead of guessing.
2955
+ */
2956
+ interface TurnoutIdentity {
2957
+ /** The turnout's id in the document. */
2958
+ id: string;
2959
+ name?: string | null;
2960
+ pos: number;
2961
+ /** The frog number the document states, if any. */
2962
+ size?: number | null;
2963
+ /** A part the document already names — the strongest possible answer. */
2964
+ statedPartId?: string | null;
2965
+ /** The part this turnout converts to; null = the owner has to say. */
2966
+ partId: string | null;
2967
+ /** How `partId` was arrived at. */
2968
+ from: "named" | "frog-number" | "unresolved";
2969
+ /**
2970
+ * The weakest provenance behind the chosen part's geometry, so a caller can
2971
+ * tell a turnout placed from real readings from one placed off a catalogue
2972
+ * figure. Null when unresolved.
2973
+ */
2974
+ source: DimensionSource | null;
2975
+ /** Why it cannot be resolved — null when it can. */
2976
+ why: string | null;
2977
+ /**
2978
+ * Parts the owner could reasonably pick, best first: an exact frog match
2979
+ * ahead of the rest. Only PLACEABLE parts are offered — listing one that
2980
+ * cannot be drawn would be an answer that does not answer.
2981
+ */
2982
+ candidates: string[];
2983
+ }
2984
+ /** Something that stops a whole document converting, whatever the owner says. */
2985
+ interface ConversionBlocker {
2986
+ /** `crossing` · `curved-turnout` · `loop` — the shapes with no piece to be. */
2987
+ kind: string;
2988
+ /** The document object it came from, where there is one. */
2989
+ ref?: string;
2990
+ why: string;
2991
+ }
2992
+ /**
2993
+ * A track the document draws but nothing in it connects — no turnout diverges
2994
+ * onto it.
2995
+ *
2996
+ * ⚠️⚠️ **THE 1-D MODEL TOLERATES THIS AND THE GRAPH CANNOT.** A siding is drawn
2997
+ * from its `lane` and its `fromPos`/`toPos` alone, so a document can carry a
2998
+ * named, capacity-bearing yard track that joins nothing at all and still look
2999
+ * completely normal. Real ones do: Idaho Falls Grain Yard has five yard tracks
3000
+ * and not one turnout. A piece, by contrast, has to be joined to something.
3001
+ *
3002
+ * So these are surfaced rather than converted. Dropping them would lose an
3003
+ * owner's named track and its capacity silently, and inventing a turnout to
3004
+ * reach it would be exactly the fabrication ADR 0001 forbids — the owner is the
3005
+ * only one who knows where it actually joins.
3006
+ */
3007
+ interface OrphanTrack {
3008
+ id: string;
3009
+ trackName?: string;
3010
+ role: TrackRole;
3011
+ why: string;
3012
+ }
3013
+ interface ModuleConversionReport {
3014
+ /** Already authored as pieces — there is nothing to convert. */
3015
+ alreadyGraph: boolean;
3016
+ /**
3017
+ * The rebuild can be offered. False when {@link blockers} is non-empty: those
3018
+ * are shapes no answer can supply, so offering would promise something that
3019
+ * cannot finish.
3020
+ */
3021
+ offerable: boolean;
3022
+ /**
3023
+ * True when the rebuild could run right now with nothing asked.
3024
+ *
3025
+ * ⚠️ Requires BOTH that every turnout resolves and that no track is orphaned.
3026
+ * Checking only the turnouts claimed four production modules were ready when
3027
+ * converting them would have quietly dropped nine named tracks.
3028
+ */
3029
+ readyWithoutAsking: boolean;
3030
+ turnouts: TurnoutIdentity[];
3031
+ /** Ids of the turnouts the owner must identify first. */
3032
+ unanswered: string[];
3033
+ orphanTracks: OrphanTrack[];
3034
+ blockers: ConversionBlocker[];
3035
+ }
3036
+ /**
3037
+ * What would happen if this document were rebuilt as pieces — WITHOUT rebuilding
3038
+ * anything.
3039
+ *
3040
+ * This exists so an owner can be shown the offer and its cost before they accept
3041
+ * it (ADR 0001 amendment, 2026-07-27: conversion is opt-in PER MODULE, never
3042
+ * silent). It is pure, cheap, reads no geometry, and is safe to run on every
3043
+ * module in a list.
3044
+ *
3045
+ * ⚠️ **A document with a graph is NOT re-reported.** It is already the thing
3046
+ * conversion produces.
3047
+ */
3048
+ declare function moduleConversionReport(doc: ModuleSchematicDoc, library?: TrackPart[]): ModuleConversionReport;
3049
+ /**
3050
+ * What the owner said when offered the rebuild.
3051
+ *
3052
+ * ⭐ **ONE ANSWER FOR THE WHOLE MODULE.** Owners lay one kind of turnout
3053
+ * throughout, or at most two, so asking per turnout asks the same question eight
3054
+ * times on a yard. The module-wide answer IS the question; `overrides` is for the
3055
+ * odd one out. (Will's call, 2026-07-27 — it turns 41 questions across the
3056
+ * production database into 14.)
3057
+ */
3058
+ interface ConversionAnswers {
3059
+ /** "The turnouts on this module are…" — a part id from the library. */
3060
+ turnoutPartId?: string | null;
3061
+ /** The exceptions, by turnout id. Beats {@link turnoutPartId}. */
3062
+ overrides?: Record<string, string>;
3063
+ }
3064
+ interface DocToGraphResult {
3065
+ /** The graph to store, or null when the document could not be converted. */
3066
+ graph: NonNullable<ModuleSchematicDoc["graph"]> | null;
3067
+ /** Why the whole conversion was refused — null when it ran. */
3068
+ refused: string | null;
3069
+ /**
3070
+ * Track the conversion could not lay, each with a reason.
3071
+ *
3072
+ * ⚠️ **A REAL LOSS, AND THE OWNER MUST SEE IT.** Unshown, a "successful"
3073
+ * rebuild would quietly be missing a named siding.
3074
+ */
3075
+ notLaid: {
3076
+ id: string;
3077
+ why: string;
3078
+ }[];
3079
+ warnings: string[];
3080
+ }
3081
+ /**
3082
+ * Rebuild a 1-D document as a graph of placed pieces.
3083
+ *
3084
+ * ⭐ **THE OWNER ASKED FOR THIS ONE MODULE** (ADR 0001 amendment, 2026-07-27).
3085
+ * Nothing calls it on a schedule, on load, or in bulk. Conversion supplies
3086
+ * geometry the 1-D document never recorded, which is what the original
3087
+ * constraint forbade doing behind an owner's back; done in front of them, having
3088
+ * shown them {@link moduleConversionReport} and taken their answer, it is an
3089
+ * edit they made.
3090
+ *
3091
+ * ⚠️ **STRAIGHT HOSTS ONLY.** A run is laid along its host's axis, so a module
3092
+ * whose main is DRAWN with bends is refused rather than quietly straightened.
3093
+ * Every module in production today has a straight main; the refusal is there so
3094
+ * the first curved one is a message and not a flattened drawing.
3095
+ *
3096
+ * ⭐ **PIECES CHAIN FROM MEASURED JOINTS, NOT FROM ABSOLUTE ARITHMETIC.** Each
3097
+ * piece is placed, its real joints are read back with {@link placedJoints}, and
3098
+ * the next starts from one of those. Connection then holds BY CONSTRUCTION
3099
+ * rather than by two independent calculations agreeing to within
3100
+ * {@link JOINT_SNAP_INCHES} — a hundredth of an inch is far too tight to hit by
3101
+ * coincidence.
3102
+ *
3103
+
3104
+ * ⭐ A TURNOUT'S THROAT GOES AT `pos − frogOffset`: a document's `pos` is the
3105
+ * That is the inverse of what {@link walkTrackGraph} reports, which is the only
3106
+ * thing that makes convert-then-derive an identity. It is deliberately NOT
3107
+ * {@link turnoutOccupiedSpan}'s anchor, and the two disagree by
3108
+ * `lead − pointsOffset` — 2.97″ on an Atlas #7. That disagreement is real and
3109
+ * unresolved: see the note on {@link moduleConversionReport}. Because of it the
3110
+ * flex gaps here are measured off the placed pieces' OWN joints rather than
3111
+ * through the 1-D helper, so whichever anchor turns out to be right, the track
3112
+ * still meets the turnout it was cut for.
3113
+ */
3114
+ declare function docToGraph(doc: ModuleSchematicDoc, answers?: ConversionAnswers, library?: TrackPart[]): DocToGraphResult;
2931
3115
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2932
3116
  * points, `y` = lateral offset from the through route. */
2933
3117
  interface FrogCasting {
@@ -3209,4 +3393,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
3209
3393
  heading: number;
3210
3394
  }>;
3211
3395
 
3212
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, 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, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
3396
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
package/dist/index.d.ts CHANGED
@@ -2157,6 +2157,19 @@ interface PartExtent {
2157
2157
  aheadOfPoints: number;
2158
2158
  /** Frog → the far end. How much turnout there still is past the frog. */
2159
2159
  pastFrog: number;
2160
+ /**
2161
+ * Frog → the NEAR end of the tie strip.
2162
+ *
2163
+ * ⭐⭐ **THIS IS THE ONE A BODY IS MEASURED BACK FROM.** A document's `pos` is
2164
+ * the FROG (Will, 2026-07-27), so a turnout occupies
2165
+ * `[pos − behindFrog, pos + pastFrog]`. Anchoring on the points instead put
2166
+ * every turnout body `lead` too far along — 3.59″ on an Atlas #7.
2167
+ *
2168
+ * ⚠️ Falls back to `behindPoints` when the frog was never measured: without a
2169
+ * frog reading there is nowhere to anchor, and the points is the only landmark
2170
+ * left. `pastFrog` degrades the same way, so the pair stays consistent.
2171
+ */
2172
+ behindFrog: number;
2160
2173
  }
2161
2174
  /**
2162
2175
  * A part's real extent, or **null when it hasn't been measured**.
@@ -2928,6 +2941,177 @@ declare function deriveGraphDoc(doc: ModuleSchematicDoc, library?: TrackPart[]):
2928
2941
  doc: ModuleSchematicDoc;
2929
2942
  warnings: string[];
2930
2943
  };
2944
+ /**
2945
+ * What a turnout in a 1-D document would become as a piece — or what it is
2946
+ * missing before it can become anything.
2947
+ *
2948
+ * ⭐⭐ **IDENTITY IS THE GATE, NOT GEOMETRY.** This is the thing that surprised
2949
+ * us and it is worth stating plainly: converting a drawn module to pieces is
2950
+ * blocked far more often by not knowing WHICH TURNOUT IT IS than by any missing
2951
+ * measurement. Across the production database not one turnout names a part, and
2952
+ * most state no frog number either — so for those there is nothing to look up.
2953
+ * No amount of measuring fixes that; only the owner knows what they laid, which
2954
+ * is exactly why conversion asks instead of guessing.
2955
+ */
2956
+ interface TurnoutIdentity {
2957
+ /** The turnout's id in the document. */
2958
+ id: string;
2959
+ name?: string | null;
2960
+ pos: number;
2961
+ /** The frog number the document states, if any. */
2962
+ size?: number | null;
2963
+ /** A part the document already names — the strongest possible answer. */
2964
+ statedPartId?: string | null;
2965
+ /** The part this turnout converts to; null = the owner has to say. */
2966
+ partId: string | null;
2967
+ /** How `partId` was arrived at. */
2968
+ from: "named" | "frog-number" | "unresolved";
2969
+ /**
2970
+ * The weakest provenance behind the chosen part's geometry, so a caller can
2971
+ * tell a turnout placed from real readings from one placed off a catalogue
2972
+ * figure. Null when unresolved.
2973
+ */
2974
+ source: DimensionSource | null;
2975
+ /** Why it cannot be resolved — null when it can. */
2976
+ why: string | null;
2977
+ /**
2978
+ * Parts the owner could reasonably pick, best first: an exact frog match
2979
+ * ahead of the rest. Only PLACEABLE parts are offered — listing one that
2980
+ * cannot be drawn would be an answer that does not answer.
2981
+ */
2982
+ candidates: string[];
2983
+ }
2984
+ /** Something that stops a whole document converting, whatever the owner says. */
2985
+ interface ConversionBlocker {
2986
+ /** `crossing` · `curved-turnout` · `loop` — the shapes with no piece to be. */
2987
+ kind: string;
2988
+ /** The document object it came from, where there is one. */
2989
+ ref?: string;
2990
+ why: string;
2991
+ }
2992
+ /**
2993
+ * A track the document draws but nothing in it connects — no turnout diverges
2994
+ * onto it.
2995
+ *
2996
+ * ⚠️⚠️ **THE 1-D MODEL TOLERATES THIS AND THE GRAPH CANNOT.** A siding is drawn
2997
+ * from its `lane` and its `fromPos`/`toPos` alone, so a document can carry a
2998
+ * named, capacity-bearing yard track that joins nothing at all and still look
2999
+ * completely normal. Real ones do: Idaho Falls Grain Yard has five yard tracks
3000
+ * and not one turnout. A piece, by contrast, has to be joined to something.
3001
+ *
3002
+ * So these are surfaced rather than converted. Dropping them would lose an
3003
+ * owner's named track and its capacity silently, and inventing a turnout to
3004
+ * reach it would be exactly the fabrication ADR 0001 forbids — the owner is the
3005
+ * only one who knows where it actually joins.
3006
+ */
3007
+ interface OrphanTrack {
3008
+ id: string;
3009
+ trackName?: string;
3010
+ role: TrackRole;
3011
+ why: string;
3012
+ }
3013
+ interface ModuleConversionReport {
3014
+ /** Already authored as pieces — there is nothing to convert. */
3015
+ alreadyGraph: boolean;
3016
+ /**
3017
+ * The rebuild can be offered. False when {@link blockers} is non-empty: those
3018
+ * are shapes no answer can supply, so offering would promise something that
3019
+ * cannot finish.
3020
+ */
3021
+ offerable: boolean;
3022
+ /**
3023
+ * True when the rebuild could run right now with nothing asked.
3024
+ *
3025
+ * ⚠️ Requires BOTH that every turnout resolves and that no track is orphaned.
3026
+ * Checking only the turnouts claimed four production modules were ready when
3027
+ * converting them would have quietly dropped nine named tracks.
3028
+ */
3029
+ readyWithoutAsking: boolean;
3030
+ turnouts: TurnoutIdentity[];
3031
+ /** Ids of the turnouts the owner must identify first. */
3032
+ unanswered: string[];
3033
+ orphanTracks: OrphanTrack[];
3034
+ blockers: ConversionBlocker[];
3035
+ }
3036
+ /**
3037
+ * What would happen if this document were rebuilt as pieces — WITHOUT rebuilding
3038
+ * anything.
3039
+ *
3040
+ * This exists so an owner can be shown the offer and its cost before they accept
3041
+ * it (ADR 0001 amendment, 2026-07-27: conversion is opt-in PER MODULE, never
3042
+ * silent). It is pure, cheap, reads no geometry, and is safe to run on every
3043
+ * module in a list.
3044
+ *
3045
+ * ⚠️ **A document with a graph is NOT re-reported.** It is already the thing
3046
+ * conversion produces.
3047
+ */
3048
+ declare function moduleConversionReport(doc: ModuleSchematicDoc, library?: TrackPart[]): ModuleConversionReport;
3049
+ /**
3050
+ * What the owner said when offered the rebuild.
3051
+ *
3052
+ * ⭐ **ONE ANSWER FOR THE WHOLE MODULE.** Owners lay one kind of turnout
3053
+ * throughout, or at most two, so asking per turnout asks the same question eight
3054
+ * times on a yard. The module-wide answer IS the question; `overrides` is for the
3055
+ * odd one out. (Will's call, 2026-07-27 — it turns 41 questions across the
3056
+ * production database into 14.)
3057
+ */
3058
+ interface ConversionAnswers {
3059
+ /** "The turnouts on this module are…" — a part id from the library. */
3060
+ turnoutPartId?: string | null;
3061
+ /** The exceptions, by turnout id. Beats {@link turnoutPartId}. */
3062
+ overrides?: Record<string, string>;
3063
+ }
3064
+ interface DocToGraphResult {
3065
+ /** The graph to store, or null when the document could not be converted. */
3066
+ graph: NonNullable<ModuleSchematicDoc["graph"]> | null;
3067
+ /** Why the whole conversion was refused — null when it ran. */
3068
+ refused: string | null;
3069
+ /**
3070
+ * Track the conversion could not lay, each with a reason.
3071
+ *
3072
+ * ⚠️ **A REAL LOSS, AND THE OWNER MUST SEE IT.** Unshown, a "successful"
3073
+ * rebuild would quietly be missing a named siding.
3074
+ */
3075
+ notLaid: {
3076
+ id: string;
3077
+ why: string;
3078
+ }[];
3079
+ warnings: string[];
3080
+ }
3081
+ /**
3082
+ * Rebuild a 1-D document as a graph of placed pieces.
3083
+ *
3084
+ * ⭐ **THE OWNER ASKED FOR THIS ONE MODULE** (ADR 0001 amendment, 2026-07-27).
3085
+ * Nothing calls it on a schedule, on load, or in bulk. Conversion supplies
3086
+ * geometry the 1-D document never recorded, which is what the original
3087
+ * constraint forbade doing behind an owner's back; done in front of them, having
3088
+ * shown them {@link moduleConversionReport} and taken their answer, it is an
3089
+ * edit they made.
3090
+ *
3091
+ * ⚠️ **STRAIGHT HOSTS ONLY.** A run is laid along its host's axis, so a module
3092
+ * whose main is DRAWN with bends is refused rather than quietly straightened.
3093
+ * Every module in production today has a straight main; the refusal is there so
3094
+ * the first curved one is a message and not a flattened drawing.
3095
+ *
3096
+ * ⭐ **PIECES CHAIN FROM MEASURED JOINTS, NOT FROM ABSOLUTE ARITHMETIC.** Each
3097
+ * piece is placed, its real joints are read back with {@link placedJoints}, and
3098
+ * the next starts from one of those. Connection then holds BY CONSTRUCTION
3099
+ * rather than by two independent calculations agreeing to within
3100
+ * {@link JOINT_SNAP_INCHES} — a hundredth of an inch is far too tight to hit by
3101
+ * coincidence.
3102
+ *
3103
+
3104
+ * ⭐ A TURNOUT'S THROAT GOES AT `pos − frogOffset`: a document's `pos` is the
3105
+ * That is the inverse of what {@link walkTrackGraph} reports, which is the only
3106
+ * thing that makes convert-then-derive an identity. It is deliberately NOT
3107
+ * {@link turnoutOccupiedSpan}'s anchor, and the two disagree by
3108
+ * `lead − pointsOffset` — 2.97″ on an Atlas #7. That disagreement is real and
3109
+ * unresolved: see the note on {@link moduleConversionReport}. Because of it the
3110
+ * flex gaps here are measured off the placed pieces' OWN joints rather than
3111
+ * through the 1-D helper, so whichever anchor turns out to be right, the track
3112
+ * still meets the turnout it was cut for.
3113
+ */
3114
+ declare function docToGraph(doc: ModuleSchematicDoc, answers?: ConversionAnswers, library?: TrackPart[]): DocToGraphResult;
2931
3115
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2932
3116
  * points, `y` = lateral offset from the through route. */
2933
3117
  interface FrogCasting {
@@ -3209,4 +3393,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
3209
3393
  heading: number;
3210
3394
  }>;
3211
3395
 
3212
- export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, 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, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };
3396
+ export { ATLAS_CODE55_N, BUILT_IN_TRACK_PARTS, BUMPER_DRAWN_INCHES, type BenchworkPoint, type BranchConnector, CLEARANCE_SPACING_INCHES, CODE55_RAIL_HEIGHT_INCHES, type ConversionAnswers, type ConversionBlocker, DEFAULT_FLEX_PART_ID, type DimensionSource, type DocToGraphResult, 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 EndplateEdge, type EndplateEdgePose, 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_MAIN_MIN_RADIUS_INCHES, FREEMO_REVERSE_CURVE_STRAIGHT_INCHES, FREEMO_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, GENERIC_END_OF_TRACK, type GeometryType, type GraphAnchor, type GraphConflict, type GraphConnection, type GraphDocInput, type GraphDocResult, type GraphRoute, type GraphTurnout, type GraphWalk, type ImportedPart, type IndustryLabelMode, type IndustrySpot, JOINT_SNAP_INCHES, type LanePinch, MAIN2_TRACK_ID, MAIN_TRACK_ID, type ModuleConversionReport, type ModuleFeatures, type ModuleFootprint, type ModuleFootprintInput, type ModuleGeometryInput, type ModuleSchematicDoc, type ModuleTrackRow, N_CAR_LENGTH_INCHES, N_SCALE_RATIO, type OccupiedSpan, type OrphanTrack, type OutlineFace, PINCH_EASE_INCHES, type PartAngle, type PartDimension, type PartEnd, type PartExtent, type PartGeometry, type PartJoint, type PartJointRole, type PartSegment, type PieceSnap, type PlacedJoint, RAIL_GAUGE_INCHES, type ResolvedAnchor, type ReturnLoopGeometry, type ReturnLoopShape, type RouteSpan, type RunInsertion, 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, TRACK_PART_KINDS, TURNOUT_LEAD_INCHES_PER_FROG, type TrackConfig, type TrackGraph, type TrackPart, type TrackPiece, type TrackRole, type TurnoutClosure, type TurnoutIdentity, type TurnoutKind, type UsableCapacity, WHOLE_MODULE_SECTION_ID, asModuleSchematic, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTrackGraph, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, deriveGraphDoc, divergeSideForHand, docToGraph, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, fitFlexBetween, flexPartFor, flexParts, flexPieces, flexRunEnd, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, graphToDoc, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, insertIntoRun, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleConversionReport, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, pastFrogInchesForSize, pathLengthInches, pieceHand, piecePartNumber, pieceRoutePaths, placedJoints, poseNeedsManual, poseOverridesFromDoc, remapPos, resizeFlexPiece, resolveGraphAnchor, returnLoop, sampleBenchworkOutline, samplePartSegments, samplePath, scaleFeetToInches, sectionAdjacency, sectionBand, sectionBreaksFromSections, sectionComponents, sectionFootprints, sectionNeighbours, sectionSpans, sectionSpansOrWhole, sectionalArcInches, sectionedCenterline, sectionedEndPose, sliceCenterline, snapPiece, spanOverhang, stateToDoc, storedPartToTrackPart, toSectionRelative, trackMeetsEndplateIssues, trackPart, trackPath, turnoutClosure, turnoutFacing, turnoutOccupiedSpan, turnoutPartForSize, usableCapacity, walkTrackGraph };