D:\QGis\MyPlugins\roll\Refactoring_roadmap.txt

**Refactoring Roadmap**

Updated for the current codebase on 2026-04-20.

The broad direction is still the same: keep persistence, import/QC logic, mutable session/runtime state, and worker lifecycle concerns out of `roll_main_window.py`, then clean up plotting/redraw boundaries before touching worker contracts or performance.

This document reflects the live codebase after the `SessionState`, `SessionService`, `ImportService`, `AppSettings`, `RuntimeState`, `DocumentContextService`, `ProjectService`, `ProjectLoadApplier`, `WorkerOperationController`, `worker_result_appliers`, legacy-sidecar compatibility, offset-tab extraction, broader plot-interaction/status-bar cleanup work, the well survey-context cleanup, the seed/well parameter-state and creation-helper extractions, and the remaining parameter-child wiring cleanup in `my_parameters.py`.

It also reflects the current UI split more accurately than the previous version: tab construction is no longer only an O/A-specific extraction. The layout, pattern, geometry, SPS, trace-table, stack-response, offset-inline/xline, and O/A tab builders already live in separate `roll_main_window_create_*_tab.py` modules. What still remains in `roll_main_window.py` is the heavier plot-input computation, widget mutation, plot-interaction behavior, and surface-specific controller flow.

It also reflects the wizard-default extraction work: land and marine wizard presets no longer live in `config.py`, and the old mutable `AppSettings -> config.py` compatibility bridge has been removed.

**Current Status Summary**

Completed or substantially landed:
1. `ProjectService` owns project XML read/write, sidecar save/load helpers, memmap opening, and batch project-sidecar loading.
2. `ProjectLoadApplier` owns the loaded-sidecar apply path instead of leaving that logic inline in `fileLoad()`.
3. `FilterService` exists and the duplicate/orphan handlers in `roll_main_window.py` are thin dispatch wrappers.
4. Analysis sidecars save at the right moment after binning, including `.off.npy`, `.azi.npy`, and the refreshed `.ana.npy` reopen path.
5. `ProjectService` normalizes legacy point sidecars that predate the `InUse` field and logs the exact normalized sidecar file path.
6. Display, logging, and property dock construction has been extracted into dedicated builder helpers.
7. Worker-thread lifecycle code is already out of `RollMainWindow` and lives in `binning_worker_mixin.py`.
8. `SessionState` owns imported arrays, geometry arrays, and derived live/dead and convex-hull state.
9. `SessionService` owns canonical array refresh behavior, array clearing, and timer/profiling helpers.
10. `ImportService` owns SPS/RPS/XPS text parsing, cancellation/progress callbacks, QC passes, duplicate/orphan checks, CRS conversion, and transform messaging.
11. `AppSettings` and `RuntimeState` now exist, and `settings.py` loads/saves through them rather than only pushing mutable data directly into `config.py`.
12. `sps_import_dialog.py` now takes editable SPS/RPS/XPS format ownership from `AppSettings` rather than treating `config.py` as the primary mutable owner.
13. `AppSettings.activate()` provides the explicit runtime activation path instead of writing mutable settings back into `config.py`.
14. Land and marine wizard defaults live in `LandSurveyWizard` and `MarineSurveyWizard` instead of `config.py`.
15. Main-window tab construction is already split across `roll_main_window_create_layout_tab.py`, `roll_main_window_create_pattern_tab.py`, `roll_main_window_create_geom_tab.py`, `roll_main_window_create_sps_tab.py`, `roll_main_window_create_trace_table_tab.py`, `roll_main_window_create_stack_response_tab.py`, `roll_main_window_create_offset_tabs.py`, and `roll_main_window_create_off_azi_tab.py`.
16. The O/A histogram now has explicit rectangular versus polar UI state, split render paths, and direct regression coverage for renderer dispatch.
17. Stack-response and pattern-response plots now share a reusable analysis image/colorbar preparation helper instead of duplicating the full image-item and colorbar setup path in each controller.
18. Pattern-response input gathering and Kx/Ky computation have been narrowed into dedicated helpers instead of leaving combo-box and response-array concerns mixed together in `plotPatterns()`.
19. Stack-cell pattern selection and stack-cell response computation have been narrowed into dedicated helpers instead of leaving all selection and numerical work inline in `plotStkCel()`.
20. A first analysis redraw dispatcher slice now exists for patterns, stack-response surfaces, O/A, and offset, with typed redraw reasons and direction-aware stack-response gating.
21. The O/A file-load/reset path now forces the next render back to the default `0/max` display range instead of preserving stale manual levels across files.
22. Redraw reasons now drive real invalidation behavior for patterns, O/A, and stack-response instead of being pass-through metadata only.
23. Pattern-response and stack-response surfaces now reuse cached responses for unchanged selections or navigation contexts, rather than recomputing on every redraw.
24. `PlotRedrawHelper` now owns redraw invalidation policy, redraw cache-key state, surface cache reuse checks, and reused-axis reconstruction for pattern, inline, xline, and stack-cell analysis surfaces instead of leaving that policy inside `roll_main_window.py`.
25. O/A now follows the same input-preparation versus widget-mutation structure as the pattern and stack-response redraw paths.
26. `plotOffset()` now follows the same redraw/controller contract as O/A, with dedicated offset histogram/input preparation, prepared render helpers, and dispatcher routing.
27. Layout image creation and layout colorbar wiring now share a narrower `prepareLayoutImageAndColorBar()` helper without moving layout into the full redraw-policy model.
28. The test baseline is materially stronger than the earlier roadmap assumed: project round-trip, sidecar behavior, batch sidecar loading, higher-level file-load/apply behavior, legacy sidecar compatibility, session-service behavior, import-service coverage, filter-service coverage, settings/runtime ownership checks, and targeted plot/redraw regressions all exist.
29. The main worker launch paths now use explicit request/result contracts end to end for binning from templates, binning from geometry/imported SPS, and geometry creation from templates.
30. Converted workers now emit typed `resultReady` payloads plus a narrowed no-argument `finished()` signal, instead of mixing typed results with a redundant success boolean on the completion signal.
31. There is now direct worker-level success/failure regression coverage for two converted worker paths, in addition to the existing launcher/completion wiring regressions.
32. Converted worker result payloads no longer copy the large analysis arrays a second time just to cross the worker boundary; geometry profiling now travels as one explicit optional payload, and RMS summary values are omitted when no RMS surface exists.
33. The geometry binning completion path now has direct failure-boundary regression coverage at the main-window/mixin layer, so both emission-time and consumer-time behavior are protected on the converted binning paths.
34. `roll_survey.py` now has direct regression coverage for the live geometry invariant work, including template/grow-step normalization behavior and direct geometry generation from templates.
35. In `roll_survey.py`, `geomTemplate2()`, `geomTemplate3()`, `binFromGeometry4()`, `binFromGeometry5()`, `binFromGeometry6()`, `binFromGeometry7()`, and `binTemplate6()` are intentionally retained as dead reference implementations for future optimization comparison; the live geometry/binning paths currently run through `geomTemplate4()`, `binFromGeometryNoRel()`, `binFromGeometry8()`, and `binTemplate7()`.
36. The active `geomTemplate4()` path is now helper-backed instead of carrying all source append, receiver de-dup, `relTemp` construction, and relation expansion inline.
37. The active vectorized binning paths now share extracted live helpers for the final valid-CMP bin updates, CMP/offArray filtering plus radial pruning, and the relation-driven lookup/receiver-selection path inside `binFromGeometry8()`, all with direct regression coverage.
38. The active geometry-generation path now has direct rolled-template correctness coverage, and `geometryFromTemplates()` uses a small helper to own template-roll dispatch instead of leaving that loop inline.
39. The redraw/controller safety net is stronger than the previous roadmap version assumed: there are now direct regressions proving O/A presentation-only redraws skip histogram recomputation, offset visible-activation reuse differs from controller invalidation, broader analysis-refresh paths reset redraw caches when bin-area changes, and the live relation-driven versus no-relation binning paths populate missing local coordinates consistently.
40. The live binning entry points now share an extracted completion-tail helper for fold/min-max post-processing plus the `fullAnalysis` branch, and there is direct regression coverage that proves the helper either runs RMS/unique/O-A post-processing or clears `anaOutput`.
41. Document-context ownership is now narrower and more explicit: `RuntimeState` owns the current file path plus MRU-backed document context, `DocumentContextService` owns current-file commit/remove/recent-file resolution and menu-building behavior, `RollMainWindow` delegates open/save/MRU state transitions through that service, and `settings.py` reads/writes the runtime document context object directly.
42. The offset-inline and offset-xline analysis tabs now use a dedicated wrapper builder with independent per-tab component controls instead of being added as bare plot widgets.
43. `numbaOffInline()` and `numbaOffXline()` now support `|offset|`, `Inline`, and `X-line` component selection, and that numeric behavior has direct regression coverage.
44. Plot mouse tracking is now wired centrally from `createPlotWidget()`, so non-layout plots show at least plot coordinates in the status bar and image-backed analysis surfaces expose sampled local values; the O/A polar view has explicit value sampling rather than relying on a scene-added image transform.
45. Pattern-grid traversal now has one canonical owner: `RollGrid.iterPoints()` owns the fixed three-grow-step iteration used by both pattern painting and pattern-response extraction.
46. `RollPattern.readXml()` now normalizes legacy implicit-seed grow lists back to exactly three entries before any assertion-backed traversal runs, instead of letting old XML shapes leak into live rendering or analysis code.
47. `RollPattern.calcPatternPointArrays()` now allocates exact-size `float32` arrays directly from shared grid traversal rather than building intermediate Python lists first, and the old list helper has been removed.
48. Pattern seeds no longer carry stale cached point-list or point-array state that the live code does not consume; the dead pattern-only point-list setup in `roll_survey.py` and obsolete `RollPatternSeed` conversion path have been removed.
49. The remaining stack-response controller cluster is no longer implemented inline in `roll_main_window.py`; `StackResponseController` now owns stack redraw-context building, stack-surface routing, inline/xline/cell plotting, and stack-cell pattern/response computation, while `RollMainWindow` keeps thin delegating wrappers.
50. The visible-plot and analysis-navigation switchboard is no longer implemented inline in `roll_main_window.py`; `PlotNavigationController` now owns plot-widget lookup, plot-mouse routing, shared visible-analysis context derivation, and visible-plot activation routing, while `RollMainWindow` keeps thin delegating wrappers.
51. The plot-toolbar and view-state synchronization cluster is no longer implemented inline in `roll_main_window.py`; `PlotViewStateController` now owns show-event toolbar synchronization plus the active-plot zoom-rect, aspect-ratio, anti-alias, and grid-toggle actions, while `RollMainWindow` keeps thin delegating wrappers.
52. The broader action/menu state synchronization cluster is no longer implemented inline in `roll_main_window.py`; `ActionStateController` now owns representative export/process enablement, processing-menu gating, focus-routed cut/copy/paste/select-all handling, and active-plot lookup for clipboard/print routing, while `RollMainWindow` keeps thin delegating wrappers.
53. The print/export presentation cluster is no longer implemented inline in `roll_main_window.py`; `PrintPresentationController` now owns print-preview setup, printer-versus-plot fallback routing, plot-to-device rendering, and PDF export output-device configuration, while `RollMainWindow` keeps thin delegating wrappers.
54. The property-panel survey edit/apply cluster is no longer implemented inline in `roll_main_window.py`; `PropertyPanelController` now owns property-tree rebuild, pattern-combo synchronization, parameter-to-survey reconstruction, XML-backed survey commit via `survey.deepcopy()`, and analysis-reset sidecar cleanup orchestration, while `RollMainWindow` keeps thin delegating wrappers.
55. The worker-thread boundary is now narrower than the earlier roadmap described: `BinningWorkerMixin` mainly acts as a facade, while `WorkerOperationController` owns job specification, launch/cancel/shutdown flow, and finish-time cleanup rules, and `BinningResultApplier` / `GeometryResultApplier` own the success/failure apply paths.
56. Well-based seeds now resolve survey CRS and transform from the live survey object instead of relying on a mutable `config.py` fallback: `RollWell` carries a weak survey reference, `RollSeed.setSurvey()` propagates that binding, and the well parameter editor refresh path uses the bound survey context when recalculating origins.
57. The well-coordinate transform direction is no longer an open question; there is now direct regression coverage for both identity-transform and inverse-global-transform local-coordinate cases.
58. New template seeds now inherit survey context all the way down to `RollWell`, and newly created well seeds default their well CRS from the current survey CRS instead of keeping an arbitrary placeholder CRS.
59. The well-parameter editor no longer relies only on the internal weak survey reference during file/CRS refresh; it now passes explicit survey CRS and transform context into `RollWell.refreshHeaderFromCurrentState()`, with direct regression coverage for that explicit-context path.
60. The seed parameter tree now enforces grid-only pattern assignment more explicitly: non-grid seeds clear `patternNo`, pattern selection refresh is tolerant of early tree-construction order, and seed-type-specific child-row visibility is now synchronized against both parameter options and live tree items rather than relying on `show()` alone.
61. Seed/well parameter-tree state policy is no longer re-expressed inline across widget classes: `parameter_seed_well_helpers.py` now owns grid-seed visibility/state rules plus well refresh/sampling coordination, with direct helper-level regression coverage.
62. Default parameter-tree creation policy is no longer duplicated inline across block/template/seed context-menu handlers: `parameter_creation_helpers.py` now owns survey-bound default block, template, and appended-seed construction, with direct helper-level regression coverage.
63. The broad repeated parameter-child lookup and child signal-wiring pattern is no longer duplicated across the touched `my_parameters.py` constructors: small local helpers now centralize child binding plus value/tree-state signal hookup, which narrows future parameter-tree edits to the behavior-bearing code paths.
64. Aggregate analysis/configuration write-back is no longer implemented inline only inside `MyAnalysisParameter` and `MyConfigurationParameter`: `parameter_aggregate_helpers.py` now owns those value snapshots and configuration apply rules, with both helper-level and wrapper-level regression coverage.
65. Aggregate grid/reflector write-back is no longer implemented inline only inside `MyGridParameter` and `MyReflectorsParameter`: `parameter_aggregate_helpers.py` now also owns local/global grid apply rules plus reflector value snapshots, with direct helper-level and wrapper-level regression coverage.
66. Aggregate block/template write-back is no longer implemented inline only inside `MyBlockParameter` and `MyTemplateParameter`: `parameter_aggregate_helpers.py` now also owns block-boundary/template-list and roll-list/seed-list apply rules plus value snapshots, with direct helper-level and wrapper-level regression coverage.
67. Repeated item-level list/context-menu orchestration for remove and move actions is no longer re-expressed inline across block/template/seed/pattern-seed parameter items: `parameter_list_helpers.py` now owns the shared collection-mutation plus reinsert flow, with direct helper-level regression coverage.
68. The straightforward add-new/list-builder flows are no longer re-expressed inline across template-seed, pattern-seed, template, block, and pattern list parameters: `parameter_list_helpers.py` now owns shared name allocation plus append/addChild/signal emission behavior, while list-specific side effects stay local.
69. The remaining pattern-list flows with extra survey/seed synchronization side effects are no longer interleaved line by line in `my_parameters.py`: `parameter_list_helpers.py` now supports optional post-remove, post-move, and post-append callbacks, while `_removePatternIndex()`, `_swapPatternIndices()`, `_syncSurveyPatternList()`, and `refreshSeedPatternLists()` stay local as behavior-bearing callbacks, with direct helper-level regression coverage.
70. Preview-item setup and label refresh are no longer re-expressed inline across the preview-bearing parameter items: `MyGroupParameterItem` now owns shared preview initialization plus preview-label update helpers, while each `showPreviewInformation()` method keeps its own text-generation behavior local.
71. The shared circle/spiral child-definition block for `Start angle`, `Point interval`, and readonly `Points` is no longer duplicated inline in both constructors: `my_parameters.py` now has a narrow local helper for that child cluster and its shared bindings, while the radius-specific children and each class's `changed()` behavior remain local.
72. The shared `Seed color` / `Seed origin` / `Grid grow steps` child-definition bundle is no longer duplicated inline across `MySeedParameter` and `MyPatternSeedParameter`: `my_parameters.py` now has narrow local helpers for those rows plus their bindings and value-change hookups, while seed-type visibility, pattern synchronization, and per-class apply behavior remain local, with direct wrapper-level regression coverage.
73. The repeated point-count preview string shaping is no longer re-expressed across the point-based preview items: `my_parameters.py` now has a narrow local formatter for `points` summaries that is reused by roll-list, pattern-seed, circle, spiral, and well previews, while each item still owns its own input gathering and error-state logic.
74. The nested block/template shot-count preview traversal is no longer duplicated inline across `MyBlockParameterItem` and `MyTemplateParameterItem`: `my_parameters.py` now has narrow local helpers for grid-step counts, per-seed source-shot counts, and template/block summary aggregation, with direct fake-node regression coverage.
75. The remaining list-style preview summaries are no longer mixed inline with counting, pluralization, and error-state policy: `my_parameters.py` now has narrow local helpers for generic count-label formatting plus seed-list source/receiver composition summaries, with direct fake-item regression coverage.
76. The last small inline preview error-state and fallback-label seam is no longer embedded only in `MyWellParameterItem`: `my_parameters.py` now has a narrow `previewWellSummary()` helper for file-validity, error-text, and formatted point-summary decisions, with direct helper-level regression coverage. The preview refactor track is now near a natural stopping point.
77. The shared preview-helper style is no longer confined to `my_parameters.py`: the preview-bearing `my_crs2.py`, `my_point2D.py`, `my_point3D.py`, `my_rectf.py`, `my_range.py`, `my_vector.py`, and `my_n_vector.py` modules now also use `MyGroupParameterItem.initializePreviewItem()` plus `updatePreviewLabelText()` instead of repeating the older preview setup and label-update boilerplate.
78. The higher-level `MySeedParameter` seed-type/pattern coordination is no longer fully inlined inside one wrapper: `parameter_seed_well_helpers.py` now owns a narrow `SeedPatternRefreshState` plus selected-pattern refresh logic, and `my_parameters.py` now uses small local helpers for seed visibility application and seed value write-back, with both helper-level and wrapper-level regression coverage.
79. The repeated parameter-tree traversal for seed/pattern synchronization is no longer scattered across `MySeedParameter` and `MyPatternListParameter`: `my_parameters.py` now has narrow local helpers for climbing to the parameter-tree root and iterating template-seed parameters under the block/template hierarchy, with direct pure-helper regression coverage plus the existing wrapper-level seed refresh regression.
80. The repeated pattern-list side-effect bundle is no longer duplicated across rename/add/remove/move callback paths: `my_parameters.py` now has narrow local helpers for survey-sync plus seed-refresh orchestration and for the move/remove index-update variants, while `MyPatternParameter` and `MyPatternListParameter` route through those helpers with direct pure-helper regression coverage plus the existing wrapper-level seed refresh regression.
81. The repeated `MyWellParameter` sampling/header apply flow is no longer duplicated across file/CRS/sampling change handlers: `my_parameters.py` now has narrow local helpers for applying sampling constraints from the live UI fields and for refresh-header/origin-sync/warning orchestration, with direct pure-helper regression coverage.
82. The repeated circle/spiral geometry apply flow is no longer embedded only in `changed()` methods: `my_parameters.py` now has narrow local helpers for applying live circle and spiral parameter values plus recomputed point counts, with direct pure-helper regression coverage.
83. The repeated local/global grid field-to-domain apply flow is no longer embedded only in `MyLocalGridParameter.changed()` and `MyGlobalGridParameter.changed()`: `my_parameters.py` now has narrow local helpers for applying live grid-field values back into `RollBinGrid`, with direct pure-helper regression coverage alongside the existing wrapper-level aggregate regression.
84. The repeated bin-angle field-to-domain apply flow is no longer embedded only in `MyBinAnglesParameter.changed()`: `my_parameters.py` now has a narrow local helper for applying live azimuth and inclination range values back into `RollAngles`, with direct pure-helper regression coverage.
85. The repeated bin-offset normalization and field-to-domain apply flow is no longer embedded only in `MyBinOffsetParameter.changed()`: `my_parameters.py` now has a narrow local helper for normalizing rectangular/radial offset ranges and applying them back into `RollOffset`, with direct pure-helper regression coverage.
86. The repeated unique-offset field-to-domain apply flow is no longer embedded only in `MyUniqOffParameter.changed()`: `my_parameters.py` now has a narrow local helper for applying pruning and rounding settings plus delta values back into `RollUnique`, with direct pure-helper regression coverage.
87. The repeated bin-method field-to-domain apply flow is no longer embedded only in `MyBinMethodParameter.changed()`: `my_parameters.py` now has a narrow local helper for translating the selected binning label back into `BinningType` and applying interval velocity to `RollBinning`, with direct pure-helper regression coverage.
88. The repeated plane field-to-domain apply flow is no longer embedded only in `MyPlaneParameter.changed()`: `my_parameters.py` now has a narrow local helper for applying live anchor, azimuth, and dip values back into `RollPlane`, with direct pure-helper regression coverage.
89. The repeated sphere field-to-domain apply flow is no longer embedded only in `MySphereParameter.changed()`: `my_parameters.py` now has a narrow local helper for applying live origin and radius values back into `RollSphere`, with direct pure-helper regression coverage.
90. The repeated reflector aggregate write-back is no longer embedded only in `MyReflectorsParameter.changed()`: `my_parameters.py` now has a narrow local helper for rebuilding `ReflectorParameterValues` from the live plane/sphere child parameters, with direct pure-helper regression coverage plus stronger wrapper-level nested write-back coverage.
91. The repeated analysis aggregate write-back is no longer embedded only in `MyAnalysisParameter.changed()`: `my_parameters.py` now has a narrow local helper for rebuilding `AnalysisParameterValues` from the live area/angles/binning/offset/unique child parameters, with direct pure-helper regression coverage plus stronger wrapper-level nested write-back coverage.
92. The repeated block aggregate write-back is no longer embedded only in `MyBlockParameter.changed()`: `my_parameters.py` now has a narrow local helper for rebuilding `BlockParameterValues` from the live boundary/template-list child parameters, with direct pure-helper regression coverage plus stronger wrapper-level nested write-back coverage.
93. The repeated template aggregate write-back is no longer embedded only in `MyTemplateParameter.changed()`: `my_parameters.py` now has a narrow local helper for rebuilding `TemplateParameterValues` from the live roll-list/seed-list child parameters, with direct pure-helper regression coverage plus stronger wrapper-level nested write-back coverage.
94. The repeated roll-list child lookup and move-list write-back is no longer embedded only in `MyRollListParameter.changed()`: `my_parameters.py` now has a narrow local helper for rereading the current `Planes` / `Lines` / `Points` child parameters and applying their values back into the backing move list, with direct pure-helper regression coverage.
95. The repeated roll-parameter XYZ increment write-back is no longer embedded only in `MyRollParameter.changedXYZ()`: `my_parameters.py` now has a narrow local helper for applying live `dX` / `dY` / `dZ` values back into `RollTranslate.increment` and refreshing derived azimuth/tilt values, with direct pure-helper regression coverage.
96. The repeated roll-parameter step-count write-back is no longer embedded only in `MyRollParameter.changedN()`: `my_parameters.py` now has a narrow local helper for applying the live `N` value back into `RollTranslate.steps` and emitting the wrapper-level value-change signal, with direct pure-helper regression coverage.
97. The repeated pattern-seed field-to-domain apply flow is no longer embedded only in `MyPatternSeedParameter.changed()`: `my_parameters.py` now has a narrow local helper for applying live color, origin, and grid grow-list values back into `RollPatternSeed`, with direct pure-helper regression coverage alongside the existing wrapper-level regression.
98. The repeated configuration wrapper assignment is no longer embedded only in `MyConfigurationParameter.changed()`: `my_parameters.py` now has a narrow local helper for assigning the result of `applyConfigurationValues()` back into the wrapper and survey state from the live CRS/type/name child values, with direct pure-helper regression coverage.
99. The repeated well header field-update and refresh flow is no longer embedded only in `MyWellParameter.changedF()` and `MyWellParameter.changedC()`: `my_parameters.py` now has a narrow local helper for applying the changed well attribute, refreshing the header, and processing UI events, with direct pure-helper regression coverage.
100. The repeated non-grid seed-type coordination is no longer embedded only in the non-grid branch of `MySeedParameter.typeChanged()`: `my_parameters.py` now has a narrow local helper for clearing the pattern selection under a tree-change blocker and applying the derived seed visibility state, with direct pure-helper regression coverage.
101. The repeated grid seed refresh application is no longer embedded only in `MySeedParameter.refreshPatternList()`: `my_parameters.py` now has a narrow local helper for applying refreshed pattern limits, selected pattern, and derived visibility state together for grid seed flows, with direct pure-helper regression coverage.
102. The repeated grid seed refresh-state derivation is no longer embedded only in `MySeedParameter.refreshPatternList()`: `my_parameters.py` now has a narrow local helper for resolving the effective seed type, climbing to the parameter-tree root, and retrieving refreshed pattern state before the already-helper-backed UI application step, with direct pure-helper regression coverage.
103. The repeated seed-type routing flow is no longer embedded only in `MySeedParameter.typeChanged()`: `my_parameters.py` now has a narrow local helper for reading the current seed type, applying it through `SeedParameterStateHelper`, and dispatching between the already-helper-backed grid and non-grid paths, with direct pure-helper regression coverage.

Still unfinished:
1. Worker launch and completion contracts are explicit on the main paths, but the cleanup is not finished until any leftover legacy worker scaffolding is either justified or removed and any further payload changes are driven by measured need rather than contract churn.
2. A few plotting paths still contain controller-owned details, but the main Phase 5 redraw seams are now in place.
3. `config.py` still serves as a broad shared constants/defaults module; that is acceptable for now, but it can be split by concern later if clearer module boundaries are wanted.
4. The previous well/CRS fallback concern is largely resolved; only routine cleanup remains if any residual callers still pass explicit survey context unnecessarily.
5. `my_parameters.py` remains a real hotspot even after the `PropertyPanelController` extraction, but the most repetitive seed/well state policy, creation defaults, child-wiring boilerplate, preview-item setup, preview text/error-state seams, aggregate wrapper write-back seams, list/context-menu mutation flows, the first higher-level seed schema coordination slice, the repeated seed/pattern traversal seam, the repeated pattern-list side-effect bundle, the repeated well sampling/header apply flow, the repeated circle/spiral apply flow, the repeated local/global grid apply flow, the repeated bin-angle apply flow, the repeated bin-offset normalization/apply flow, the repeated unique-offset apply flow, the repeated bin-method apply flow, the repeated plane/sphere apply flows, the repeated reflector/analysis/block/template aggregate apply flows, the repeated roll-list apply flow, the repeated roll-parameter XYZ and step-count apply flows, the repeated pattern-seed apply flow, the repeated configuration apply flow, the repeated well-header attribute-change flow, and the repeated seed-type routing and refresh flows are now helper-backed. The remaining issue is the higher-level coupling between the larger parameter-schema clusters, UI mutation, and the domain-specific side effects still spread across many parameter classes.

**Recommended Order From Here**

1. Keep the preserved reference implementations in `roll_survey.py` out of routine cleanup scope unless a change is specifically about comparing or replacing live algorithms.
2. Prefer the next focused refactor in `my_parameters.py`, not in `roll_survey.py`: the seed/well fixes now make the remaining parameter-tree duplication and UI/domain mixing easier to isolate safely.
3. Keep `binFromGeometry8()` stable for the moment unless a new correctness issue appears; the main live geometry/binning helper boundaries there are now in place.
4. Revisit worker payload shape only if a measured simplification remains after the active geometry/binning layer is easier to reason about.
5. Do performance work only after the structure stops moving.
6. Treat any further `config.py` work as optional organization, not as the main architectural blocker.

The earlier “create `SessionState` first” and “extract import/QC service first” recommendations are no longer current. Those seams are already good enough to move attention to plotting/redraw structure.

The other important change is that the previous roadmap understated how much UI extraction has already landed. The remaining problem is not tab-builder setup anymore; it is the concentration of surface-specific plotting computations, plot-interaction behavior, and widget mutation inside `roll_main_window.py` after redraw policy, cache state, and reused-axis reconstruction were moved into `PlotRedrawHelper`.

**Phase 0 - Baseline and Safety Net**

Status:
Substantially complete, with a few targeted gaps remaining.

What is already in place:
1. Direct tests for project persistence and sidecars.
2. Direct tests for SPS filter logic.
3. Direct tests for `FilterService`, `SessionState`, and `SessionService`.
4. Direct tests for `ImportService`.
5. Project XML round-trip coverage.
6. Regression coverage for recent numerical and legacy-sidecar fixes.
7. Regression coverage for O/A display-mode defaults and rectangular-versus-polar renderer dispatch.
8. Higher-level `fileLoad()` coverage that exercises survey sidecar loading plus session-state rebuild/application.
9. Settings/runtime ownership regressions around `AppSettings`, `RuntimeState`, and the SPS import dialog.
10. Direct redraw regressions that prove presentation-only O/A redraw reuse, controller-versus-visible offset invalidation behavior, and cache reset after broader analysis refresh.
11. Direct live-binning localization coverage that proves both relation-driven and no-relation paths populate missing local source/receiver coordinates consistently.
12. Direct live-binning completion-tail coverage that proves shared post-processing runs only for `fullAnalysis=True` and clears `anaOutput` otherwise.
13. Direct document-context coverage that proves the helper owns file-path/MRU behavior and that settings/runtime ownership checks include MRU-backed runtime document state.
14. Direct pattern-geometry coverage now proves legacy implicit pattern XML is normalized to three grow steps and that pattern-response arrays are built directly as `float32` arrays from the shared grid traversal.
15. Direct property-panel regressions now prove the working-copy path goes through `survey.deepcopy()` and that `applyPropertyChanges()` preserves or clears analysis caches correctly based on the bin-area-changed boundary.
16. Direct well-transform regressions now prove `RollWell.readHeader()` preserves identity-transform coordinates and applies the inverse global survey transform when deriving local well coordinates.

What is still worth adding here:
1. Add only further geometry-generation tests if they cover a new live-path correctness seam beyond the existing minimal and rolled-template cases.
2. A small reusable project fixture that includes a valid `.roll` plus minimal sidecars, if repeated setup starts to spread further.

Exit criteria:
1. Project persistence and filter behavior are protected.
2. At least one active geometry-generation path is covered by a direct test.
3. At least one worker path is covered by a direct test.

**Phase 1 - Project Persistence and Load Boundary**

Status:
Complete enough for the current milestone.

What is already done:
1. `ProjectService` handles XML read/write.
2. `ProjectService` handles low-level sidecar save/load helpers.
3. `ProjectService` handles memmap opening helpers.
4. `ProjectService.loadProjectSidecars()` returns a structured batch result with messages and loaded arrays.
5. `roll_main_window.py` no longer performs most raw sidecar `np.load` calls directly.
6. Loaded-project sidecar application goes through a dedicated helper instead of being implemented inline in `fileLoad()`.
7. `fileLoad()` delegates load and post-load orchestration into smaller controller helpers.
8. Geometry sidecar saves route through `ProjectService` instead of direct `np.save` calls in worker completion.
9. Legacy point sidecars are normalized at the persistence boundary instead of weakening table-model assumptions.

What remains in this phase:
1. No further Phase 1 work is required before moving to later phases.
2. Only small cleanup/documentation opportunities remain.

Exit criteria:
Met for the current milestone:
1. `fileLoad()` is much shorter and stops owning most loaded-state application logic.
2. `ProjectService` remains the single owner of sidecar naming, existence checks, validation, compatibility normalization, and load rules.
3. The remaining main-window code is mostly controller wiring: parse, call services, update top-level UI state, and trigger redraw.
4. All project sidecar writes go through `ProjectService`.

**Phase 2 - Filter Extraction**

Status:
Complete enough for now.

What has landed:
1. `FilterService` exists and is descriptor-driven.
2. Duplicate/orphan handlers in `roll_main_window.py` delegate to generic point/relation filter dispatch helpers.
3. The service returns structured filter results including summary text and derived spatial state.
4. There is direct unit-test coverage for point duplicate, point orphan, and relation orphan filtering.

Remaining note:
Do not spend more refactoring effort here unless new duplication appears.

Exit criteria:
Already met for the current milestone:
1. The many cleanup/orphan handlers collapsed into a small generic dispatch layer.
2. Filter orchestration is testable without a `QMainWindow`.
3. `roll_main_window.py` is materially less repetitive in this area.

**Phase 3 - Explicit Runtime and Session State**

Status:
Substantially complete for the current milestone.

What has landed:
1. `SessionState` owns imported arrays, geometry arrays, derived live/dead arrays, and convex-hull state.
2. `SessionService.setArray()` and `refreshArrayState()` provide the canonical refresh path for those arrays.
3. `RollMainWindow` property setters for `rpsImport`, `spsImport`, `xpsImport`, `recGeom`, `srcGeom`, and `relGeom` delegate into `SessionService`.
4. `AppSettings` exists for persisted settings loaded from `QSettings`.
5. `RuntimeState` now owns the document-context values used across open/save/import flows: `fileName`, `projectDirectory`, `importDirectory`, and `recentFileList`.
6. `DocumentContextService` now owns document-path commit behavior, MRU removal/resolution/menu building, and stored-value loading for the runtime document context.
7. `settings.py` loads/saves through `AppSettings` and `RuntimeState`, and runtime settings activation is explicit.
8. Wizard naming/incrementing uses the runtime-state `surveyNumber` instead of a direct `config.surveyNumber` mutation path.
9. `sps_import_dialog.py` edits SPS/RPS/XPS formats and dialect selection through `AppSettings` ownership, with tests covering the owner boundary.
10. The old mutable `AppSettings -> config.py` write-back bridge has been removed.
11. Timer/profiling ownership has moved into `SessionService`.
12. Land and marine wizard defaults have moved out of `config.py` and into their respective wizard classes.

What remains in this phase:
1. Decide only if a broader naming/session seam later appears whether `surveyNumber` should stay in `SessionState` or move into a wider runtime/document context.
2. Optionally reduce some remaining read-only `config.py` lookups into narrower constants modules if that later improves clarity.

Exit criteria:
Met enough for the current milestone:
1. `config.py` is no longer used as a mutable compatibility bridge for settings/session ownership.
2. `AppSettings`, `RuntimeState`, and `SessionState` are the only owners of mutable persisted/session state.
3. Services receive state objects rather than many loosely related arrays and flags.
4. `RollMainWindow` stops owning most imported-array and geometry-array lifecycle directly.

**Phase 4 - Import/QC Service**

Status:
Substantially landed.

What has landed:
1. `ImportService` exists.
2. SPS/RPS/XPS parsing loops moved out of `roll_main_window.py`.
3. QC passes, duplicate/orphan checks, CRS conversion, transform calculation, and progress text generation moved into `ImportService`.
4. Import cancellation is handled in the service boundary.
5. `fileImportSpsData()` in `roll_main_window.py` now acts mainly as a controller for file picking, progress display, and committing final arrays into session state.
6. There is direct unit-test coverage for import batching, cancellation, and QC behavior.

What remains in this phase:
1. The import dialog now uses `AppSettings` as the mutable owner, and its remaining `config.py` usage is mostly stable schema/default metadata.
2. Some UI-bound progress/update code remains in the main window by design.
3. A later worker-based import path can be considered, but only after the remaining settings/runtime ownership is stable.

Exit criteria:
Already met enough for the current milestone:
1. Import/QC logic is testable without `RollMainWindow`.
2. Core parsing/QC logic no longer depends directly on `QApplication.processEvents()`.
3. The UI owns file picking, progress display, and final reporting/commit behavior only.

**Phase 5 - Plot and Redraw Service**

Status:
Substantially complete for the current milestone. The redraw/controller slice now has helper-owned invalidation and cache reuse, O/A and offset both follow explicit preparation-versus-render boundaries, the remaining stack-response controller cluster now lives in `StackResponseController`, layout image/colorbar duplication has been reduced with a smaller helper that does not pull layout into the full redraw-policy model, and generic plot mouse/status handling now sits behind shared helpers instead of being layout-only wiring.

Goal:
Separate domain changes from redraw policy, image preparation, and colorbar wiring.

Scope:
1. Layout image preparation.
2. Analysis image preparation.
3. Colorbar input generation and reuse.
4. Plot interaction and status sampling behavior.
5. Partial invalidation rules versus full replot rules.

Why this is now the next structural phase:
1. The main persistence, filter, import, and state seams already exist.
2. The largest remaining concentration of controller complexity now lives in plotting and redraw decisions inside `roll_main_window.py`.
3. Worker cleanup should still wait until redraw/state contracts are clearer.

What has landed:
1. Tab construction is already split out for layout, pattern, geometry, SPS, trace-table, stack-response, and O/A views.
2. The O/A histogram has explicit display-mode UI state (`Rectangular` / `Polar`) instead of embedding that decision in a single plot path.
3. O/A rendering has separate controller methods for rectangular and polar rendering, along with dedicated colorbar/update helpers.
4. Stack-response and pattern-response plots now share `prepareAnalysisImageAndColorBar()` instead of duplicating the same image/colorbar wiring in each controller method.
5. `plotPatterns()` no longer owns all pattern selection and Kx/Ky computation details inline; those concerns now sit behind dedicated helper methods.
6. `plotStkCel()` no longer owns all stack-cell pattern selection and response computation details inline; those concerns now sit behind dedicated helper methods.
7. A small dispatcher now routes `patterns`, `stack-inline`, `stack-xline`, `stack-cell`, and `off-azi` redraws through one controller entry point instead of leaving every trigger to call plot methods ad hoc.
8. Stack-response redraws now honor navigation direction: inline reacts to vertical motion only, xline reacts to horizontal motion only, and stack-cell reacts to both.
9. `AnalysisRedrawReason` now drives real invalidation behavior instead of acting as pass-through metadata only.
10. Pattern-response redraws now invalidate cached Kx/Ky results on selection changes and reuse cached responses when the selected patterns are unchanged.
11. O/A redraws now distinguish controller-driven invalidation from presentation-only redraws such as display-mode and polar colorbar level changes.
12. Stack-response redraws now invalidate caches on controller-driven changes and stack-pattern changes, while reusing cached inline/xline/cell responses when the navigation context is unchanged.
13. `PlotRedrawHelper` now owns surface invalidation policy, cache reset logic, cache-key storage, cache-key builders, and cached-axis reconstruction for pattern, inline, xline, and stack-cell redraw reuse.
14. O/A now has dedicated histogram-input preparation, prepared plot-input construction, prepared render dispatch, and a thin `plotOffAzi()` wrapper over `redrawOffAzi()`.
15. Offset now has dedicated histogram-input preparation, prepared plot-input construction, prepared render dispatch, and dispatcher routing through the same analysis redraw entry point used by O/A.
16. Layout image-item creation and layout colorbar wiring now go through a narrower shared helper instead of leaving the full setup inline in `handleImageSelection()`.
17. There is direct regression coverage for shared analysis image/colorbar preparation, layout image/colorbar preparation, dispatcher routing, stack-response redraw gating, stack-response cache reuse, helper-owned key/axis reconstruction, O/A renderer dispatch, O/A dispatcher routing, offset dispatcher routing, O/A display-range reset on file load, reason-driven invalidation behavior, presentation-only O/A redraw reuse, offset controller-versus-visible invalidation, and cache reset after broader analysis refresh.
18. Offset-inline and offset-xline tab construction now use a dedicated builder module with independent component selectors per tab instead of leaving those controls inline in `roll_main_window.py`.
19. Offset-inline and offset-xline controllers now support plotting absolute offset magnitude or either source/receiver coordinate component through shared selection helpers and the extended numeric helpers.
20. Plot mouse tracking is now connected centrally for every plot widget, with shared helpers for generic coordinate display and image-value sampling on stack, pattern-response, and O/A surfaces, while preserving the richer layout-specific status readout.
21. `StackResponseController` now owns the previously inline stack-response controller seam: redraw-context derivation, stack-surface redraw routing, direction gating, inline/xline/cell rendering, and stack-cell pattern-response combination all moved out of `roll_main_window.py`, with the existing wrapper methods preserved as a stable façade.
22. `PlotNavigationController` now owns the previously inline visible-plot/navigation seam: plot-widget index lookup, first-visible plot discovery, plot-mouse routing, shared visible-analysis context derivation, and the `updateVisiblePlotWidget()` switchboard all moved out of `roll_main_window.py`, with the existing wrapper methods preserved as a stable façade.
23. `PlotViewStateController` now owns the previously inline plot-toolbar/view-state seam: show-event toolbar synchronization, zoom-all rebinding, active-plot toolbar-state refresh, and the active-plot zoom-rect / aspect-ratio / anti-alias / grid toggle actions all moved out of `roll_main_window.py`, with the existing wrapper methods preserved as a stable façade.
24. `ActionStateController` now owns the previously inline action/menu state seam: representative menu/button enablement, processing-menu gating, focus-based edit routing, clipboard-aware plot-copy fallback, and active-plot lookup for print/copy helpers all moved out of `roll_main_window.py`, with the existing wrapper methods preserved as a stable façade.
25. `PrintPresentationController` now owns the previously inline print/export presentation seam: print-preview dialog setup, XML-versus-plot print routing, plot-to-printer page layout/header rendering, and PDF export output-device configuration all moved out of `roll_main_window.py`, with the existing wrapper methods preserved as a stable façade.
26. Worker orchestration is now also past the earlier Phase 5 boundary assumptions: the mixin delegates launch/cancel/shutdown rules to `WorkerOperationController`, and result application is split into dedicated appliers rather than being left as one large completion branch.

What remains in this phase:
1. Keep the helper boundary small unless a new repeated redraw-policy concern appears; do not expand it just to move code mechanically.
2. Add only the remaining high-value regressions that materially protect later worker-boundary refactors.
3. Treat further plotting cleanup in this phase as optional polish, not the main architectural blocker anymore.
4. The small document-tab focus/selection tail around `onMainTabChange()` and `find()` is still not a worthwhile controller extraction target by itself, and the previously stronger property-panel survey edit/apply seam has now already been moved as one coherent unit.

Exit criteria:
1. Data mutations no longer directly decide full redraw policy in many places.
2. Small data edits can invalidate only affected layers.
3. Colorbar and image setup stop being scattered across controller methods.
4. O/A, patterns, and stack-response follow the same redraw/controller structure instead of relying on separate ad hoc trigger paths.

**Phase 6 - Worker Contract Cleanup**

Status:
Substantially in progress on the main paths.

Goal:
Make worker inputs and outputs explicit, smaller, and easier to validate.

Current note:
The narrow vertical-slice migration has landed for the three main launch paths: binning from templates, binning from geometry/imported SPS, and geometry creation from templates. The converted workers now emit typed results, use a no-argument `finished()` signal, avoid redundant array copies in the result payload, carry geometry profiling as one explicit optional sub-payload, and have direct worker-level plus consumer-boundary regressions on the converted binning paths. The orchestration layer is also more explicit than before: `WorkerOperationController` owns job specs plus launch/cancel/shutdown flow, and `BinningResultApplier` / `GeometryResultApplier` own the apply paths. The remaining work here is selective cleanup only where it makes the boundary clearer; this phase no longer needs another contract-shape tweak just to keep moving. The old reference implementations still kept inside `roll_survey.py` should not be treated as part of this worker-cleanup scope unless a future optimization pass explicitly compares them with the live paths.

Future direction:
1. Keep worker inputs and outputs explicit on every converted path.
2. Centralize result validation and shared completion behavior only where it removes real duplication.
3. Keep trimming payload fields only when the completion handlers do not need them and the trim does not push derivation back into UI/controller code.
4. Reduce XML round-tripping for internal worker handoff if profiling shows it matters.

Exit criteria:
1. Worker inputs and outputs are explicit and documented.
2. Completion handlers do less copying and less implicit mutation.
3. Compatibility-only worker APIs on the converted paths are gone.
4. Remaining payload fields each have a clear owner-side reason to exist.

**Phase 7 - Performance Work After Structure Stabilizes**

Status:
Deferred by design.

The main performance candidates are still:
1. repeated `np.unique` work during geometry generation,
2. large array copying on worker completion,
3. broad redraw invalidation after small changes,
4. XML used as both persistence format and internal transport.

Do not optimize these speculatively. Measure them after the service boundaries and state ownership are clearer.

**Tests To Add Next**

The earlier test list is outdated because several of those tests already exist.

The best next tests are:
1. one redraw regression only if another controller-owned invalidation seam is extracted beyond the current O/A and offset coverage,
2. one higher-level runtime/session ownership regression only if another document/session seam moves out of `RollMainWindow` beyond the current file-path/MRU ownership,
3. one small reusable project fixture follow-up only if test setup starts spreading further,
4. one narrow parameter-tree regression only when another schema-construction or domain-sync seam is extracted from `my_parameters.py` beyond the now-covered seed/well state and creation helpers.

**Outdated Items From The Previous Roadmap**

The following items should now be considered completed or reordered:
1. “Create `ProjectService` first” is no longer future work.
2. “Create a higher-level batch sidecar-load method” is no longer future work.
3. “Extract `FilterService` next” is no longer future work.
4. “Introduce `SessionState` first” is no longer future work in the strict sense; the phase is already substantially complete.
5. “Create `ImportService` next” is no longer future work in the strict sense; the phase is already substantially landed.
6. “Only the O/A builder extraction has started” is now outdated; the builder-module pattern already covers most main tabs.
7. “Add one direct geometry-generation correctness test for a minimal template or block” is no longer future work; both minimal and rolled-template active-path regressions now exist.
8. “Add one direct O/A regression that proves presentation-only redraw skips histogram recomputation” is no longer future work.
9. “Add one higher-level offset redraw regression that distinguishes controller invalidation from visible-activation reuse” is no longer future work.
10. “Add one geometry-to-plot invalidation regression that exercises cache reset after a broader analysis refresh” is no longer future work.
11. “Add one narrow regression for shared local-coordinate preparation from the live binning paths” is no longer future work.
12. “Add one narrow regression for any future shared binning-completion helper” is no longer current future work; that helper and its direct regression now exist.
13. Worker cleanup should still not be ahead of plot/redraw extraction and final state-ownership cleanup.
14. “Decide whether project file paths belong permanently in `RuntimeState`” is no longer immediate future work; the runtime document-context decision has now been implemented with a dedicated service boundary.
15. “Merge `calcPatternPointLists()` into `calcPatternPointArrays()`” is no longer future work; the direct-array path now exists and the list helper has been removed.
16. “Keep pattern-seed cached point arrays for later use” is no longer current design guidance; that cache path turned out to be dead code after pattern rendering and analysis moved to shared grid traversal.
17. “Prefer the property-panel survey edit/apply cluster as the next `RollMainWindow` seam” is no longer future work; that controller boundary has now landed with regression coverage and continues to rely on the existing XML-backed `survey.deepcopy()` copy path.
18. “Check if CRS coordinate transform goes in the right direction for well-files” is no longer future work; direct regressions now cover the active transform direction.

**Recommended Next Step**

If choosing one next step only, do this:
1. Reassess before taking another `my_parameters.py` slice: the tiny seed-type routing and refresh seams are now helper-backed, so the next useful step is either one more behavior-aware parameter-tree extraction only if a comparably small remaining seam is identified, or a switch to selective worker cleanup rather than forcing another wrapper-helper split.

Why:
1. The active geometry-generation path, live localization seam, live bin-update seam, and live completion tail in `roll_survey.py` now all have direct regression coverage and extracted helper boundaries.
2. Recent regressions came from parameter-tree coupling, and the lower-level state/default/wiring duplication in `my_parameters.py` has now been reduced further, including the preview-item setup/update seams, the preview text/error-state helpers, two shared constructor clusters, the pattern-list synchronization callbacks, four higher-level `MySeedParameter` coordination slices, the repeated block/template seed traversal, the remaining pattern-list side-effect bundle, the repeated `MyWellParameter` sampling/header flow, the repeated circle/spiral apply flow, the repeated local/global grid apply flow, the repeated bin-angle apply flow, the repeated bin-offset normalization/apply flow, the repeated unique-offset apply flow, the repeated reflector aggregate apply flow, the repeated analysis aggregate apply flow, the repeated block/template aggregate apply flow, the repeated roll-list apply flow, the repeated roll-parameter XYZ apply flow, the repeated configuration apply flow, the repeated well-header attribute-change flow, and the repeated seed-type routing and refresh flows.
3. The next remaining cost in `my_parameters.py` is no longer in an obvious tiny wrapper seam; what remains is more behavior-aware coupling inside larger parameter clusters, where another extraction should be chosen only if it is clearly as small and testable as the slices above.
4. Forcing another micro-extraction in the parameter tree without that clarity is now less attractive than either a deliberately chosen behavior seam or a switch to selective worker cleanup.

After that, the next step should be:
1. either continue with one more behavior-aware parameter-tree extraction in `my_parameters.py` if a similarly small seam is identified from the live code, or switch to selective worker cleanup now that the small seed routing/refresh track is exhausted.
1. keep the new document-context boundary and the new property-panel boundary stable, then prefer either one deliberately chosen parameter-tree behavior seam or selective worker cleanup before reopening `RollMainWindow` for another extraction.

Estimated effort:
small to medium: one focused extraction around one remaining wrapper seam in `my_parameters.py`, with 1-3 narrow regressions.

**Backlog of Next 12 Tickets**

1. Keep the new document-context boundary stable and only move additional file/session values if another clearly bounded seam appears.
2. Revisit worker payload shape only if a measured simplification remains after the live geometry/binning layer is easier to reason about.
3. Optionally split `config.py` into narrower read-only modules by concern if that improves navigation.
4. Re-measure performance after the above structure stabilizes.
5. Profile worker result copying on the largest realistic survey inputs.
6. Revisit XML as internal worker transport only if profiling shows it is still material.
7. Add a small reusable project fixture follow-up only if test setup starts spreading further.
8. Add redraw follow-up coverage only if another controller-owned invalidation seam is extracted.
9. Keep the new `PropertyPanelController` boundary small and stable unless a further coherent parameter-tree or survey-commit seam appears with clear regression value.
10. Revisit preserved reference implementations only during a deliberate algorithm-comparison pass.
11. Keep Phase 5 follow-up work limited to protective tests and small cleanup only.
12. Keep worker cleanup selective rather than reopening contract-shape churn.
13. Extract one more narrow schema-or-domain-sync seam from `my_parameters.py` before attempting any broader property-tree cleanup.

**Definition of Done for the First Major Milestone**

The first major milestone is complete when:
1. `RollMainWindow` is no longer responsible for project persistence internals or most loaded-state application internals.
2. Project load/save is covered by domain tests and at least one higher-level load/apply regression test.
3. Filter actions no longer duplicate orchestration logic across many handlers.
4. Session runtime state has explicit owners.
5. Mutable settings state no longer depends on `config.py` as a compatibility bridge.

The previous well-transform verification note is now resolved: direct regressions cover the active transform direction used by `RollWell.readHeader()`.

