Phase 2: extract headless core modules (sras_format, scan_geometry, config)

- core/sras_format.py: THE v6 implementation — create_scan_file (writer,
  byte-identical to the old one, enforced against the Phase-0 goldens),
  SrasFile parser with frontier/truncation walk, and zero-copy mmap
  load_angle/load_row views for multi-GB files
- core/scan_geometry.py: ScanPlan/AngleGeometry dataclasses, build_plan
  (rotated-bbox trig from MainWindow._build_scan_params), travel-limit
  validate_plan (limits now a StageLimits dataclass, not literals buried
  in the worker), format_eta + EtaEstimator (bounded deque)
- core/config.py: ScanDefaults dataclass replaces the module-import-time
  dict globals. FIXES: editing any main-window port used to rewrite
  aui_defaults.json without helios_port, silently reverting the Helios
  port every time (test_helios_port_survives_partial_update covers it).
  Also drops the inert laser_freq_hz plumbing — scans always used the
  LASER_FREQ_HZ constant.
- hardware/serial_util.py: shared 8N1 open + scored port enumeration
  (promoted from t3r_control_panel); helios_laser and the panel use it
- sc3_aui_app.py and sras_scan_manager.py migrated onto core (three
  format implementations down to one); ScanWorker now takes a ScanPlan
- tests: byte-identical writer vs golden, frontier over every truncation
  variant, mmap==eager, geometry vs golden fixtures + invariants, config
  round-trip. 28 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Ales
2026-07-28 10:20:44 -05:00
parent 67aabde4b6
commit dff9f69d78
15 changed files with 1117 additions and 809 deletions
+21
View File
@@ -0,0 +1,21 @@
"""Shared constants for the golden .sras fixtures.
These mirror the values tests/gen_goldens.py used when the fixtures were
generated against the pre-refactor code (commit d185676); they must never
change, or the byte-identical comparisons stop meaning anything.
"""
SPF = 8
SAMPLE_RATE = 6.25e9
CHANNELS = [1, 3, 4]
PREAMBLES = [f"WFMOUTPRE:CH{ch};SYNTHETIC;PT_FMT Y;XINCR 1.6E-10" for ch in CHANNELS]
BACKGROUND = bytes(range(SPF))
# build_plan inputs for the fixture geometry: 2 angles × 3 rows × 4 frames
TINY_PLAN_ARGS = dict(x_start=1.0, y_start=1.0, x_delta=0.02, y_delta=0.02,
num_angles=2, row_spacing=0.01)
LASER_FREQ_HZ = 20000.0
VELOCITY_MM_S = 100.0
def synthetic_frame(ai, ri, ci, fi):
return bytes((ai * 7 + ri * 5 + ci * 3 + fi + s) % 256 for s in range(SPF))