# KOPR R154 — Comet Analyzer state-machine and interactive release-gate report

**Base build:** `KOPR_CLEAN_BUILD_20260730_R153_COMET_ANALYZER_FIT_MULTICOLOUR.zip`  
**Implementation date:** 2026-07-31  
**Scope:** final R154 batch from the R151–R154 Comet Analyzer GUI/state remediation plan  
**Qualification status:** **IMPLEMENTED / PASS_HEADLESS / REAL PYQT5 GATE PENDING**

## 1. Result

R154 production changes are implemented. The Comet Analyzer now derives every primary control from one immutable, Qt-free control-state projection above the existing R151 capability model. The deterministic EMPTY, SINGLE_TARGET, MULTI_TARGET and BUSY matrix passes all available tests.

This build is intentionally **not marked fully release-ready** because the execution environment does not contain the Python `PyQt5` bindings. The strict release gate detects that condition and returns a failing qualification when invoked with `--require-qt`; it does not convert an unavailable GUI runtime into a false pass.

## 2. Production changes

### 2.1 Immutable complete control-state projection

Added `CometAnalyzerControlState` and:

```python
resolve_comet_analyzer_control_state(...)
```

The projection owns:

- target-selection availability;
- dataset/source/settings/formula action availability;
- Editing target visibility and availability;
- Multi-colour availability;
- adaptive Break windows availability;
- Analyse availability;
- live committed-view controls;
- Refresh availability;
- Matplotlib toolbar availability;
- Fixed slope and fixed-n availability;
- Shared-n coupling availability;
- adaptive completeness.

`WLightCurves._refresh_gui_capabilities()` now applies this projection instead of independently recreating the same rules in multiple handlers.

### 2.2 EMPTY / SINGLE / MULTI / BUSY behavior

The deterministic contract is:

- **EMPTY:** only Select comets is active; dataset, fit, Multi-colour, range and toolbar actions are unavailable.
- **SINGLE_TARGET:** dataset actions are active; Editing target is hidden; Shared n is unavailable.
- **MULTI_TARGET:** Editing target is visible and navigational; Shared n is available only with a compatible preflight group.
- **BUSY:** state-changing controls, Analyse and Refresh are blocked; an already committed graph remains navigable through the axis/magnitude controls and toolbar.

Adaptive Analyse remains blocked until all selected targets have valid break windows. Adaptive mode disables Shared n, Fixed slope and fixed n through the same projection.

### 2.3 No stale worker-state restoration

The Comet Analyzer override of `_analysis_set_busy()` does not save and replay historical `isEnabled()` values. Success, failure and cancellation all end by resolving the controls from the then-current committed dataset and target topology.

This specifically prevents the following stale-state failure:

```text
start worker with one target
→ commit or select a different target topology while BUSY
→ worker finishes
→ obsolete one-target enabled/visible states overwrite the new topology
```

### 2.4 View-only and transactional preservation contracts

R154 confirms and gates these existing contracts:

- Editing-target navigation does not redraw, reload observations or request geometry.
- Axis/magnitude view changes render committed arrays and do not invalidate/start a compute worker.
- Invalid Refresh ranges preserve the old graph.
- Incomplete calculated result sets preserve the old graph and committed result dictionary.
- Worker callbacks are job-identity guarded for success, failure, cancellation and finish.

## 3. Interactive release artifacts

### `r154_comet_analyzer_interactive_release_gate.py`

Runs the complete Qt-free matrix on every platform. When PyQt5 is available, it also invokes the real Qt smoke runner.

Outcomes:

- `PASS`: Qt-free matrix and real PyQt5 smoke both passed; release-qualified.
- `PASS_HEADLESS`: deterministic matrix passed but PyQt5 was unavailable; not release-qualified.
- `FAIL`: a matrix/Qt check failed, or `--require-qt` was used without a working Qt runtime.

Strict command:

```bash
QT_QPA_PLATFORM=offscreen \
python3 r154_comet_analyzer_interactive_release_gate.py --require-qt
```

### `r154_comet_analyzer_qt_smoke.py`

The real GUI runner instantiates `WLightCurves` and exercises:

- EMPTY;
- SINGLE_TARGET;
- MULTI_TARGET;
- BUSY from a committed plot;
- target-topology change while BUSY and capability-driven restoration;
- incomplete and complete multi-target adaptive setup;
- background worker success;
- background worker exception;
- cooperative background worker cancellation.

Offscreen command:

```bash
QT_QPA_PLATFORM=offscreen python3 r154_comet_analyzer_qt_smoke.py
```

Native Linux command:

```bash
python3 r154_comet_analyzer_qt_smoke.py --native
```

## 4. QA additions

Added:

```text
tests/test_r154_comet_analyzer_state_matrix.py
tests/test_r154_worker_terminal_states.py
tests/test_r154_view_only_actions.py
tests/test_r154_release_gate.py
tests/test_r154_comet_analyzer_qt_state_machine.py
```

The real Qt pytest module uses `pytest.importorskip("PyQt5")`; therefore its skip is explicit and visible on a host without PyQt5 rather than silently replacing the test with a source assertion.

## 5. Validation performed

### Focused R154 tests

```text
13 passed
1 skipped — PyQt5 unavailable
```

### R130–R154 regression selection

```text
189 passed
1 skipped — the same real PyQt5 state-machine test
```

### Release gates

```text
R146 PASS
R151 PASS
R152 PASS
R153 PASS
R154 PASS_HEADLESS
```

R154 deterministic checks:

```text
14 / 14 PASS
```

Strict Qt qualification in this environment:

```text
FAIL — qt_runtime_unavailable
```

This strict failure is expected and is the reason the build is not described as fully release-ready.

### Other validation

```text
Python syntax parse: 121 CLEAN production files, 0 failures
Documentation hygiene CLEAN: PASS (5/5 Markdown files)
Documentation hygiene QA: PASS (12/12 Markdown files)
Performance release validator: PASS
CLEAN SHA-256 manifest: 132/132 files
QA SHA-256 manifest: 414/414 files
```

## 6. Manual/native visual gate still required

On a Linux host with PyQt5, execute the strict offscreen runner and one native GUI run, then confirm:

1. one target remains black;
2. adding/reordering a second target preserves target colours;
3. the channel legend shows only channels with rendered points;
4. point and formula legends remain in opposite upper plot corners;
5. fit-group labels cannot be edited directly;
6. channel fit groups can be assigned only through dropdowns;
7. Multi-colour Date, Delta T, r and log10(r) projections remain ordered;
8. settings, load, Analyse, Cancel and worker-exception flows remain responsive;
9. an invalid Refresh or failed/cancelled worker preserves the previous graph;
10. the strict gate reports `PASS` and `release_ready: true`.

## 7. Files changed relative to R153 CLEAN

Modified:

```text
README.md
comet_analyzer_gui_capabilities.py
docs/CHANGELOG.md
r151_comet_analyzer_gui_release_gate.py
r152_comet_analyzer_settings_release_gate.py
wlightcurves.py
```

Added:

```text
r154_comet_analyzer_interactive_release_gate.py
r154_comet_analyzer_qt_smoke.py
```

QA additionally contains the five R154 test modules and updated static tests/gates whose old source-token assertions were superseded by the central R154 control-state contract.

## 8. Completion assessment

```text
R154 implementation                         COMPLETE
Qt-free state-machine qualification          PASS
Regression and static release qualification  PASS
Real PyQt5 offscreen execution                NOT RUN — PyQt5 unavailable
Native Linux visual smoke                     NOT RUN — PyQt5 unavailable
Final release readiness                       CONDITIONAL / PENDING QT GATE
```
