From 0daca6dfd71e5e34b3bc156fe54a758af5a67eb9 Mon Sep 17 00:00:00 2001 From: Coasterpete <83011124+Coasterpete@users.noreply.github.com> Date: Tue, 21 Jul 2026 18:33:59 -0400 Subject: [PATCH 1/2] M166: Add first functional track authoring foundation --- .../Controls/InspectorPaneControl.axaml.cs | 93 +++-- .../Controls/RoutePaneControl.axaml | 32 +- .../Controls/RoutePaneControl.axaml.cs | 75 ++++ .../Controls/SectionEditorDialog.cs | 309 +++++++++++++++ Quantum.Editor.Avalonia/MainWindow.axaml.cs | 95 +++++ .../Services/Documents/TrackEditorDocument.cs | 76 +++- .../Services/EditorWorkspace.cs | 234 +++++++++++- .../V2/TrackLayoutPackageV2GraphAdapter.cs | 3 +- .../Editor/EditorTestDocumentFactory.cs | 25 ++ Quantum.Tests/Editor/EditorWorkspaceTests.cs | 178 +++++++-- .../Editor/EngineeringPlotProjectionTests.cs | 6 +- .../Editor/PaneExtractionContractTests.cs | 7 + .../Editor/TrackDocumentFileServiceTests.cs | 61 ++- .../Track/CoasterApiBoundaryContractTests.cs | 8 + .../TrackAuthoringCandidateEvaluatorTests.cs | 306 +++++++++++++++ .../TrackAuthoringGraphOperationsTests.cs | 256 +++++++++++++ .../TrackAuthoringScalarCurvatureTests.cs | 167 ++++++++ ...rackAuthoringSectionDefaultFactoryTests.cs | 260 +++++++++++++ .../ConstantCurvatureSectionDefinition.cs | 15 +- .../CurvatureTransitionSectionDefinition.cs | 2 + .../Authoring/GeometricSectionDefinition.cs | 9 +- .../TrackAuthoringCandidateEvaluation.cs | 65 ++++ .../Graph/TrackAuthoringCandidateEvaluator.cs | 86 +++++ .../Graph/TrackAuthoringCandidateOperation.cs | 161 ++++++++ .../Authoring/Graph/TrackAuthoringGraph.cs | 2 +- .../Graph/TrackAuthoringGraphCompiler.cs | 254 +------------ .../Graph/TrackAuthoringGraphDiagnostic.cs | 4 +- .../Graph/TrackAuthoringGraphNode.cs | 16 +- .../Graph/TrackAuthoringGraphOperations.cs | 231 ++++++++++++ .../Graph/TrackAuthoringGraphRouteResult.cs | 30 ++ .../TrackAuthoringGraphRouteValidator.cs | 306 +++++++++++++++ .../TrackAuthoringSectionDefaultFactory.cs | 356 ++++++++++++++++++ .../Authoring/SpatialSectionDefinition.cs | 2 + .../Authoring/StraightSectionDefinition.cs | 2 + ...kAuthoringBoundaryContinuityDiagnostics.cs | 40 +- .../TrackAuthoringDocumentBuilder.cs | 2 +- .../TrackAuthoringScalarCurvature.cs | 193 ++++++++++ .../Authoring/TrackAuthoringSectionCatalog.cs | 29 ++ .../TrackAuthoringSectionDefaults.cs | 70 ++++ .../TrackAuthoringSectionDefinition.cs | 42 +++ .../Authoring/TrackAuthoringSectionFamily.cs | 11 + .../TrackAuthoringSectionTypeDescriptor.cs | 35 ++ .../Authoring/TrackAuthoringSectionTypeIds.cs | 20 + .../m166-first-functional-track-authoring.md | 86 +++++ 44 files changed, 3865 insertions(+), 395 deletions(-) create mode 100644 Quantum.Editor.Avalonia/Controls/SectionEditorDialog.cs create mode 100644 Quantum.Tests/Editor/EditorTestDocumentFactory.cs create mode 100644 Quantum.Tests/Track/TrackAuthoringCandidateEvaluatorTests.cs create mode 100644 Quantum.Tests/Track/TrackAuthoringGraphOperationsTests.cs create mode 100644 Quantum.Tests/Track/TrackAuthoringScalarCurvatureTests.cs create mode 100644 Quantum.Tests/Track/TrackAuthoringSectionDefaultFactoryTests.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringCandidateEvaluation.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringCandidateEvaluator.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringCandidateOperation.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringGraphOperations.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringGraphRouteResult.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringGraphRouteValidator.cs create mode 100644 Quantum.Track/Authoring/Graph/TrackAuthoringSectionDefaultFactory.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringScalarCurvature.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionCatalog.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionDefaults.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionDefinition.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionFamily.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionTypeDescriptor.cs create mode 100644 Quantum.Track/Authoring/TrackAuthoringSectionTypeIds.cs create mode 100644 docs/editor/m166-first-functional-track-authoring.md diff --git a/Quantum.Editor.Avalonia/Controls/InspectorPaneControl.axaml.cs b/Quantum.Editor.Avalonia/Controls/InspectorPaneControl.axaml.cs index b7c5f23..5bba35e 100644 --- a/Quantum.Editor.Avalonia/Controls/InspectorPaneControl.axaml.cs +++ b/Quantum.Editor.Avalonia/Controls/InspectorPaneControl.axaml.cs @@ -37,7 +37,7 @@ private void RebuildInspector() TrackEditorDocument? document = currentWorkspace.ActiveDocument; EditorSelection? selection = currentWorkspace.CurrentSelection; - if (document?.Graph is null || document.GraphCompileResult is null || selection is null) + if (document?.Graph is null || selection is null) { InspectorTitleText.Text = "Nothing selected"; AddInspectorNote("Select a section, Math Plot station, or viewport sample."); @@ -57,11 +57,11 @@ private void RebuildInspector() break; case EditorSelectionKind.BankingKey: InspectorTitleText.Text = "Banking key"; - AddInspectorNote("Banking-key editing is outside the M157 graph-authoring vertical slice."); + AddInspectorNote("Banking-key editing is outside the M166 authoring scope."); break; case EditorSelectionKind.ControlPoint: InspectorTitleText.Text = "Spatial control point"; - AddInspectorNote("Control-point editing is outside the M157 graph-authoring vertical slice."); + AddInspectorNote("Control-point editing is outside the M166 authoring scope."); break; } } @@ -110,8 +110,9 @@ private void BuildTrackInspector(TrackEditorDocument document) editable: false); } - AddInspectorNote( - "M157 edits section nodes through the authoring graph. Document metadata remains read-only."); + AddInspectorNote(document.IsEmpty + ? "Use Add in the Route pane to create the first geometric section." + : "Section edits compile atomically through the backend authoring graph."); } private void BuildGraphNodeInspector( @@ -135,7 +136,12 @@ private void BuildGraphNodeInspector( return; } - GeometricSectionDefinition section = node.Section; + GeometricSectionDefinition section = + (GeometricSectionDefinition)node.Section; + bool supportsParameterEditing = + section is StraightSectionDefinition || + section is ConstantCurvatureSectionDefinition || + section is CurvatureTransitionSectionDefinition; InspectorTitleText.Text = "Section: " + section.Id; AddInspectorField("id", "Section ID", section.Id, editable: false); AddInspectorField("kind", "Section type", DescribeSectionKind(section), editable: false); @@ -143,12 +149,12 @@ private void BuildGraphNodeInspector( "sectionLength", "Length (m)", section.Length.ToString("G17", CultureInfo.InvariantCulture), - editable: false); + editable: supportsParameterEditing); AddInspectorField( "rollDegrees", "Section roll (deg)", (section.RollRadians * 180.0 / System.Math.PI).ToString("G17", CultureInfo.InvariantCulture), - editable: false); + editable: supportsParameterEditing); if (section is ConstantCurvatureSectionDefinition arc) { @@ -156,9 +162,6 @@ private void BuildGraphNodeInspector( "radius", "Signed radius (m)", arc.Radius.ToString("G17", CultureInfo.InvariantCulture)); - AddInspectorNote( - "Changing signed radius recompiles the complete graph through the existing backend pipeline."); - AddApplyButton("Apply radius", () => ApplyRadiusInspector(node.Id)); } else if (section is CurvatureTransitionSectionDefinition transition) { @@ -166,18 +169,17 @@ private void BuildGraphNodeInspector( "startCurvature", "Start curvature", transition.StartCurvature.ToString("G17", CultureInfo.InvariantCulture), - editable: false); + editable: true); AddInspectorField( "endCurvature", "End curvature", transition.EndCurvature.ToString("G17", CultureInfo.InvariantCulture), - editable: false); + editable: true); AddInspectorField( "interpolation", "Interpolation", transition.InterpolationMode.ToString(), editable: false); - AddInspectorNote("Transition editing is intentionally deferred beyond M157."); } else if (section is SpatialSectionDefinition spatial) { @@ -191,12 +193,18 @@ private void BuildGraphNodeInspector( "Control points", spatial.ControlPoints.Count.ToString(CultureInfo.InvariantCulture), editable: false); - AddInspectorNote("Spatial control-point editing is intentionally deferred beyond M157."); + AddInspectorNote("Spatial control-point editing is intentionally deferred beyond M166."); } else + { + AddInspectorNote("Straight sections use the common length and roll parameters."); + } + + if (supportsParameterEditing) { AddInspectorNote( - "Straight-node length editing is deferred because authored banking shares the total station domain."); + "Applying parameters validates and recompiles the complete immutable route."); + AddApplyButton("Apply section", () => ApplySectionInspector(node.Id)); } int sectionIndex = currentWorkspace.GraphNodes @@ -281,12 +289,16 @@ private void AddCanonicalEngineeringFields( AddInspectorField( keyPrefix + "SectionType", "Section type", - sectionNode is null ? "Unavailable" : DescribeSectionKind(sectionNode.Section), + sectionNode?.Section is GeometricSectionDefinition geometricSection + ? DescribeSectionKind(geometricSection) + : "Unavailable", editable: false); AddInspectorField( keyPrefix + "SectionLength", "Section length", - sectionNode is null ? "Unavailable" : $"{sectionNode.Section.Length:F3} m", + sectionNode?.Section is GeometricSectionDefinition sectionDefinition + ? $"{sectionDefinition.Length:F3} m" + : "Unavailable", editable: false); } @@ -386,26 +398,45 @@ private void AddApplyButton(string label, Action apply) InspectorFieldsPanel.Children.Add(button); } - private void ApplyRadiusInspector(string nodeId) + private void ApplySectionInspector(string nodeId) { EditorWorkspace currentWorkspace = GetWorkspace(); try { - double radius = NumberField("radius"); - currentWorkspace.ApplyGraphEdit($"Edit {nodeId} radius", graph => + double length = NumberField("sectionLength"); + double rollRadians = NumberField("rollDegrees") * System.Math.PI / 180.0; + currentWorkspace.ApplyGraphEdit($"Edit section {nodeId}", graph => { TrackAuthoringGraphNode node = graph.Nodes.Single(candidate => string.Equals(candidate.Id, nodeId, StringComparison.Ordinal)); - ConstantCurvatureSectionDefinition arc = - node.Section as ConstantCurvatureSectionDefinition ?? - throw new InvalidOperationException( - $"Graph node ID '{nodeId}' is not a constant-curvature section."); - var replacement = new ConstantCurvatureSectionDefinition( - arc.Id, - arc.Length, - radius, - arc.RollRadians); - return graph.WithSection(nodeId, replacement); + TrackAuthoringSectionDefinition replacement = node.Section switch + { + StraightSectionDefinition straight => + new StraightSectionDefinition( + straight.Id, + length, + rollRadians), + ConstantCurvatureSectionDefinition arc => + new ConstantCurvatureSectionDefinition( + arc.Id, + length, + NumberField("radius"), + rollRadians), + CurvatureTransitionSectionDefinition transition => + new CurvatureTransitionSectionDefinition( + transition.Id, + length, + NumberField("startCurvature"), + NumberField("endCurvature"), + transition.InterpolationMode, + rollRadians), + _ => throw new NotSupportedException( + $"Section type '{node.TypeId}' does not have an M166 Inspector editor.") + }; + return TrackAuthoringGraphOperations.Replace( + graph, + nodeId, + replacement); }); } catch (Exception exception) when (exception is FormatException || exception is OverflowException) diff --git a/Quantum.Editor.Avalonia/Controls/RoutePaneControl.axaml b/Quantum.Editor.Avalonia/Controls/RoutePaneControl.axaml index 9aff17f..b8df607 100644 --- a/Quantum.Editor.Avalonia/Controls/RoutePaneControl.axaml +++ b/Quantum.Editor.Avalonia/Controls/RoutePaneControl.axaml @@ -25,7 +25,7 @@ Background="#151C24" BorderBrush="#2B3948" BorderThickness="0,0,1,0"> - + @@ -34,9 +34,37 @@ + + +