@willcgage/module-schematic 0.83.2 → 0.85.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
@@ -34,6 +34,14 @@ interface SchematicEndplate {
34
34
  pos: number;
35
35
  side: "up" | "down";
36
36
  };
37
+ /** ⭐ THE ENDPLATE'S EDGE OF THE BENCHWORK (ADR 0001). When present it WINS
38
+ * over `pose` and over derivation, and position, heading and width are all
39
+ * read off the polygon — so they cannot drift apart from the board.
40
+ *
41
+ * `pose` remains for modules authored before this and for shapes with no
42
+ * benchwork polygon to bind to. Nothing is auto-converted: guessing which
43
+ * edge an owner's freehand pose meant would be inventing an intent. */
44
+ edge?: EndplateEdge | null;
37
45
  /** Manual pose override (#175 phase 1b) — the endplate's module-local track
38
46
  * point (x, y inches) + outward-normal heading (°). Hand-entered for shapes
39
47
  * the geometry fields can't derive (wye, freeform, loop); wins over
@@ -1184,6 +1192,9 @@ interface EditorState {
1184
1192
  /** Authored per-endplate TRACK offsets by id — the primary track's signed
1185
1193
  * distance from the plate centre, inches. Absent id = the §2.0 default. */
1186
1194
  endplateTrackOffsets: Record<string, number>;
1195
+ /** Endplate EDGE bindings by id (ADR 0001) — an endplate that is part of the
1196
+ * benchwork. Wins over a pose; nothing is auto-converted from one. */
1197
+ endplateEdges?: Record<string, EndplateEdge>;
1187
1198
  /** Benchwork footprint outline — polygon vertices in module-local inches
1188
1199
  * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
1189
1200
  * Empty = no authored outline (fall back to the endplate-width band). */
@@ -2302,6 +2313,62 @@ declare function turnoutClosure(size: number, opts?: {
2302
2313
  * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
2303
2314
  easeInches?: number;
2304
2315
  }): TurnoutClosure;
2316
+ /** Which end of a part this is. A graph connects joints; a walk uses `routes`. */
2317
+ type PartJointRole = "throat" | "through" | "diverge" | "divergeB";
2318
+ /** One end of a part, in part-local inches: `x` along the through route from
2319
+ * the tie end, `y` lateral toward the diverging side, `angleDeg` the OUTWARD
2320
+ * tangent (0 = +x). Named, unlike {@link PartEnd}, because a graph has to know
2321
+ * a throat from a frog. */
2322
+ interface PartJoint extends PartEnd {
2323
+ id: string;
2324
+ role: PartJointRole;
2325
+ }
2326
+ interface PartGeometry {
2327
+ joints: PartJoint[];
2328
+ /** Joint id pairs a train can run between. A turnout has two routes sharing
2329
+ * its throat; flex has one; a double crossover has four. */
2330
+ routes: [string, string][];
2331
+ /** The weakest provenance among the dimensions used — so a caller can tell a
2332
+ * placed-from-measurement part from a placed-from-a-catalogue-figure one. */
2333
+ source: DimensionSource;
2334
+ /** True when the diverging end came from a measured {@link
2335
+ * TrackPart.divergingLength} rather than from assuming the diverging rail
2336
+ * leaves the moulding at the same place the through route does. */
2337
+ divergingEndMeasured: boolean;
2338
+ }
2339
+ /**
2340
+ * Why a part has no derivable geometry — null when it has.
2341
+ *
2342
+ * Worth its own function because "we cannot place this yet" is a fact an owner
2343
+ * should see in the picker, not a silent absence. It is also the parts-library
2344
+ * backlog in machine-readable form: every string this returns is a measurement
2345
+ * someone could take.
2346
+ */
2347
+ declare function partGeometryGap(part: TrackPart): string | null;
2348
+ /**
2349
+ * A part's joints and routes, in its own frame.
2350
+ *
2351
+ * The through route runs along +x from the tie end; the diverging side is +y.
2352
+ * A placed piece is this, transformed — which is the whole point: the geometry
2353
+ * belongs to the PART, and placement is a rotation and a translation.
2354
+ *
2355
+ * ⚠️ THE DIVERGING END IS MEASURED WHERE WE HAVE A MEASUREMENT. `divergingLength`
2356
+ * (frog → end of the diverging rail, along the rail) says exactly where that end
2357
+ * is. Without it we fall back to assuming the diverging rail leaves the moulding
2358
+ * at the same x as the through route — which is what `partExtent` has always
2359
+ * assumed, and is an assumption, not a reading. `divergingEndMeasured` says
2360
+ * which you got.
2361
+ */
2362
+ declare function partGeometry(part: TrackPart, library?: TrackPart[]): PartGeometry | null;
2363
+ /** Every part that can be PLACED on a board today, and every one that can only
2364
+ * be named — with the reason. The gap list is the parts backlog. */
2365
+ declare function partsPlaceable(library?: TrackPart[]): {
2366
+ placeable: TrackPart[];
2367
+ blocked: {
2368
+ part: TrackPart;
2369
+ why: string;
2370
+ }[];
2371
+ };
2305
2372
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2306
2373
  * points, `y` = lateral offset from the through route. */
2307
2374
  interface FrogCasting {
@@ -2358,6 +2425,62 @@ flipped?: boolean | null): -1 | 0 | 1;
2358
2425
  */
2359
2426
  declare function moduleFeatures(doc: ModuleSchematicDoc): ModuleFeatures;
2360
2427
  type GeometryType = "straight" | "corner_45" | "corner_90" | "curve" | "offset" | "dead_end" | "wye" | "other";
2428
+ /**
2429
+ * An endplate bound to a BENCHWORK EDGE (ADR 0001).
2430
+ *
2431
+ * Will Gage, 2026-07-26: *"Endplates are a part of the benchwork. We have
2432
+ * separated them causing some challenges."* A {@link SchematicEndplate.pose} is
2433
+ * a point floating in module space, so it can disagree with the board — and has:
2434
+ * a junction plate landed on the module CENTRE LINE rather than the fascia, a
2435
+ * derived pose written back silently PINNED a plate so it went stale when the
2436
+ * length changed (which is the only reason `poseAuthored` exists), and a face
2437
+ * drawn flat across a tapered edge is flush only at its midpoint.
2438
+ *
2439
+ * Bind it to an edge and none of those are expressible. Position, heading and
2440
+ * WIDTH all come from the edge, so they cannot drift apart.
2441
+ */
2442
+ interface EndplateEdge {
2443
+ /** The section whose outline owns this edge; absent = the module outline. */
2444
+ section?: string | null;
2445
+ /** Which edge: the segment from vertex `index` to vertex `index + 1`. */
2446
+ index: number;
2447
+ /** Optional span along that edge, 0…1. Absent = the whole edge — which is the
2448
+ * common case, because a board's end IS the endplate. */
2449
+ fromT?: number | null;
2450
+ toT?: number | null;
2451
+ }
2452
+ /** What an {@link EndplateEdge} resolves to. Everything here is READ OFF the
2453
+ * polygon; nothing is stored, so nothing can go stale. */
2454
+ interface EndplateEdgePose {
2455
+ x: number;
2456
+ y: number;
2457
+ /** The edge's OUTWARD normal, degrees. */
2458
+ heading: number;
2459
+ /** The span's length — the face's real width, not a separate number that
2460
+ * might disagree with the board. */
2461
+ widthInches: number;
2462
+ /** The face's two ends. On a tapered board this follows the slope, which a
2463
+ * stored pose + width could never do. */
2464
+ face: [{
2465
+ x: number;
2466
+ y: number;
2467
+ }, {
2468
+ x: number;
2469
+ y: number;
2470
+ }];
2471
+ }
2472
+ /**
2473
+ * Resolve an endplate's edge binding against a benchwork polygon.
2474
+ *
2475
+ * ⚠️ REFUSES A CURVED EDGE. Free-moN §2.0 requires the track crossing an
2476
+ * endplate to be perpendicular, straight and level for 4″ — so an endplate face
2477
+ * is flat by the standard, and a bulged edge is not somewhere one can go. That
2478
+ * is a rule, not a missing feature.
2479
+ *
2480
+ * Returns null when the edge doesn't exist or isn't usable, so a caller can say
2481
+ * why rather than drawing something wrong.
2482
+ */
2483
+ declare function endplateEdgePose(outline: BenchworkPoint[] | null | undefined, edge: EndplateEdge): EndplateEdgePose | null;
2361
2484
  interface EndplatePose {
2362
2485
  /** Endplate id — "A"/"B" axial, "C"/"D"… branch. */
2363
2486
  id: string;
@@ -2370,6 +2493,19 @@ interface EndplatePose {
2370
2493
  trackConfig: "single" | "double";
2371
2494
  /** Lateral track offsets from the crossing anchor (0 = centred single). */
2372
2495
  trackOffsets: number[];
2496
+ /** Set when this pose came from an {@link EndplateEdge} — read off the
2497
+ * benchwork rather than stored. Its width and face are the board's own. */
2498
+ boundToEdge?: boolean;
2499
+ /** The face's real width, when bound to an edge. */
2500
+ widthInches?: number;
2501
+ /** The face's two ends, when bound to an edge — follows a tapered board. */
2502
+ face?: [{
2503
+ x: number;
2504
+ y: number;
2505
+ }, {
2506
+ x: number;
2507
+ y: number;
2508
+ }];
2373
2509
  /** True when the pose was hand-entered, not derived (wye/loop/other). */
2374
2510
  manual?: boolean;
2375
2511
  }
@@ -2420,6 +2556,12 @@ interface ModuleGeometryInput {
2420
2556
  side: "up" | "down";
2421
2557
  config?: "single" | "double" | null;
2422
2558
  }[];
2559
+ /** ⭐ Endplate EDGE bindings by id (ADR 0001) — an endplate that is part of
2560
+ * the benchwork rather than a point floating beside it. Wins over
2561
+ * `poseOverrides` and over derivation. */
2562
+ endplateEdges?: Record<string, EndplateEdge>;
2563
+ /** The module's benchwork polygon, needed to resolve `endplateEdges`. */
2564
+ outline?: BenchworkPoint[] | null;
2423
2565
  /** Hand-entered pose overrides by endplate id — win over derivation. */
2424
2566
  poseOverrides?: Record<string, {
2425
2567
  x: number;
@@ -2508,4 +2650,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2508
2650
  heading: number;
2509
2651
  }>;
2510
2652
 
2511
- 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, 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_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, 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 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 SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, 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, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, 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 };
2653
+ 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 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_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, 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, 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 SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, 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, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, 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
@@ -34,6 +34,14 @@ interface SchematicEndplate {
34
34
  pos: number;
35
35
  side: "up" | "down";
36
36
  };
37
+ /** ⭐ THE ENDPLATE'S EDGE OF THE BENCHWORK (ADR 0001). When present it WINS
38
+ * over `pose` and over derivation, and position, heading and width are all
39
+ * read off the polygon — so they cannot drift apart from the board.
40
+ *
41
+ * `pose` remains for modules authored before this and for shapes with no
42
+ * benchwork polygon to bind to. Nothing is auto-converted: guessing which
43
+ * edge an owner's freehand pose meant would be inventing an intent. */
44
+ edge?: EndplateEdge | null;
37
45
  /** Manual pose override (#175 phase 1b) — the endplate's module-local track
38
46
  * point (x, y inches) + outward-normal heading (°). Hand-entered for shapes
39
47
  * the geometry fields can't derive (wye, freeform, loop); wins over
@@ -1184,6 +1192,9 @@ interface EditorState {
1184
1192
  /** Authored per-endplate TRACK offsets by id — the primary track's signed
1185
1193
  * distance from the plate centre, inches. Absent id = the §2.0 default. */
1186
1194
  endplateTrackOffsets: Record<string, number>;
1195
+ /** Endplate EDGE bindings by id (ADR 0001) — an endplate that is part of the
1196
+ * benchwork. Wins over a pose; nothing is auto-converted from one. */
1197
+ endplateEdges?: Record<string, EndplateEdge>;
1187
1198
  /** Benchwork footprint outline — polygon vertices in module-local inches
1188
1199
  * (endplate A's track point at the origin, mainline +x, perpendicular +y up).
1189
1200
  * Empty = no authored outline (fall back to the endplate-width band). */
@@ -2302,6 +2313,62 @@ declare function turnoutClosure(size: number, opts?: {
2302
2313
  * around 25″ on a #7 — clear of the Free-moN 22″ minimum. */
2303
2314
  easeInches?: number;
2304
2315
  }): TurnoutClosure;
2316
+ /** Which end of a part this is. A graph connects joints; a walk uses `routes`. */
2317
+ type PartJointRole = "throat" | "through" | "diverge" | "divergeB";
2318
+ /** One end of a part, in part-local inches: `x` along the through route from
2319
+ * the tie end, `y` lateral toward the diverging side, `angleDeg` the OUTWARD
2320
+ * tangent (0 = +x). Named, unlike {@link PartEnd}, because a graph has to know
2321
+ * a throat from a frog. */
2322
+ interface PartJoint extends PartEnd {
2323
+ id: string;
2324
+ role: PartJointRole;
2325
+ }
2326
+ interface PartGeometry {
2327
+ joints: PartJoint[];
2328
+ /** Joint id pairs a train can run between. A turnout has two routes sharing
2329
+ * its throat; flex has one; a double crossover has four. */
2330
+ routes: [string, string][];
2331
+ /** The weakest provenance among the dimensions used — so a caller can tell a
2332
+ * placed-from-measurement part from a placed-from-a-catalogue-figure one. */
2333
+ source: DimensionSource;
2334
+ /** True when the diverging end came from a measured {@link
2335
+ * TrackPart.divergingLength} rather than from assuming the diverging rail
2336
+ * leaves the moulding at the same place the through route does. */
2337
+ divergingEndMeasured: boolean;
2338
+ }
2339
+ /**
2340
+ * Why a part has no derivable geometry — null when it has.
2341
+ *
2342
+ * Worth its own function because "we cannot place this yet" is a fact an owner
2343
+ * should see in the picker, not a silent absence. It is also the parts-library
2344
+ * backlog in machine-readable form: every string this returns is a measurement
2345
+ * someone could take.
2346
+ */
2347
+ declare function partGeometryGap(part: TrackPart): string | null;
2348
+ /**
2349
+ * A part's joints and routes, in its own frame.
2350
+ *
2351
+ * The through route runs along +x from the tie end; the diverging side is +y.
2352
+ * A placed piece is this, transformed — which is the whole point: the geometry
2353
+ * belongs to the PART, and placement is a rotation and a translation.
2354
+ *
2355
+ * ⚠️ THE DIVERGING END IS MEASURED WHERE WE HAVE A MEASUREMENT. `divergingLength`
2356
+ * (frog → end of the diverging rail, along the rail) says exactly where that end
2357
+ * is. Without it we fall back to assuming the diverging rail leaves the moulding
2358
+ * at the same x as the through route — which is what `partExtent` has always
2359
+ * assumed, and is an assumption, not a reading. `divergingEndMeasured` says
2360
+ * which you got.
2361
+ */
2362
+ declare function partGeometry(part: TrackPart, library?: TrackPart[]): PartGeometry | null;
2363
+ /** Every part that can be PLACED on a board today, and every one that can only
2364
+ * be named — with the reason. The gap list is the parts backlog. */
2365
+ declare function partsPlaceable(library?: TrackPart[]): {
2366
+ placeable: TrackPart[];
2367
+ blocked: {
2368
+ part: TrackPart;
2369
+ why: string;
2370
+ }[];
2371
+ };
2305
2372
  /** A frog casting's parts, in TURNOUT-LOCAL inches: `x` = distance past the
2306
2373
  * points, `y` = lateral offset from the through route. */
2307
2374
  interface FrogCasting {
@@ -2358,6 +2425,62 @@ flipped?: boolean | null): -1 | 0 | 1;
2358
2425
  */
2359
2426
  declare function moduleFeatures(doc: ModuleSchematicDoc): ModuleFeatures;
2360
2427
  type GeometryType = "straight" | "corner_45" | "corner_90" | "curve" | "offset" | "dead_end" | "wye" | "other";
2428
+ /**
2429
+ * An endplate bound to a BENCHWORK EDGE (ADR 0001).
2430
+ *
2431
+ * Will Gage, 2026-07-26: *"Endplates are a part of the benchwork. We have
2432
+ * separated them causing some challenges."* A {@link SchematicEndplate.pose} is
2433
+ * a point floating in module space, so it can disagree with the board — and has:
2434
+ * a junction plate landed on the module CENTRE LINE rather than the fascia, a
2435
+ * derived pose written back silently PINNED a plate so it went stale when the
2436
+ * length changed (which is the only reason `poseAuthored` exists), and a face
2437
+ * drawn flat across a tapered edge is flush only at its midpoint.
2438
+ *
2439
+ * Bind it to an edge and none of those are expressible. Position, heading and
2440
+ * WIDTH all come from the edge, so they cannot drift apart.
2441
+ */
2442
+ interface EndplateEdge {
2443
+ /** The section whose outline owns this edge; absent = the module outline. */
2444
+ section?: string | null;
2445
+ /** Which edge: the segment from vertex `index` to vertex `index + 1`. */
2446
+ index: number;
2447
+ /** Optional span along that edge, 0…1. Absent = the whole edge — which is the
2448
+ * common case, because a board's end IS the endplate. */
2449
+ fromT?: number | null;
2450
+ toT?: number | null;
2451
+ }
2452
+ /** What an {@link EndplateEdge} resolves to. Everything here is READ OFF the
2453
+ * polygon; nothing is stored, so nothing can go stale. */
2454
+ interface EndplateEdgePose {
2455
+ x: number;
2456
+ y: number;
2457
+ /** The edge's OUTWARD normal, degrees. */
2458
+ heading: number;
2459
+ /** The span's length — the face's real width, not a separate number that
2460
+ * might disagree with the board. */
2461
+ widthInches: number;
2462
+ /** The face's two ends. On a tapered board this follows the slope, which a
2463
+ * stored pose + width could never do. */
2464
+ face: [{
2465
+ x: number;
2466
+ y: number;
2467
+ }, {
2468
+ x: number;
2469
+ y: number;
2470
+ }];
2471
+ }
2472
+ /**
2473
+ * Resolve an endplate's edge binding against a benchwork polygon.
2474
+ *
2475
+ * ⚠️ REFUSES A CURVED EDGE. Free-moN §2.0 requires the track crossing an
2476
+ * endplate to be perpendicular, straight and level for 4″ — so an endplate face
2477
+ * is flat by the standard, and a bulged edge is not somewhere one can go. That
2478
+ * is a rule, not a missing feature.
2479
+ *
2480
+ * Returns null when the edge doesn't exist or isn't usable, so a caller can say
2481
+ * why rather than drawing something wrong.
2482
+ */
2483
+ declare function endplateEdgePose(outline: BenchworkPoint[] | null | undefined, edge: EndplateEdge): EndplateEdgePose | null;
2361
2484
  interface EndplatePose {
2362
2485
  /** Endplate id — "A"/"B" axial, "C"/"D"… branch. */
2363
2486
  id: string;
@@ -2370,6 +2493,19 @@ interface EndplatePose {
2370
2493
  trackConfig: "single" | "double";
2371
2494
  /** Lateral track offsets from the crossing anchor (0 = centred single). */
2372
2495
  trackOffsets: number[];
2496
+ /** Set when this pose came from an {@link EndplateEdge} — read off the
2497
+ * benchwork rather than stored. Its width and face are the board's own. */
2498
+ boundToEdge?: boolean;
2499
+ /** The face's real width, when bound to an edge. */
2500
+ widthInches?: number;
2501
+ /** The face's two ends, when bound to an edge — follows a tapered board. */
2502
+ face?: [{
2503
+ x: number;
2504
+ y: number;
2505
+ }, {
2506
+ x: number;
2507
+ y: number;
2508
+ }];
2373
2509
  /** True when the pose was hand-entered, not derived (wye/loop/other). */
2374
2510
  manual?: boolean;
2375
2511
  }
@@ -2420,6 +2556,12 @@ interface ModuleGeometryInput {
2420
2556
  side: "up" | "down";
2421
2557
  config?: "single" | "double" | null;
2422
2558
  }[];
2559
+ /** ⭐ Endplate EDGE bindings by id (ADR 0001) — an endplate that is part of
2560
+ * the benchwork rather than a point floating beside it. Wins over
2561
+ * `poseOverrides` and over derivation. */
2562
+ endplateEdges?: Record<string, EndplateEdge>;
2563
+ /** The module's benchwork polygon, needed to resolve `endplateEdges`. */
2564
+ outline?: BenchworkPoint[] | null;
2423
2565
  /** Hand-entered pose overrides by endplate id — win over derivation. */
2424
2566
  poseOverrides?: Record<string, {
2425
2567
  x: number;
@@ -2508,4 +2650,4 @@ declare function poseOverridesFromDoc(doc: ModuleSchematicDoc): Record<string, {
2508
2650
  heading: number;
2509
2651
  }>;
2510
2652
 
2511
- 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, 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_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, 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 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 SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, 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, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, 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 };
2653
+ 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 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_TRACK_SPACING_INCHES, type FlexPiece, type FlexPieceEnd, type FrogCasting, type GeometryType, type ImportedPart, type IndustryLabelMode, type IndustrySpot, 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, 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 SectionEnd, type SectionEndAssessment, type SectionFootprint, type SectionJointAssessment, 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, assessSectionEnd, assessSectionJoint, benchworkBand, benchworkOutline, buildCrossover, buildPassingSiding, buildTransition, carCapacity, checkEndplateWidth, clearancePointPastFrogInches, crossoverPinches, deriveEndplatePoses, divergeSideForHand, docToState, emptyEditorState, endplateCentreOffsetInches, endplateEdgePose, endplateFaceSegments, endplateLead, endplateTrackOffsetInches, endplateWidthInches, flexPartFor, flexParts, flexPieces, flexUsage, frogCasting, frogNumberFromName, fromSectionRelative, geometryTurnDegrees, hasNoFarEndplate, importedPartToTrackPart, inchesToScaleFeet, isLoopDoc, isTransitionTurnout, laneOffsetAt, leadInchesForSize, maxFlexPieceInches, mergeImportedParts, mergeStoredParts, moduleCenterline, moduleFeatures, moduleFootprint, moduleLengthFromSections, moduleSections, nextId, parseXtpLibrary, partExtent, partExtentForSize, partGeometry, partGeometryGap, partOutlineAtFrog, partsPlaceable, 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 };