@willcgage/module-schematic 0.55.0 → 0.56.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
@@ -1035,18 +1035,24 @@ interface ModuleFeatures {
1035
1035
  /** N-scale track gauge, inches (9 mm). */
1036
1036
  declare const RAIL_GAUGE_INCHES = 0.354;
1037
1037
  /**
1038
- * ⚠️ **REFUTED AS A GENERAL RULE LAST RESORT ONLY.** Points→frog for a #1
1039
- * frog, i.e. a turnout's LEAD if lead were proportional to frog number. From an
1040
- * Atlas code 55 #7 measuring 3⅜″ (Steve Branton, #173) ⇒ 3.375 / 7.
1038
+ * ⚠️⚠️ **DEAD REFUTED *AND* BASELESS. NOTHING SHOULD REACH THIS.** Kept only
1039
+ * so a library with no measured leads at all still returns a number, and as a
1040
+ * record of how the mistake was made.
1041
1041
  *
1042
- * It is NOT proportional. Measured lead ÷ N: **#5 = 0.600, #7 = 0.482,
1043
- * #10 = 0.494.** This rule reads ~20% SHORT at N=5 (2.41″ vs a measured 3.00″).
1042
+ * It was points→frog for a #1 frog: a turnout's LEAD if lead were proportional
1043
+ * to frog number, from an Atlas #7 measuring 3⅜″ (Steve Branton, #173)
1044
+ * 3.375 / 7.
1044
1045
  *
1045
- * Prefer {@link leadInchesForSize}, which returns a real part's measurement when
1046
- * one exists and only falls back here for sizes nothing in the library covers.
1047
- * ⚠️ The remaining fallback user is the #2.5 wye at 1.21″; since per-frog is
1048
- * HIGHEST at the low end (0.600 at N=5), that is very likely SHORT. It is a
1049
- * placeholder, not a value.
1046
+ * TWO independent things are wrong with it:
1047
+ * 1. **Lead is not proportional to N.** Measured lead ÷ N: #5 = 0.600,
1048
+ * #7 = 0.513, #10 = 0.494. It reads ~20% SHORT at N=5.
1049
+ * 2. **Its founding measurement was superseded.** Will Gage re-measured the same
1050
+ * part at 3¹⁹⁄₃₂″, ⁷⁄₃₂″ longer, confirmed against his own points and frog
1051
+ * positions. So 0.482 is not even the right constant for the wrong model.
1052
+ *
1053
+ * {@link leadInchesForSize} now INTERPOLATES across the measured parts instead.
1054
+ * A single measurement can only ever be scaled — which is this rule — so the
1055
+ * lesson is that one data point cannot support a shape, only a value.
1050
1056
  *
1051
1057
  * A constant-switch-angle model was also proposed here and is likewise REFUTED:
1052
1058
  * inverting {@link turnoutClosure} gives α = 0.036 (#5), 0.067 (#7), 0.043
@@ -1135,18 +1141,23 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
1135
1141
  *
1136
1142
  * MEASURED GEOMETRY (all Will Gage's, off physical parts, frog taken at the V):
1137
1143
  *
1138
- * part points frog V overall lead past frog ÷N
1139
- * #5 1¾″ 4.75″ 6.00″ 3.0000″ 1.25″ 0.2500
1140
- * #7 4³⁄₁₆″ 6.00″ 3.3750″* 1.8125 0.2589
1141
- * #10 ⁹⁄₁₆″ 5.50″ 8.00″ 4.9375″ 2.50″ 0.2500
1144
+ * part points frog V overall lead past frog ÷N
1145
+ * #5 1¾″ 4.75″ 6.00″ 3.00000″ 1.25″ 0.2500
1146
+ * #7 ⁵⁄₈″ 4⁷⁄₃₂″ 6.00″ 3.59375″ 1.78125 0.2545
1147
+ * #10 ⁹⁄₁₆″ 5.50″ 8.00″ 4.93750″ 2.50″ 0.2500
1142
1148
  *
1143
- * * the #7's lead is Steve Branton's (#173), a DIFFERENT person and possibly
1144
- * a different convention. Its points offset has never been measured, so
1145
- * 4³⁄₁₆ − 3⅜ = ¹³⁄₁₆″ is implied, not observed. Measure a 2052 to close it.
1149
+ * Every one of these is Will Gage's, off a physical part, frog taken at the V.
1150
+ * ⚠️ The #7 lead SUPERSEDES Steve Branton's 3⅜″ (#173) — the library's founding
1151
+ * measurement, ⁷⁄₃₂″ short. Will's single-span reading and his two positions
1152
+ * agree exactly, which is why it wins. Steve's number was also the sole basis of
1153
+ * {@link TURNOUT_LEAD_INCHES_PER_FROG}; that constant is now baseless as well as
1154
+ * refuted, and {@link leadInchesForSize} interpolates across this table instead.
1146
1155
  *
1147
1156
  * ⭐ **THE PART RUNS PAST THE FROG FAR ENOUGH TO GAIN 0.25″ OF SEPARATION.**
1148
- * Past-frog run ÷ N is 0.2500 / 0.2589 / 0.2500 — the #5 and #10 EXACT, the #7
1149
- * within ¹⁄₁₆″ of the tie end (a 4¼″ frog would make it exact too). At the frog
1157
+ * Past-frog run ÷ N is 0.2500 / 0.2545 / 0.2500 — the #5 and #10 EXACT, the #7
1158
+ * within ¹⁄₃₂″ of the tie end (a 4¼″ frog would make it exact too, one tape
1159
+ * division away). Provenance note: the measurer was told that prediction BEFORE
1160
+ * re-reading and did not return 4¼″, so this is not a confirmation artefact. At the frog
1150
1161
  * the routes are one gauge apart; at the end of the moulding they are
1151
1162
  * 0.354 + 0.25 ≈ 0.6″ apart. So Atlas size the moulding for a constant
1152
1163
  * CLEARANCE, not a constant length — which is what a manufacturer would
@@ -1158,10 +1169,11 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
1158
1169
  *
1159
1170
  * NOTHING HERE IS PROPORTIONAL TO FROG NUMBER. Three rules assumed otherwise;
1160
1171
  * all three were tested against a physical part and FAILED:
1161
- * - ❌ **Lead ∝ N.** Measured lead ÷ N: #5 = 0.600, #7 = 0.482, #10 = 0.494.
1162
- * {@link TURNOUT_LEAD_INCHES_PER_FROG} reads ~20% SHORT at N=5.
1172
+ * - ❌ **Lead ∝ N.** Measured lead ÷ N: #5 = 0.600, #7 = 0.513, #10 = 0.494.
1173
+ * {@link TURNOUT_LEAD_INCHES_PER_FROG} reads ~20% SHORT at N=5 — and its
1174
+ * founding measurement has since been superseded, so it is baseless too.
1163
1175
  * - ❌ **Constant switch angle.** Inverting {@link turnoutClosure} gives α =
1164
- * 0.036 (#5), 0.067 (#7), 0.043 (#10). Not constant.
1176
+ * 0.036 (#5), 0.055 (#7), 0.043 (#10). Not constant.
1165
1177
  * - ❌ **Overall length ∝ N.** #5 = 6.00″ and #7 = 6.00″ — same moulding, two
1166
1178
  * frog numbers.
1167
1179
  * - ❌ **Frog fixed at 4.75″** (shipped in 0.54.0, retracted in 0.55.0). It
@@ -1182,9 +1194,19 @@ declare function trackPart(id: string, library?: TrackPart[]): TrackPart | null;
1182
1194
  /** The closest built-in turnout for a frog number — what a bare `size` maps to
1183
1195
  * when a turnout names no part. Exact match wins; otherwise the nearest frog. */
1184
1196
  declare function turnoutPartForSize(size: number, library?: TrackPart[]): TrackPart | null;
1185
- /** The lead to draw a turnout of this size with — a real part's measurement when
1186
- * we have one, else the per-frog rule. Keeps `frogLegOf` honest without it
1187
- * needing to know the library exists. */
1197
+ /**
1198
+ * The lead to draw a turnout of this size with. Keeps `frogLegOf` honest without
1199
+ * it needing to know the library exists.
1200
+ *
1201
+ * 1. An exact part match wins — a #7's measurement says nothing about a #4.
1202
+ * 2. Otherwise INTERPOLATE piecewise-linearly across the measured parts. This
1203
+ * replaced `size × TURNOUT_LEAD_INCHES_PER_FROG`, which was refuted (its
1204
+ * error changes sign across the measured range) and is now baseless besides,
1205
+ * its founding #7 measurement having been superseded.
1206
+ * 3. ⚠️ Outside the measured range the end segments EXTRAPOLATE. Measured leads
1207
+ * span N = 5…10, so a #4 or #12 is a projection, not a reading, and the #2.5
1208
+ * wye is a long way out. Treat those as placeholders until a part is measured.
1209
+ */
1188
1210
  declare function leadInchesForSize(size: number, library?: TrackPart[]): number;
1189
1211
  /** One drawn piece of a part's geometry, in the part's own local frame. */
1190
1212
  type PartSegment = {
package/dist/index.d.ts CHANGED
@@ -1035,18 +1035,24 @@ interface ModuleFeatures {
1035
1035
  /** N-scale track gauge, inches (9 mm). */
1036
1036
  declare const RAIL_GAUGE_INCHES = 0.354;
1037
1037
  /**
1038
- * ⚠️ **REFUTED AS A GENERAL RULE LAST RESORT ONLY.** Points→frog for a #1
1039
- * frog, i.e. a turnout's LEAD if lead were proportional to frog number. From an
1040
- * Atlas code 55 #7 measuring 3⅜″ (Steve Branton, #173) ⇒ 3.375 / 7.
1038
+ * ⚠️⚠️ **DEAD REFUTED *AND* BASELESS. NOTHING SHOULD REACH THIS.** Kept only
1039
+ * so a library with no measured leads at all still returns a number, and as a
1040
+ * record of how the mistake was made.
1041
1041
  *
1042
- * It is NOT proportional. Measured lead ÷ N: **#5 = 0.600, #7 = 0.482,
1043
- * #10 = 0.494.** This rule reads ~20% SHORT at N=5 (2.41″ vs a measured 3.00″).
1042
+ * It was points→frog for a #1 frog: a turnout's LEAD if lead were proportional
1043
+ * to frog number, from an Atlas #7 measuring 3⅜″ (Steve Branton, #173)
1044
+ * 3.375 / 7.
1044
1045
  *
1045
- * Prefer {@link leadInchesForSize}, which returns a real part's measurement when
1046
- * one exists and only falls back here for sizes nothing in the library covers.
1047
- * ⚠️ The remaining fallback user is the #2.5 wye at 1.21″; since per-frog is
1048
- * HIGHEST at the low end (0.600 at N=5), that is very likely SHORT. It is a
1049
- * placeholder, not a value.
1046
+ * TWO independent things are wrong with it:
1047
+ * 1. **Lead is not proportional to N.** Measured lead ÷ N: #5 = 0.600,
1048
+ * #7 = 0.513, #10 = 0.494. It reads ~20% SHORT at N=5.
1049
+ * 2. **Its founding measurement was superseded.** Will Gage re-measured the same
1050
+ * part at 3¹⁹⁄₃₂″, ⁷⁄₃₂″ longer, confirmed against his own points and frog
1051
+ * positions. So 0.482 is not even the right constant for the wrong model.
1052
+ *
1053
+ * {@link leadInchesForSize} now INTERPOLATES across the measured parts instead.
1054
+ * A single measurement can only ever be scaled — which is this rule — so the
1055
+ * lesson is that one data point cannot support a shape, only a value.
1050
1056
  *
1051
1057
  * A constant-switch-angle model was also proposed here and is likewise REFUTED:
1052
1058
  * inverting {@link turnoutClosure} gives α = 0.036 (#5), 0.067 (#7), 0.043
@@ -1135,18 +1141,23 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
1135
1141
  *
1136
1142
  * MEASURED GEOMETRY (all Will Gage's, off physical parts, frog taken at the V):
1137
1143
  *
1138
- * part points frog V overall lead past frog ÷N
1139
- * #5 1¾″ 4.75″ 6.00″ 3.0000″ 1.25″ 0.2500
1140
- * #7 4³⁄₁₆″ 6.00″ 3.3750″* 1.8125 0.2589
1141
- * #10 ⁹⁄₁₆″ 5.50″ 8.00″ 4.9375″ 2.50″ 0.2500
1144
+ * part points frog V overall lead past frog ÷N
1145
+ * #5 1¾″ 4.75″ 6.00″ 3.00000″ 1.25″ 0.2500
1146
+ * #7 ⁵⁄₈″ 4⁷⁄₃₂″ 6.00″ 3.59375″ 1.78125 0.2545
1147
+ * #10 ⁹⁄₁₆″ 5.50″ 8.00″ 4.93750″ 2.50″ 0.2500
1142
1148
  *
1143
- * * the #7's lead is Steve Branton's (#173), a DIFFERENT person and possibly
1144
- * a different convention. Its points offset has never been measured, so
1145
- * 4³⁄₁₆ − 3⅜ = ¹³⁄₁₆″ is implied, not observed. Measure a 2052 to close it.
1149
+ * Every one of these is Will Gage's, off a physical part, frog taken at the V.
1150
+ * ⚠️ The #7 lead SUPERSEDES Steve Branton's 3⅜″ (#173) — the library's founding
1151
+ * measurement, ⁷⁄₃₂″ short. Will's single-span reading and his two positions
1152
+ * agree exactly, which is why it wins. Steve's number was also the sole basis of
1153
+ * {@link TURNOUT_LEAD_INCHES_PER_FROG}; that constant is now baseless as well as
1154
+ * refuted, and {@link leadInchesForSize} interpolates across this table instead.
1146
1155
  *
1147
1156
  * ⭐ **THE PART RUNS PAST THE FROG FAR ENOUGH TO GAIN 0.25″ OF SEPARATION.**
1148
- * Past-frog run ÷ N is 0.2500 / 0.2589 / 0.2500 — the #5 and #10 EXACT, the #7
1149
- * within ¹⁄₁₆″ of the tie end (a 4¼″ frog would make it exact too). At the frog
1157
+ * Past-frog run ÷ N is 0.2500 / 0.2545 / 0.2500 — the #5 and #10 EXACT, the #7
1158
+ * within ¹⁄₃₂″ of the tie end (a 4¼″ frog would make it exact too, one tape
1159
+ * division away). Provenance note: the measurer was told that prediction BEFORE
1160
+ * re-reading and did not return 4¼″, so this is not a confirmation artefact. At the frog
1150
1161
  * the routes are one gauge apart; at the end of the moulding they are
1151
1162
  * 0.354 + 0.25 ≈ 0.6″ apart. So Atlas size the moulding for a constant
1152
1163
  * CLEARANCE, not a constant length — which is what a manufacturer would
@@ -1158,10 +1169,11 @@ declare const CODE55_RAIL_HEIGHT_INCHES = 0.055;
1158
1169
  *
1159
1170
  * NOTHING HERE IS PROPORTIONAL TO FROG NUMBER. Three rules assumed otherwise;
1160
1171
  * all three were tested against a physical part and FAILED:
1161
- * - ❌ **Lead ∝ N.** Measured lead ÷ N: #5 = 0.600, #7 = 0.482, #10 = 0.494.
1162
- * {@link TURNOUT_LEAD_INCHES_PER_FROG} reads ~20% SHORT at N=5.
1172
+ * - ❌ **Lead ∝ N.** Measured lead ÷ N: #5 = 0.600, #7 = 0.513, #10 = 0.494.
1173
+ * {@link TURNOUT_LEAD_INCHES_PER_FROG} reads ~20% SHORT at N=5 — and its
1174
+ * founding measurement has since been superseded, so it is baseless too.
1163
1175
  * - ❌ **Constant switch angle.** Inverting {@link turnoutClosure} gives α =
1164
- * 0.036 (#5), 0.067 (#7), 0.043 (#10). Not constant.
1176
+ * 0.036 (#5), 0.055 (#7), 0.043 (#10). Not constant.
1165
1177
  * - ❌ **Overall length ∝ N.** #5 = 6.00″ and #7 = 6.00″ — same moulding, two
1166
1178
  * frog numbers.
1167
1179
  * - ❌ **Frog fixed at 4.75″** (shipped in 0.54.0, retracted in 0.55.0). It
@@ -1182,9 +1194,19 @@ declare function trackPart(id: string, library?: TrackPart[]): TrackPart | null;
1182
1194
  /** The closest built-in turnout for a frog number — what a bare `size` maps to
1183
1195
  * when a turnout names no part. Exact match wins; otherwise the nearest frog. */
1184
1196
  declare function turnoutPartForSize(size: number, library?: TrackPart[]): TrackPart | null;
1185
- /** The lead to draw a turnout of this size with — a real part's measurement when
1186
- * we have one, else the per-frog rule. Keeps `frogLegOf` honest without it
1187
- * needing to know the library exists. */
1197
+ /**
1198
+ * The lead to draw a turnout of this size with. Keeps `frogLegOf` honest without
1199
+ * it needing to know the library exists.
1200
+ *
1201
+ * 1. An exact part match wins — a #7's measurement says nothing about a #4.
1202
+ * 2. Otherwise INTERPOLATE piecewise-linearly across the measured parts. This
1203
+ * replaced `size × TURNOUT_LEAD_INCHES_PER_FROG`, which was refuted (its
1204
+ * error changes sign across the measured range) and is now baseless besides,
1205
+ * its founding #7 measurement having been superseded.
1206
+ * 3. ⚠️ Outside the measured range the end segments EXTRAPOLATE. Measured leads
1207
+ * span N = 5…10, so a #4 or #12 is a projection, not a reading, and the #2.5
1208
+ * wye is a long way out. Treat those as placeholders until a part is measured.
1209
+ */
1188
1210
  declare function leadInchesForSize(size: number, library?: TrackPart[]): number;
1189
1211
  /** One drawn piece of a part's geometry, in the part's own local frame. */
1190
1212
  type PartSegment = {
package/dist/index.js CHANGED
@@ -1137,14 +1137,19 @@ var ATLAS_CODE55_N = [
1137
1137
  partNumbers: { left: "2052", right: "2053" },
1138
1138
  frogNumber: 7,
1139
1139
  lead: {
1140
- inches: 3.375,
1140
+ inches: 3.59375,
1141
1141
  source: "measured",
1142
- note: "Steve Branton, physical Atlas code 55 #7, 3\u215C\u2033 points\u2192frog (#173)"
1142
+ note: "Will Gage, physical Atlas 2052 (#7), 3\xB9\u2079\u2044\u2083\u2082\u2033 points\u2192frog as a single span \u2014 and it matches his two positions exactly (4\u2077\u2044\u2083\u2082 \u2212 \u2075\u2044\u2088), so the reading is internally consistent. SUPERSEDES Steve Branton's 3\u215C\u2033 (#173), which was \u2077\u2044\u2083\u2082\u2033 short and was the library's founding measurement. \u26A0\uFE0F Steve's number was also the SOLE basis of TURNOUT_LEAD_INCHES_PER_FROG (3.375/7 = 0.482) \u2014 that constant is now baseless as well as refuted."
1143
+ },
1144
+ pointsOffset: {
1145
+ inches: 0.625,
1146
+ source: "measured",
1147
+ note: "Will Gage, physical Atlas 2052 (#7) \u2014 \xB9\u2070\u2044\u2081\u2086\u2033 tie end to point tips"
1143
1148
  },
1144
1149
  frogOffset: {
1145
- inches: 4.1875,
1150
+ inches: 4.21875,
1146
1151
  source: "measured",
1147
- note: "Will Gage, physical Atlas 2052 (#7) \u2014 tie end to the apex of the V"
1152
+ note: "Will Gage, physical Atlas 2052 (#7) \u2014 4\u2077\u2044\u2083\u2082\u2033, tie end to the apex of the V. Supersedes an initial 4\xB3\u2044\u2081\u2086\u2033. Worth noting for provenance: he was told beforehand that the clearance hypothesis predicted 4\xBC\u2033 and did NOT read 4\xBC\u2033 \u2014 so this number is not a confirmation artefact, and it sits \xB9\u2044\u2083\u2082\u2033 (one tape division) from the predicted value."
1148
1153
  },
1149
1154
  overallLength: {
1150
1155
  inches: 6,
@@ -1230,10 +1235,24 @@ function turnoutPartForSize(size, library = BUILT_IN_TRACK_PARTS) {
1230
1235
  (best, p) => Math.abs(p.frogNumber - size) < Math.abs(best.frogNumber - size) ? p : best
1231
1236
  );
1232
1237
  }
1238
+ function measuredLeadPoints(library) {
1239
+ return library.filter(
1240
+ (p) => p.kind === "turnout" && p.frogNumber != null && p.lead?.source === "measured"
1241
+ ).map((p) => ({ n: p.frogNumber, lead: p.lead.inches })).sort((a, b) => a.n - b.n);
1242
+ }
1233
1243
  function leadInchesForSize(size, library = BUILT_IN_TRACK_PARTS) {
1234
1244
  const part = turnoutPartForSize(size, library);
1235
1245
  if (part?.lead && part.frogNumber === size) return part.lead.inches;
1236
- return size * TURNOUT_LEAD_INCHES_PER_FROG;
1246
+ const pts = measuredLeadPoints(library);
1247
+ if (!pts.length) return size * TURNOUT_LEAD_INCHES_PER_FROG;
1248
+ if (pts.length === 1) return size / pts[0].n * pts[0].lead;
1249
+ let i;
1250
+ if (size >= pts[pts.length - 1].n) i = pts.length - 2;
1251
+ else i = Math.max(0, pts.findIndex((p) => p.n >= size) - 1);
1252
+ const lo = pts[i];
1253
+ const hi = pts[i + 1];
1254
+ const t = (size - lo.n) / (hi.n - lo.n);
1255
+ return lo.lead + t * (hi.lead - lo.lead);
1237
1256
  }
1238
1257
  function parseXtpLibrary(text) {
1239
1258
  const parts = [];