# KOPR R169 — Comet Analyzer point-size control

**Base build:** R168 Comet Analyzer Marcus dust vector alignment hotfix  
**Date:** 2026-08-02  
**Status:** PASS (headless/static GUI qualification)

## Scope

R169 adds a compact global controller for observation-point size in Comet Analyzer. The change is presentation-only and does not alter observations, channel selection, fitting, formulae, geometry, ephemerides, automatic range identity or Multi-colour results.

## User interface

A new row appears directly below **Point style**:

```text
Point size                         [ 3.0 ]
```

Contract:

- control: `QDoubleSpinBox`;
- range: `1.0` to `12.0` Matplotlib points;
- step: `0.5`;
- default: `3.0`, matching R168 exactly;
- applied to pre- and post-perihelion observation artists;
- formula-line markers and legend keys are not resized.

The control is on a separate compact row so the long Point style encoding labels remain readable in the narrow classic Analyzer sidebar.

## View-only behavior

Changing Point size performs only:

```text
persist display preference
+ increment view revision
+ redraw prepared plot
```

It performs:

```text
0 observation reloads
0 geometry jobs
0 ephemeris calls
0 fit recomputations
0 formula recomputations
0 range invalidations
```

The current automatic/user range and Matplotlib zoom are preserved under the existing R163 range contract.

## Persistence

The existing file is reused:

```text
config-files/light_curve_display_styles.json
```

A backward-compatible optional field is added:

```json
"point_size": 6.5
```

Existing R160–R168 JSON files without this field load with the default `3.0`. Invalid, non-finite or out-of-range values are ignored independently and do not discard valid comet/channel style overrides.

**Reset all display defaults** also restores point size to `3.0`.

## Production files changed

```text
wlightcurves.py
light_curve_rendering.py
light_curve_display_styles.py
light_curve_display_style_persistence.py
```

No other production file changed relative to R168.

## QA additions

```text
r169_comet_analyzer_point_size_release_gate.py
tests/test_r169_comet_analyzer_point_size_control.py
```

The cumulative QA test for the R164 artist layering contract was also corrected to include the already-production `zorder=4.0` observation artist property. The R161 PyQt availability probe was hardened against a synthetic module with `__spec__ = None`; neither change affects production code.

## Validation

- R169 focused tests: **7 passed**
- Analyzer regression R151–R169: **152 passed, 1 skipped**
- R158–R169 release gates: **PASS** (`R161: PASS_HEADLESS`)
- R169 release gate: **10/10 PASS**
- QA aggregate performance/release validator: **PASS**
- Python syntax:
  - CLEAN: **99 files, 0 errors**
  - QA: **397 files, 0 errors**
- SHA-256 manifest:
  - CLEAN: **108/108 PASS**
  - QA: **452/452 PASS**
- CLEAN distribution hygiene: no tests, benchmarks or release-gate scripts
- ZIP integrity: **PASS**

The single skipped test is the real PyQt5 offscreen smoke test because PyQt5 is unavailable on the build host. The control's Qt structure is statically validated; renderer and persistence behavior are executed directly.

## Acceptance result

```text
Default 3.0                       -> visual compatibility with R168
Change 3.0 -> 6.5                -> both observation sides redraw at 6.5
Change point encoding mode       -> point size remains 6.5
Change comet/channel colour      -> point size remains 6.5
Restart with persisted JSON      -> point size restored
Legacy JSON without point_size   -> defaults safely to 3.0
Invalid persisted point_size     -> ignored; other overrides retained
Point-size change                -> no data/fit/range invalidation
Reset all display defaults       -> point size returns to 3.0
```
