feat(core): junction regeneration learns turn-set changes (p2-s2) - #270
Merged
Conversation
regenerate_junction refused any turn-set change and the editor swallowed the failure into a warning toast, so adding a lane to a road feeding a junction left it stale until a manual delete-and-recreate. Teach it to add, drop, and rewrite connections instead — the case GW-2 step 12's turn lane needs. - DirtySet gains junctions_are_current: the flag that says a command already brought its junctions up to date, split out from topology so a lane appearing can be topology AND need regeneration. The editor's regen loop keys off it instead of topology. - add_lane, remove_lane, set_lane_type declare junctions_touching so the editor knows which junctions to regenerate. - regenerate_junction is restructured around a creator: it partitions the plan into matched/new/dropped turns, keeps the ids of the turns that survive (keyed matching), materializes the ones that appeared, and erases the ones that vanished with everything they own. A TurnSetPolicy parameter keeps the per-frame drag path in-place-only, where creating connecting roads would leak arena slots on every discarded frame. - The regen loop now merges a regeneration's own topology into the dirty set, so a dropped connecting road prunes stale selections. Author: Armando Anaya <armando@robomous.ai>
Lane Form and Lane Carve need a lane that starts partway across the carriageway, which add_lane (outermost-only) cannot author. insert_lane places a lane at a given odr id and renumbers every lane already at or outside it one step further out. Because odr id IS the lateral position, an interior insert necessarily renumbers, and everything that named the shifted lanes by id is remapped rather than cleared: adjacent-section predecessor/successor links (the writer refuses a dangling one in either direction) and junction lane_links. The inserted lane is a new lane, so it does not link back to the neighbouring sections (asam new_lane_appear). All of it runs inside the command creator, so apply/revert stays byte-identical. Also exposes it through the Python bindings and the soak driver, adds the TurnSetPolicy enum and junctions_are_current to the bindings, and amends the frozen M2 editing-tools doc with the turn-set-change decision. Author: Armando Anaya <armando@robomous.ai>
…y scope GCC -Werror=shadow (Linux only; the macOS clang preset does not flag it) rejected the creator's road/here locals shadowing the factory-scope ones.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #263. Highest-risk sprint in P2; gates #218 (Lane Carve).
What this does
regenerate_junctionrefused any turn-set change (twice — connection count, then per-turn key) and the editor swallowed the failure into a warning toast, so adding a lane to a road feeding a junction left it stale until a manual delete-and-recreate. That is exactly the case GW-2 step 12's turn lane needs. This teaches regeneration to add, drop, and rewrite connections in place.The dirty-set contract (
junctions_are_current)Discovery found the M2 restriction was not where the issue assumed:
add_lanesettopology = truebut never populated.junctions, so the editor's regen loop iterated an empty list. AndDirtySet::topologywas doing double duty — "roads/junctions added or removed" (a view concern) and "this command already handled its own junctions, don't regenerate" (a correctness concern). Those coincided only because every topology command so far also built junctions; a lane appearing is topology and needs regeneration, which one flag cannot say.So
DirtySetgainsjunctions_are_current, set by the commands that build or tear down junction structure (create/delete junction, split_road, delete_road). The editor's regen loop keys off it instead oftopology. Defaultfalsefails safe: a command that forgets it gets a redundant regeneration (slow, correct), never a stale junction (fast, wrong).The regeneration restructure
regenerate_junctionis rebuilt around a creator. It partitions the freshly planned turns against the existing connection table into matched / new / dropped (keyed by the incoming/outgoing road+contact+lane, so a re-ordered plan is not a changed turn set). Matched turns keep their connecting-road ids; new turns get fresh connecting roads via a factored-outmaterialize_connection; dropped turns are erased with everything they own (sections, lanes, and any objects/signals anchored to them —erase_road_exactdoes not cascade).A
TurnSetPolicyparameter keeps the per-frame node-drag preview in-place-only: a preview command is reverted and destroyed every frame, andArena::erase_exactreserves a slot rather than recycling it, so creating connecting roads there would leak arena slots for the rest of the session. The drag keeps today's behaviour (stale junction + toast); lane edits, which are not previewed, regenerate normally. (The general preview-session leak is filed as a separate issue gating #218.)insert_laneInterior lane insert with renumbering + link remap, moved here from #262 — Lane Form and Lane Carve need a lane that starts partway across the carriageway, which
add_lane(outermost-only) cannot author. Since odr id is the lateral position, an interior insert renumbers the outer block and remaps everything that named those lanes by id (adjacent-section links, junction lane_links); the inserted lane is unlinked from its neighbours (asam new_lane_appear).Verification
insert_laneop exercised.RegenerateJunctionIsByteEqualWhenNothingChangedstays green and was strengthened to also assert a clean revert.InPlaceOnlystill refuses,add_lanenames its junctions without claiming they are current,insert_lanerenumber / unlinked / neighbour-remap / junction-lane_link-remap / rejections, and the editor path (add_laneon an arm regenerates with noregeneration_skippedtoast). The grow and shrink tests were mutation-checked to confirm they bite.🤖 Generated with Claude Code