Skip to content

p2-s2: junction regeneration learns turn-set changes #263

Description

@JArmandoAnaya

Part of the Road to Parity roadmap. Epic #251. Ground truth: p2_discovery.md §4.

Highest-risk sprint in the pillar. Gates p2-s6 (Lane Carve).

The problem

regenerate_junction refuses any turn-set change, twice:

  • "regeneration changed the connection count; delete and recreate the junction"
  • "regeneration changed the turn set; delete and recreate the junction"

and the editor swallows the failure into a warning toast rather than failing the user's edit (document.cpp). So today: add a lane to a road feeding a junction → the junction does not update and stays stale until the user deletes and recreates it. GW-2 step 12 carves a turn lane "approaching the junction" — exactly the refused case.

The restriction is not incidental: regen's ability to preserve connecting-road ids (and therefore keep undo exact) depends on a fixed connection count.

Decision (maintainer, 2026-07-15): teach regen turn-set changes. A turn lane that cannot reach a junction is a hollow feature.

Scope

  • Regen allocates ids for new turns, erase_exacts removed ones, and rewrites the connection table, keeping undo exact. Turn matching is already by key rather than by order — that half is done.

  • GenericCommand's creator/erased/before/after sets already support this shape; no new command machinery.

  • Interior lane insert with renumbering + link remap (moved here from p2-s1: kernel lane-section foundations (split, width profiles, interior lanes) #262, maintainer-approved 2026-07-15). add_lane is outermost-only today; Lane Form and Lane Carve need an interior insert, which means renumbering lanes outward and remapping every link that named them — the neighbouring sections' predecessor/successor links and JunctionConnection::lane_links.

    It moved here because that remap is the same problem this sprint already owns: junction lane_links reference only a road's end sections, and inserting a lane in an end section changes the turn set — the case regen refuses today. Implementing it in p2-s1: kernel lane-section foundations (split, width profiles, interior lanes) #262 meant writing an unexercisable end-section path against a contract this sprint redefines. remove_lane already clears those links rather than remapping; generalize that here, once.

    The new lane must NOT be linked back to the neighbouring sections: a lane that appears is not connected to the original (asam.net:xodr:1.4.0:road.lane.link.new_lane_appear).

Refactorings

  • regenerate_junction becomes a topology-changing command. This collides with the editor's dirty-set contract: document.cpp deliberately breaks out of the regen loop when dirty.topology is set, so create/delete/split don't self-regenerate. The contract needs a third state (topology-changed-and-regenerate).
  • Resolve that contract on paper in this issue before writing code.

Acceptance

  • Add a lane to a junction arm → the junction regenerates; no warning toast. This case has no test today because the answer was "refuse".
  • No-op regen stays byte-identical — RegenerateJunctionIsByteEqualWhenNothingChanged must stay green.
  • verify_junction_welds reports no breach after a turn-set change.
  • Undo restores the exact connection table and connecting-road ids.
  • Soak driver exercises lane edits on junction arms; ASan clean.

Out of scope

Junction interior geometry changes; corner radius and per-corner materials (P4).

Note

If this sprint overruns, stop and report. p2-s6 depends on it, and the fallback (carve refuses near junction arms) is a GW-2 amendment requiring maintainer approval — not a silent deviation.

Supersedes

(none)

Metadata

Metadata

Assignees

No one assigned

    Labels

    pillar:P2Road to Parity pillar P2

    Type

    No type

    Projects

    Status
    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions