# Inventory of user-configurable values (aaredaq + aarecommon) Everything that behaves like a setting but is fixed in code, plus a map of the configuration surfaces that already exist and how well the code actually lines up with them. Line numbers are from the working tree on 2026-10-13. Scope: `aaredaq/src`, `aarecommon/src`. Pydantic request/response model defaults are excluded per the brief; the handful of *settings* models whose defaults are the live policy are listed in §6. --- ## 1. Configuration surfaces that already exist | Surface | Mechanism | Where | |---|---|---| | Per-beamline YAML | `cfg_get("dotted.key", default)` → `BeamlineYAMLConfig` | `aarecommon/config/beamline.py:61`, files in `aarecommon/config/beamline_configs/{x10sa,x06da,x06sa,simulated}.yaml` | | Local-contact Redis config | `LocalContactConfigModel` stored at `:local_contact_config` | `aaredaq/daq/config_model.py`, read `config.py:1203` | | Operator-set Redis state | `:auto_params` (`SimpleScanParameters`), `:settings`, `:simple_input_params`, `:runtime:simulate:` | `config.py:963`, `:630`, `:948`, `:1050` | | Environment | `BEAMLINE`, `JWT_AAREDAQ_KEY`, `AAREDB_SHARED_PASSWORD`, `AAREDB_USERNAME`/`AAREDB_PASSWORD`, `AARE_RECOVERY_CODE`, `AARE_BEAMLINE`, `USER` | `aarecommon/config/beamline.py:30`; `beamline_dispatch/default/beamline_dispatch.py:20`; `aaredb.py:65,132,152,331,335,407,411`; `server.py:179`; `gui/threads/daq_worker.py:1249`; `aarecommon/models/auth.py:67` | | Beam-centre calibration data | `beam_centres.json` | `aarecommon/config/beamline_configs/beam_centres.json` | ### 1.1 `LocalContactConfigModel` — the only Redis-backed, UI-editable policy today | Field | Default | Bounds | |---|---|---| | `mount_to_center_sleep_s` | 0.0 | 0–120 | | `line_scan_loop_face_y_padding_fraction_each_side` | 0.5 | 0–5 | | `line_scan_loop_all_y_padding_fraction_each_side` | 0.5 | 0–5 | Three fields. Almost everything in §3–§5 is a candidate to join them. ### 1.2 Keys the YAMLs define `x10sa.yaml` / `x06da.yaml` share a nested schema (`gui.*`, `shared.*`, `daq.hardware.*`, `daq.data_collection_settings.*`, `daq.auto_raster.*`). `simulated.yaml` is a **flat, unrelated schema** (`redis_host`, `smargon_base`, `tell_url`, …) — see §2.3. --- ## 2. Config-integrity findings (code vs YAML) Produced by diffing every `cfg_get("…")` key in code against the flattened YAMLs. ### 2.1 Keys read in code that no YAML defines — the code default *always* wins | Key read | Where | Falls back to | Likely intended key | |---|---|---|---| | `daq.data_collection_settings.default_rotation_settings.incr_omega_deg` | `daq.py:2334` | 0.2 | YAML has `increment_omega_deg` | | `daq.data_collection_settings.default_raster_settings.{dtz,exp_time_s,transmission}` | `gui/scan_logic/raster_grid_manager.py:143-149` | 200.0 / 0.01 / 1.0 | YAML has `default_raster_scan_settings` | | `daq.data_collection_settings.default_screening_settings.exp_time_s` | `gui/panels/rotation_data_collection.py:116` | 0.1 | no screening block exists in any YAML | | `daq.auto_raster.exclude_pin_x_overlap` | `operations/common/ml_bounding_box.py:475` | True | — | | `daq.auto_raster.exclude_pin_max_overlap_fraction` | `operations/common/ml_bounding_box.py:480` | 0.75 | — | | `daq.hardware.jfjoch_url` | `aarecommon/config/beamline.py:45-49` | per-beamline host | YAML has `shared.jfjoch.jfjoch_url`, which `beamline.py:77` reads — two readers, two keys | | `endpoints.smargon_base`, `epics.pv_prefix` | `aarecommon/config/beamline.py:64-65` | None | legacy | ### 2.2 Keys defined in a YAML that nothing reads — dead config | File | Key | Note | |---|---|---| | x06da | `daq.detector_limit_modifier = 2.0` | Code reads `daq.detector_distance_limit_modifier` (`daq/devices.py:51`). x10sa spells it right. **x06da silently runs with modifier 1.0.** | | x06da | `daq.auto_raster.line_scan_y_padding_fraction = 0.15` | Superseded by the two `*_each_side` fields in `LocalContactConfigModel` | | x06da | `daq.hardware.zoom_min = 1`, `zoom_max = 1000` | never read | | x06da | `daq.hardware.sam_cam = 'white_balance_ratio = 1.233'` | never read | | both | `daq.db.aaredb_url` | `aaredb.py:44` hard-codes the host as a default arg instead | | both | `daq.maximum_flux` | never read | | both | `beamline_id` | never read | ### 2.3 Per-beamline gaps - **x10sa has no `daq.auto_raster.*` block**, so grid padding (0.15/0.15/=top) and `include_crystal=False` come from code (`ml_bounding_box.py:277-279, 455`). May be intentional; it is invisible. - `gui.cameras.secondary_beamline_camera_url` exists only in x06da and its only reader is commented out (`gui/main_window.py:520`). - **Under `BEAMLINE=SIMULATED` every `cfg_get` misses** because `simulated.yaml` uses a different schema, so the simulated beamline runs entirely on the in-code fallbacks in §3. ### 2.4 Same concept, different fallbacks | Concept | Values | Where | |---|---|---| | Default raster dtz | 200 · 250 · 200.0 | `daq.py:2093`, `daq.py:2318`, `raster_grid_manager.py:149` | | Default rotation dtz | 250 (daq) · 110 (dispatch) · 150 (`SimpleScanParameters`) | `daq.py:2330`, `beamline_dispatch/default:60`, `aarecommon/models/models.py:700` | | Face-detection sweep | model default `steps=14, step_size=15` vs inline `steps=7, step_size=30` | `operations/face_detection/models.py:14-16` vs `daq.py:2501` | --- ## 3. Hard-coded values — automation & beamline policy These are the strongest candidates for `LocalContactConfigModel` or YAML. ### 3.1 Automation sequence (`daq.py`) | Value | Location | Note | |---|---|---| | `MIN_SPOTS_LOW_RES_THRESHOLD = 10.0` | `daq.py:280` | crystal-present threshold | | `AUTOMATION_BUSY_TIMEOUT_S = 600` | `daq.py:281` | | | `MANUAL_RASTER_BUSY_TIMEOUT_S = 600` | `daq.py:282` | | | `AUTO_RASTER_MAX_IMAGES = 4500` | `daq.py:283` | grid is scaled down to fit (`ml_bounding_box.py:132`) | | `AUTO_RASTER_MIN_CELL_SIZE_MM = 0.005` | `daq.py:284` | | | `AUTO_RASTER_SKIP_IF_EXCEED_MAX_IMAGE_THRESHOLD = True` | `daq.py:285` | behaviour flag | | `_sample_sync_min_interval_s = 2.0` | `daq.py:305` | TELL sync rate limit | | smargon trace path `/sls/mx/applications/logs/smargon_trace.csv` | `daq.py:301` (also `gui/panels/smargon_trace_panel.py:520`) | | | rotation `min_exp_time = 0.0011` | `daq.py:1446` | silently clamps the request | | resolution fudge `new_res = 1/((1/res) + 0.01)` | `daq.py:1666` | spreadsheet target-resolution → dtz | | omega sanity bound `-2000 < val < 2000` | `daq.py:1702` | | | face detection `steps=7, step_size=30, face_min_ratio=0.3` | `daq.py:2501` | overrides the model defaults | | run token `secrets.token_hex(3)` | `daq.py:2518` | 6 hex chars shared by raster+rotation | | auto-raster seed grid = `beam_size × 0.5` | `daq.py:2534` | | | autofocus `settle_time_s = 1.0` | `daq.py:1901` | | | screenshot `settle_time_s = 0.2` | `daq.py:2262, :2272`; `operations/screenshot/service.py:61, :67` | | | mount-to-centre, unmount waits `sleep(2)`, `sleep(0.2)` | `daq.py:1980, :2574`, `:1740, :1749` | | | `smargon_wait(timeout=180)` | `daq.py:2016`; `raster/service.py:534` | | | default dtz min/max fallback `100` / `1000` | `daq.py:3028-3029` | YAML overrides exist | | raster/rotation default fallbacks | `daq.py:2312-2337` | see §2.4 | ### 3.2 Mounting (`operations/mounting/service.py`) | Value | Location | |---|---| | `DRY_AFTER_FAIL_COUNT = 3` | `:14` | | `STOP_AFTER_FAIL_COUNT = 5` | `:15` | | mount `timeout=360.0` | `:83` | | unmount `timeout=60.0` | `:65, :154, :173, :186` | | magnet-sensor check `timeout=10.0` (default 1.0) | `:146` (def `:33`) | | TELL post-mount `sleep(0.1)` | `:41` | ### 3.3 Raster (`operations/raster/service.py`, `models.py`) | Value | Location | Note | |---|---|---| | Y re-target threshold `beam_size.y × 2.0` | `:302`; and `max(beam.y×2.0, cell.y×4.0)` `:736` | | | line-scan cell height `beam_size.y × 0.25` | `:730` | | | per-row overhead `n_y × 0.3 s` | `:441` | time estimate | | total-time cap `> 1200 s` | `:446` | refuses long rasters | | detector timeout `expected × 1.1 + 60` | `:428` | | | `wait_till_running(timeout=60.0)` | `:506`; `rotation/service.py:35` | | | aerotech `wait_till_done(timeout=60)` | `:438` | | | `wait_for_screenshot_s = 0.3` | `raster/models.py:26` | | | `sleep(0.2)` settle | `:644` | | ### 3.4 Auto-raster bounding box (`operations/common/ml_bounding_box.py`) | Value | Location | Note | |---|---|---| | grid padding x / y / y_bottom `0.15 / 0.15 / =y` | `:277-279` | YAML-overridable (x06da only) | | `include_crystal = False` | `:455` | YAML-overridable (x06da only) | | `exclude_pin_x_overlap = True` | `:475` | no YAML key anywhere | | `exclude_pin_max_overlap_fraction = 0.75` | `:480` | no YAML key anywhere | | line-scan cell = `beam_size × 0.8` | `:258` | | | line-scan `exp_time_s=0.01, transmission=1.0` | `:378-379` | fixed regardless of beamline defaults | | image-count scale `(count/max)**0.5` | `:132` | | | `sleep(0.2)` | `:163, :414` | | ### 3.5 Loop centering & face detection | Value | Location | Note | |---|---|---| | `overlap_with_pin = 0.5` | `operations/loop_centering/models.py:36` | settings-model default | | `zoom_value = 200.0` | `:38` | | | `max_attempts = 2` | `:39` | | | `target_tolerance_um = 500.0` | `:40` | | | `sleep(0.2)` | `loop_centering/service.py:100` | | | face detection `steps=14, step_size=15, face_min_ratio=0.3` | `face_detection/models.py:14-16` | overridden at `daq.py:2501` | | angle wrap `< 720` | `face_detection/service.py:91` | | | centre-correction `tolerance=0.2` | `:129` (def `:51`) | | | MAD outlier `k=3.5` | `face_detection/utils.py:39, :69` | | | `enlargement_factor = 2` | `beamcenterfit.py:44` | comment says 50% | | ML inference `conf=0.4` | `aarecommon/lc_infer_wrapper.py:60` | detection confidence floor | | `RETRY_COUNT = 3`, `RETRY_SLEEP_S = 0.1` | `mlbox.py:46-47` | | ### 3.6 Per-beamline dispatch defaults (`aaredaq/beamline_dispatch/`) | Value | Location | |---|---| | rotation `{exp_time_s: 0.04, dtz: 110, incr_omega_deg: 0.2}` | `default/beamline_dispatch.py:60` | | auto-raster `{exp_time_s: 0.04, transmission: 1.0, dtz: 200}` | `default/beamline_dispatch.py:65` | | x10sa rotation `transmission: 0.1`; raster `{0.02, 0.1, 200}` | `x10sa/beamline_dispatch.py:129, :134` | | x06da rotation `transmission: 1.0`; raster `{0.04, 1.0, 150}` | `x06da/beamline_dispatch.py:126, :130` | | x06da detector tilt `poni_rot1 = -0.001396263`, `poni_rot2 = -0.003839724` | `x06da/beamline_dispatch.py:109-110` (also `aarecommon/math/diffraction_geometry.py:83-84`) | | x06da flux-vs-energy tables | `x06da/beamline_dispatch.py:10, :25` | These duplicate the YAML `default_*_settings` blocks with different numbers (§2.4). --- ## 4. Hard-coded values — hardware, calibration, network ### 4.1 Lamp brightness — three disagreeing encodings | Encoding | Front light | Back light | Location | |---|---|---|---| | EPICS PV predefs | off 1.49 · half 2.0 · max 3.0 | off 0 · half 0.98 · max 1.2 | `daq/devices.py:57-70` | | % ↔ raw conversion | 0 % at ≤1.0, 100 % at ≥2.5, linear `/1.5` | 100 % at 3.0, linear `/3.0` | `daq.py:1767-1792` | | BEC device YAML `userParameter` | on 3.0 · off 0.0 | on 1.3 · off 0.0 | `pxii_bec/…/pxii-devices.yaml` (out of scope, for comparison) | Back light "max" is 1.2 in one place, 3.0 in another, 1.3 in the third. ### 4.2 Other hardware constants | Value | Location | Note | |---|---|---| | `CHI_ZERO_TOLERANCE_DEG = 1e-3` | `daq/devices.py:31` | | | `dtz_mod` default 1.0 | `daq/devices.py:51` | x06da YAML key misspelled (§2.2) | | PV `timeout=10.0` ×4 | `daq/devices.py:60, :67, :77, :90` | | | `det_z(..., timeout=60)` | `daq/devices.py:305` | | | `smargon_wait(timeout=10.0)` | `daq/devices.py:364, :371` | | | `DEFAULT_LENS_MAGNIFICATION = 10.0` | `config.py:53` | YAML-overridable | | `ABR_POS_MOUNT`, `ABR_OMEGA_MOUNT = 0.0` | `config.py:47-52` | | | `SMARGON_HOME` (z=18), `AERO_HOME` | `devices/smargon.py:19-20`; `devices/aerotech.py:10`; `gui/panels/smargon_trace_panel.py:33-35` | three copies | | `SAFE_POSITION = 600` | `daq/workflows.py:13` | detector safe dtz | | `dtz_safe_position` fallback 300.0 | `config.py:1246` | x06da YAML has `None` | | area detector `gain=15, expo=0.025` | `devices/area_detector.py:113` | | | `MIN_DTZ = 108.0 # beamline dependent` | `gui/panels/scan_settings_panel.py:122` | its own comment says so | | `MAX_OMEGA_SPEED_DEG_S = 500.0` | `gui/panels/rotation_data_collection.py:19` | | | `MIN_EXP_TIME_S = {X06DA: 1/900, X10SA: 1/120}` | `rotation_data_collection.py:21` | detector frame-rate limits | | `STRATEGY_MIN_EXP_TIME_S = {X06DA: 0.002}`, `DEFAULT_MIN_EXP_TIME_S = 0.0005`, `FIXED_FRAME_RATE_HZ = 100.0` | `gui/panels/smart_rotation_panel.py:41-48` | | ### 4.3 Hosts, URLs, paths (code fallbacks) | Value | Location | Note | |---|---|---| | Redis `port=6379, db=0` | `config.py:105` | host is YAML-overridable, port/db are not | | `{bl}-redis.psi.ch`, `{bl}-bec-001.psi.ch` | `config.py:104`; `devices/bec_worker.py:97` | | | smargon `http://{x06da,x10sa}-smargopolo.psi.ch:3000` | `devices/smargon.py:25, :28` | | | aerotech `http://mx-{bl}-queue-01.psi.ch:5234` | `devices/aerotech.py:20-26`; `config.py:1178` | | | TELL `http://{bl}-tell.psi.ch:22222` / `http://PC17488:22222` | `devices/tell_backend.py:90, :92` | x10sa points at a PC hostname | | JFJoch `sls-gpu-001/002:8080`, frontends | `aarecommon/config/beamline.py:45-49`; `config.py:1184-1186` | | | aaredb `https://mx-aaredb-dmz-01.psi.ch/dispatcher` | `aaredb.py:44`; `spreadsheetupdater.py:144, :168`; `tellupdater.py:382` | YAML key exists but is unread (§2.2) | | data root `/sls/mx/data` | `server.py:2366`; `gui/panels/file_path_panel.py:121`; `gui/threads/jfjoch_viewer.py:69`; `operations/screenshot/service.py:41` | four copies | | logs `/sls/mx/applications/logs` | `operations/screenshot/service.py:40`; `daq.py:301` | | | BEC log path `/tmp/logs` | `devices/bec_worker.py:99` | | | BEC device-config path template | `devices/bec_worker.py:346` | | | uvicorn `host="127.0.0.1", port=5210, workers=4` | `server.py:2625-2629` | `workers` matters for the queue-loop design | | GUI per-beamline fallbacks (DAQ URL, cert path, camera hosts, gonio id) | `gui/gui.py:51-97` | all YAML-overridable | | axis camera MJPEG params `1280x720&fps=20` | `gui/threads/axis_video_thread.py:34` | | ### 4.4 JFJoch dataset settings (`devices/jfjoch.py`) | Value | Location | Note | |---|---|---| | pgroup fallback `"p16371"`, sample `"unknown_sample"` | `:142-143` | **writes into a real pgroup when sample is unknown** | | `data_folder` routing `raw/raster`, `raw/screening`, `raw` | `:152-169` | see `docs/rotation_filename_path.md` §6.1 | | `max_spot_count = 1000` | `:198` | | | `detect_ice_rings = True` | `:199` | | | `polarization_factor = 1.0` | `:204` | | | grid `snake = True`, `vertical = False` | `:211, :214` | | | omega axis `vector = [-1, 0, 0]` | `:226` | | | `wait_till_running(timeout=60)` | `:293` | | --- ## 5. Timeouts, polling, sessions, auth | Value | Location | Note | |---|---|---| | `ACCESS_TOKEN_EXPIRE_MINUTES = 24*60*7` | `daq/auth.py:30` | JWT lifetime 1 week | | `SESSION_EXPIRE_SECONDS = 600` | `auth.py:31`; `config.py:96` (`GUI_SESSION_EXPIRE_SECONDS`) | duplicated | | `BATON_REQUEST_TIMEOUT_SECONDS = 30`; refusal re-arm `timeout_sec=10` | `auth.py:32`, `:423`; `config.py:431` default 30, `+5` grace `:439` | | | `grace_seconds = 60` | `config.py:208` | | | `STAFF_GROUP = "unx-sls_mx_bs"`, `SUPER_USERS = [...]` | `auth.py:34-35` | identity policy in code | | `ALGORITHM = "HS256"` | `auth.py:29` | | | Redis lock `timeout=10` | `config.py:349, :361, :383, :393, :479, :640, :645` | hardware lock has **no** timeout (`config.py:106`) | | `_ALL_PGROUPS_TTL_S = 60.0` | `server.py:76` | | | SSE poll `0.15 s`; spreadsheet/ref-tools SSE `10 s`; baton `0.5 s`; `0.2 s` | `server.py:283, :303`; `:1295, :1313`; `:2262`; `:2534, :2537` | | | `SSE_RECONNECT_DELAY_S = 5` | `daq/tellupdater.py:64` | | | spreadsheet updater `sleep(5)` | `spreadsheetupdater.py:187` | | | aaredb HTTP `timeout=30` | `aaredb.py:338, :414` | | | TELL command `timeout=360.0`; ready `30.0`; probe `1.0`; poll `0.01`; lazy retry `2.0` | `devices/tell_client.py:82, :87, :202, :367`; `:386`; `tell_backend.py:65`; `:172`; `:336` | | | smargon REST `timeout=2.0`; `initialize(360.0)` / `(30.0)`; `wait(60, tol 0.01, poll 0.01)` | `devices/smargon.py:43, :77`; `:111`, `:235`; `:214, :222` | | | aerotech `wait_till_done(60)` | `devices/aerotech.py:114` | | | fluorimeter `timeout_s=30.0` | `devices/fluorimeter.py:65, :91, :98, :114` | | | ZMQ `timeout_ms=2000` | `devices/zmq_client.py:27` | | | motor `timeout=5.0` / `300.0`, `poll_rate=0.01` | `devices/my_motor.py:9, :63, :84, :94` | | | filter transmission `timeout=60.0, fail_on_timeout=True` | `devices/filter_transmission.py:8` | | | `wait_position(timeout=60.0)` | `devices/workflow_tools.py:6` | | | workflow waits `60`, `sleep(10.0)` | `daq/workflows.py:163, :175, :289` | | | BEC `sleep(1)` | `devices/bec_worker.py:566` | | --- ## 6. Analysis thresholds (aarecommon) | Value | Location | Note | |---|---|---| | autofocus threshold `median × 3.0` | `math/autofocus.py:20` | | | ice veto `spots_ice > spots × 0.8` | `math/find_xtal.py:147` | comment says 50 % | | `min_efficiency = 1.0` | `find_xtal.py:178` | | | `min_low_res_spots = 10.0` | `find_xtal.py:344` | same number as `MIN_SPOTS_LOW_RES_THRESHOLD` | | mask `n_sigma = 0.5`; `n_clusters = 3`; `percentile = 60.0` | `find_xtal.py:507, :568, :587-590` | | | coordinate equality `tol = 0.1` / `0.01` | `math/coordinate.py:91, :115` | | | exception `DEFAULT_CRITICALITY` flags | `errors/exception_handler.py:14, 77, 82, 90, 133, 146, 159, 168, 177, 182, 249, 275, 299, 349, 370, 375` | decide 503 vs 422 per exception class | | logger levels (all `DEBUG`) | `config/logger.py:63-88` | no env/YAML override | --- ## 7. GUI behaviour and UX policy | Value | Location | Note | |---|---|---| | `LOW_CURRENT_THRESHOLD = 360.0` mA | `gui/widgets/message_box.py:11`; used `panels/sample_queue_panel.py:226` | gates automation start/pause | | `SNOOZE_SECONDS = 3600.0` | `message_box.py:12` | | | TELL-warning recovery pause `600 × 1000` ms | `sample_queue_panel.py:59` | | | `DEFAULT_SAMPLE_ESTIMATE_S = 150.0` | `panels/automation_panel.py:43` | ETA before data exists | | dose model `KDOSE_1A_PH_UM2_GY = 2000.0`, `GLASS_TRANSITION_K = 200.0` | `panels/smart_rotation_panel.py:29, :36` | | | resolution UI `VISIBLE_RES_{MIN,MAX,DEFAULT}_A = 0.8 / 10.0 / 2.0`, `DAQ_RES_EPS_A = 0.001`, `DTZ_STEP_MM = 10.0`, `EXP_TIME_STEP_S = 0.001` | `smart_rotation_panel.py:51-59` | | | `DEFAULT_ABR_STEP_UM = 5` | `panels/abr_tweak_panel.py:12` | | | `READBACK_GRACE_S = 1.5` | `panels/illumination_panel.py:11` | | | `ENERGY_AT_TARGET_TOL_KEV = 0.001` | `panels/monochromator_panel.py:19` | | | filename templates `"{date}/{puck}/{pos}"`, `"{sample}"`; run range `1-999` | `panels/file_path_panel.py:64, :77, :92` | `{pos}` bug, see `rotation_filename_path.md` §6.6 | | status poll `500 ms`; `SPREADHSEET_FREQUENCY = 25` ticks (≈5 s); baton tick `1000 ms`; stream restarts `1000 ms` | `gui/threads/daq_worker.py:158, :58, :217, :1897, :2077, :2351` | | | idle timer `60_000 ms`; remote-close `1000 ms` | `gui/main_window.py:880, :885` | | | status message clear `5000 ms` | `gui/widgets/status_bar.py:155` | | | automation refresh `1000 ms` | `panels/automation_panel.py:53` | | | `MAX_FRAMES_IN_FLIGHT = 2` | `gui/threads/prediction_subscriber.py:28` | | | `TABLE_MAX_ROWS = 20` | `panels/smargon_trace_panel.py:36` | | --- ## 8. Settings-model defaults (pydantic, listed for completeness) Excluded from the inventory proper, but these *are* the live policy when nothing overrides them: - `SimpleScanParameters` (`aarecommon/models/models.py:698-716`) — schema of `:auto_params`; `dtz=150, exp_time_s=0.02, incr_omega_deg=0.2, steps=1800, transmission=1.0`. - `LoopCenteringSettings` (`operations/loop_centering/models.py:36-40`) — §3.5. - `FaceDetectionSettings` (`operations/face_detection/models.py:14-16`) — §3.5, overridden inline. - `RasterSettings.wait_for_screenshot_s = 0.3` (`operations/raster/models.py:26`). - `DiffractionGeometry` field bounds (`aarecommon/math/diffraction_geometry.py:12-14`) — validation limits on energy/dtz/pixel size.