@willcgage/module-schematic 0.24.0 → 0.26.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
@@ -201,6 +201,10 @@ interface ModuleSchematicDoc {
201
201
  * tracks unrolled as a ladder past the throat — the default when inLoop
202
202
  * tracks exist), "geometric" (drawn balloon, AL&E-style). */
203
203
  loopRender?: "bulb" | "fan" | "geometric";
204
+ /** The two mains' positions are swapped: Main 1 draws above (lane 1), Main 2
205
+ * on the centre line (lane 0). Absent/false = the default (Main 1 below).
206
+ * Identities are unchanged — only which lane each is drawn in (#FMN-0043). */
207
+ mainsSwapped?: boolean;
204
208
  endplates: SchematicEndplate[];
205
209
  tracks: SchematicTrack[];
206
210
  turnouts?: SchematicTurnout[];
@@ -429,6 +433,11 @@ interface EditorState {
429
433
  loopReturn: "same" | "main2";
430
434
  configA: TrackConfig;
431
435
  configB: EndplateBConfig;
436
+ /** Swap the two mains' POSITIONS: Main 1 draws above (lane 1) and Main 2 on
437
+ * the centre line (lane 0). The module decides which physical track is which
438
+ * main — on some modules the upper track is the through/primary main
439
+ * (#FMN-0043). Identities and references are unchanged; only the lanes swap. */
440
+ mainsSwapped: boolean;
432
441
  extraTracks: EditorTrack[];
433
442
  turnouts: EditorTurnout[];
434
443
  /** Grade crossings / diamonds (#170). */
package/dist/index.d.ts CHANGED
@@ -201,6 +201,10 @@ interface ModuleSchematicDoc {
201
201
  * tracks unrolled as a ladder past the throat — the default when inLoop
202
202
  * tracks exist), "geometric" (drawn balloon, AL&E-style). */
203
203
  loopRender?: "bulb" | "fan" | "geometric";
204
+ /** The two mains' positions are swapped: Main 1 draws above (lane 1), Main 2
205
+ * on the centre line (lane 0). Absent/false = the default (Main 1 below).
206
+ * Identities are unchanged — only which lane each is drawn in (#FMN-0043). */
207
+ mainsSwapped?: boolean;
204
208
  endplates: SchematicEndplate[];
205
209
  tracks: SchematicTrack[];
206
210
  turnouts?: SchematicTurnout[];
@@ -429,6 +433,11 @@ interface EditorState {
429
433
  loopReturn: "same" | "main2";
430
434
  configA: TrackConfig;
431
435
  configB: EndplateBConfig;
436
+ /** Swap the two mains' POSITIONS: Main 1 draws above (lane 1) and Main 2 on
437
+ * the centre line (lane 0). The module decides which physical track is which
438
+ * main — on some modules the upper track is the through/primary main
439
+ * (#FMN-0043). Identities and references are unchanged; only the lanes swap. */
440
+ mainsSwapped: boolean;
432
441
  extraTracks: EditorTrack[];
433
442
  turnouts: EditorTurnout[];
434
443
  /** Grade crossings / diamonds (#170). */
package/dist/index.js CHANGED
@@ -208,6 +208,7 @@ function emptyEditorState(lengthInches) {
208
208
  loopReturn: "same",
209
209
  configA: "single",
210
210
  configB: "single",
211
+ mainsSwapped: false,
211
212
  extraTracks: [],
212
213
  turnouts: [],
213
214
  crossings: [],
@@ -224,18 +225,35 @@ function emptyEditorState(lengthInches) {
224
225
  function isTransitionTurnout(t) {
225
226
  return t.onTrack === MAIN_TRACK_ID && t.divergeTrack === MAIN2_TRACK_ID || t.onTrack === MAIN2_TRACK_ID && t.divergeTrack === MAIN_TRACK_ID;
226
227
  }
228
+ function main1Track(state) {
229
+ const bothDouble = state.configA === "double" && state.configB === "double";
230
+ const isDouble = state.configA === "double" || state.configB === "double";
231
+ const sw = state.turnouts.find(isTransitionTurnout);
232
+ const lane = state.mainsSwapped && isDouble ? 1 : 0;
233
+ if (!isDouble || bothDouble || !sw || sw.onTrack !== MAIN2_TRACK_ID) {
234
+ return { id: MAIN_TRACK_ID, role: "main", lane, from: "A", to: "B" };
235
+ }
236
+ return state.configA === "double" ? (
237
+ // Double at A: Main 1 runs from A and ends at the turnout.
238
+ { id: MAIN_TRACK_ID, role: "main", lane, fromPos: 0, toPos: sw.pos }
239
+ ) : (
240
+ // Double at B: Main 1 begins at the turnout and runs to B.
241
+ { id: MAIN_TRACK_ID, role: "main", lane, fromPos: sw.pos, toPos: state.lengthInches }
242
+ );
243
+ }
227
244
  function main2Track(state) {
228
245
  const bothDouble = state.configA === "double" && state.configB === "double";
229
246
  const sw = state.turnouts.find(isTransitionTurnout);
247
+ const lane = state.mainsSwapped ? 0 : 1;
230
248
  if (bothDouble || !sw || sw.divergeTrack !== MAIN2_TRACK_ID) {
231
- return { id: MAIN2_TRACK_ID, role: "main", lane: 1, from: "A", to: "B" };
249
+ return { id: MAIN2_TRACK_ID, role: "main", lane, from: "A", to: "B" };
232
250
  }
233
251
  return state.configA === "double" ? (
234
252
  // Double at A: Main 2 runs from A and ends at the turnout.
235
- { id: MAIN2_TRACK_ID, role: "main", lane: 1, fromPos: 0, toPos: sw.pos }
253
+ { id: MAIN2_TRACK_ID, role: "main", lane, fromPos: 0, toPos: sw.pos }
236
254
  ) : (
237
255
  // Double at B: Main 2 begins at the turnout and runs to B.
238
- { id: MAIN2_TRACK_ID, role: "main", lane: 1, fromPos: sw.pos, toPos: state.lengthInches }
256
+ { id: MAIN2_TRACK_ID, role: "main", lane, fromPos: sw.pos, toPos: state.lengthInches }
239
257
  );
240
258
  }
241
259
  function buildTransition(state) {
@@ -292,6 +310,7 @@ function stateToDoc(state, recordNumber) {
292
310
  lengthInches: state.lengthInches,
293
311
  ...state.loop ? { loop: true } : {},
294
312
  ...state.loop && state.loopReturn === "main2" ? { loopReturn: "main2" } : {},
313
+ ...state.mainsSwapped ? { mainsSwapped: true } : {},
295
314
  endplates: withWidths(
296
315
  withPoses(
297
316
  [
@@ -336,7 +355,11 @@ function stateToDoc(state, recordNumber) {
336
355
  state.loop ? (
337
356
  // The main runs the lead from A and turns back at the balloon.
338
357
  { id: MAIN_TRACK_ID, role: "main", lane: 0, fromPos: 0, toPos: state.lengthInches }
339
- ) : { id: MAIN_TRACK_ID, role: "main", lane: 0, from: "A", to: "B" },
358
+ ) : (
359
+ // Normally A→B; partial when MAIN 2 is the through main and Main 1 is
360
+ // the one that ends at the transition turnout (#FMN-0043).
361
+ main1Track(state)
362
+ ),
340
363
  // Double track: Main 2 is a real entity so turnouts/signals can attach.
341
364
  // On a loop it exists only for a Main 2 directional return (the U joins
342
365
  // the two lanes at the balloon); a same-main loop's parallel lead legs
@@ -499,6 +522,7 @@ function docToState(doc, fallbackLength, moduleTracks = []) {
499
522
  lengthInches: len,
500
523
  loop,
501
524
  loopReturn: loop && d.loopReturn === "main2" ? "main2" : "same",
525
+ mainsSwapped: d.mainsSwapped === true,
502
526
  configA: configOf("A"),
503
527
  // On a loop, a missing B means pure turnback; present = interchange loop.
504
528
  configB: loop && !hasB ? "none" : configOf("B"),