Skip to content

Inter-section dispatch table

Inter-section edges (port/junction to port/junction) are routed by _route_inter_section in routing/inter_section_handlers.py. It chooses the route’s shape from a declarative table, _INTER_SECTION_RULES, rather than a hand-written if-ladder: one _InterFacts snapshot of the edge is matched against an ordered list of named rules, and the first whose predicate holds owns the route.

Every rule’s handler builds its route from a centreline through the bundle builder (build_concentric_bundle / build_tapered_bundle), so no handler assembles per-line points or corner radii by hand. The runtime curve guard (below) is a backstop, not the mechanism that keeps the routes correct.

_InterFacts resolves the geometry and topology each rule keys on:

  • Relative position - the source and target grid columns and rows (src_col/row, tgt_col/row), and the derived same_y, same_x, same_col, cross_row, and needs_bypass (a multi-column hop with an intervening section in the source or target row).
  • Exit side - whether the source is a LEFT/RIGHT exit port, a TOP/BOTTOM perpendicular exit (is_perp_exit), a TB BOTTOM exit (is_tb_bottom_exit, or is_tb_perp_exit_to_side when it feeds a not-below side entry), or a junction.
  • Entry side - entry_side is the target entry port’s side (LEFT/RIGHT/TOP/BOTTOM) or None when the target is a junction; merge_ep is the resolved entry-port station when the target is a merge junction.

The table is ordered: earlier rules shadow later ones, so the order encodes the precedence between overlapping cases. The first matching rule wins; if none match, the standard L-shape is the fall-through.

#RuleFires whenRoute
1perp-exitsource is a TOP/BOTTOM exit on a horizontal-flow section_route_perp_exit (column-aligned drop, else up-and-over). Before the same-Y rule: an exit and entry sharing an edge Y would graze both boxes on a straight run.
2TB perp-exit overBOTTOM exit on a TB/BT section feeding a side (LEFT/RIGHT) entry at or above the exit Y_route_perp_exit_over (down-and-over). The exit sits on the section’s bottom edge, so before the same-Y rule for the same graze reason as rule 1: a downward drop cannot reach a not-below side entry and a straight run rides the edge out through the corner.
3same-Y straightsame_y, not needs_bypass, not a right-entry ploughstraight horizontal
4TB bottom exitBOTTOM exit on a TB/BT section, with station offsets_route_tb_bottom_exit (drop / jog into a target below)
5TOP entry L-shapeentry_side is TOP_route_top_entry_l_shape. Before the same-X rule, which would drop straight in with no horizontal lead-in.
6same-X vertical dropsame_xstraight vertical
7bottom-exit junctionsource is a bottom-exit junction_route_bottom_exit_junction
8bypass familyneeds_bypass_route_bypass_family: merge trunk/branch, LEFT-entry-one-row-below straight drop, RIGHT-entry wrap (gap-above or around-below), else the U-shaped bypass
9near-vertical same-col junctionjunction dropping almost straight into a same-column entry_route_near_vertical_junction
10RIGHT entry wrapentry_side is RIGHT and travelling right_route_right_entry_wrap (over the top / around the right side)
11LEFT entry wrap familyentry_side is LEFT, dx < 0, cross_row_route_left_entry_family: inter-row gap wrap, or the corridor / around-below loop when that gap crosses a section
12serpentine LEFT exit -> LEFT entryLEFT exit into a LEFT entry stacked in the same column_route_left_exit_left_entry_drop
13merge entry familymerge_ep is not None_route_merge_entry_family: straight (near-collinear), corridor / around-below (LEFT entry crossing a section), else L-shape into the entry port
14RIGHT entry plough -> bypassa higher-row L-shape to a RIGHT entry that would plough an intervening same-row section_route_bypass
-fall-throughno rule matched_route_l_shape

The three rules whose handlers carry their own residual decisions (8, 11, 13) own that logic inside the named handler, so the top-level table stays a single declarative pass.

assert_render_curve_invariants (routing/invariants.py) runs on every render and the stage-boundary checks run under validate=True. Because every rule above builds its route through the centreline bundle builder

  • which makes a flipped, pinched, or collinear bundle impossible by construction - these checks are a thin safety net. In normal operation they never fire; a failure means a genuinely new, un-tabled shape reached the renderer built some other way, and the fix is to route it through the builder too, not to relax the guard.