# Fixed-width preflight — F1 full strict fold0, 2026-10-04

Status: GO for one development sensitivity, **not model-family acceptance**. Owner: model_campaign/fixed_width_preflight. No fit was submitted by this preflight. Historical fixed coefficient is prescribed by `/astro/armin/koji/syndiff/dev_runs/colourwidth_r2_20261001/README.md`; no coefficient was estimated or retuned against held outcomes.

## Frozen charge construction

$$q_i=\min\{2\,\mathrm{s}\;a\,F_i\,p_i,\;200000\,e^-\},\qquad
\Delta\Sigma_i=(0.8\times10^{-3}\,\mathrm{pixel}^2)\frac{q_i-q_{\rm ref}}{10^4\,e^-}.$$

Here $i$ is the immutable scene row. $q_i$ is its peak electron charge per 2-second read. $F_i$ is external catalogue `tess_flux` in the scene's catalogue units; $a$ is the native training-only catalogue-to-image flux scale, recovered from the strict baseline's stage1 `f_prior/F_i`. Their product is source amplitude in electrons/second. $p_i$ is the maximum pixel of the native unit-flux template rendered from the strict training-only initialization, with no clipped-stamp renormalization. $q_{\rm ref}$ is the frozen contributor-weighted training charge gauge in electrons/read, using native squared-S/N weights. $\Delta\Sigma_i$ is the prescribed extra covariance per detector axis in pixels squared. The actual renderer uses raw-P-gauged Laplacian weight $\Delta\Sigma_i/2$. The two-second read conversion, generator factor 0.5 and charge cap are the historical model's constants; the FFI integration time is not substituted for the per-read time.

Training scale: 166296471.33856636. Frozen gauge: 17184.419082832468 electrons/read. Baseline stage1, stage2 and stage3 prior arrays are identical. All 2242 held scene rows have positive catalogue-driven charge. Charge median / 99th percentile / cap: 949.50 / 63683.95 / 200000 electrons/read; 47 rows reach the cap. The development scale is deliberately reused only for this same F1 strict fold0 sensitivity. Another fold or independent confirmation requires its own training-only scale, initialization and gauge.

![Catalogue-driven peak-charge audit](charge_audit.png)

Inspected: the held rows occupy the same catalogue-magnitude charge sequence; the plateau is the prescribed cap. This is a construction check, not evidence for astrophysical broadening or a residual improvement.

## Executed tests and the mapper bug

| Check | Result | Record |
|---|---|---|
| Full native source-template zero-width parity, all 14733 rows / three roles | max absolute difference 0, bit exact | `summary.json` |
| No pixel/no flux-solver access in frozen-q setup | passes forbidden-access test | `prepare_fixed_width.py` |
| Native diagnostic saved-q versus direct-q rebuild | max differences in flux, chi2 and normalized residual = 0, 0, 0 | `score_test.json` |
| Native diagnostic zero-width versus baseline | max differences = 0, 0, 0 | `score_test.json` |
| Ambiguous reordered q file | rejected | `score_test.json` |
| Training versus evaluation source/geometry row identity | exact | `patch_q_metadata.py` |

The actual smoke test found 14733 rows but only 14731 unique source IDs. Stock `bright_q_by_source` uses an ID dictionary and silently collapses the two duplicate pairs. That changed q by as much as 17.0034 electrons/read and a solved source amplitude by 0.00537. This was a real restoration bug, not a width result. The process-local adapter now requires exactly ordered source ID, bundle index, row ordinal and native x0/y0/cx/cy geometry. It restores q by immutable row identity and refuses ambiguous reordering. Role and pixel-valid masks are intentionally absent from the identity: evaluation restores held stars' roles.

Duplicate pairs: source ID 1621785840017564288 at scene/bundle rows 5994 and 5995; source ID 1631112756639029760 at rows 10927 and 10928. Both pairs are nuisance, with distinct bundle indices and all 225 stamp UIDs shared within each pair. All 14733 bundle indices are unique, so context charge scattering does not alias rows. Details are in `duplicate_rows.json`. The original source bundle has no source-ID array to trace the cause. Physical duplicates versus catalogue conversion/precision history is not determined here, and no rows were deleted or merged.

## API, frozen implementation and exact commands

Runtime is the immutable production pin `/astro/armin/koji/syndiff/dev_runs/epsf_model_closure_20261004/models/pin`, commit `3609d467a7f17c9ed56a434d71f131ca5cf15528`. Source lives in `/home/kshukawa/syndiff_pipeline/dev/.claude/worktrees/epsf-model-closure-20261004/model_closure_20261004/` on branch `codex/epsf-model-closure-20261004`. Native role masks, nuisance stop-gradient, flux estimator, optimizer and A3 recipe are retained. The existing CCD adapter's additional leaf stays exactly zero and frozen to match baseline graph.

Frozen adapter: `code/fixed_width_adapter.py`, SHA256 `ea46a5b518dbc3d0867fcc3e1477b9349b3f31a07153896e0c89ca7cc2bdbe35`.

Frozen charges: `bright_width_q.npz`, SHA256 `d0d8fb3da07353b660d4a5fa2799befa95ee25426446f5d6f19f77fd154a8a5d`. The preserved `summary_initial.json` contains superseded setup hashes; the current hashes above and `SHA256SUMS` include the duplicate-ID fix and stronger identity metadata. Charge, amplitude, peak and gauge arrays were unchanged by that metadata update. The current `summary.json` is authoritative. Source backed up as dev commit `21d41da`, pushed to `codex/epsf-model-closure-20261004`. hp_d and native FFI image BUNIT headers independently confirm `electrons/s` (units.json).

Training: install the immutable CCD adapter with `initialize=True, freeze=True`; import this directory's frozen `fixed_width_adapter` and call `install_training('/astro/armin/koji/syndiff/dev_runs/epsf_model_closure_20261004/models/fixed_width_preflight/bright_width_q.npz')` before native `SF.run`. Add exactly `--bright-width lin --bright-width-init 0.8e-3 --no-bright-width-train` to baseline arguments. q setup validates these flags. It supplies the frozen gauge to the native CLI path; final fit metadata therefore may label its gauge origin `cli`, with detailed training provenance retained in the q metadata.

Scoring: install the same CCD adapter, then call `fixed_width_adapter.install_scoring()` before using `score_oof.score` or `raster_oof.rasterise_fit`. Their API is unchanged. The wrapper loads the arm's own saved `bright_width_q.npz` and `fit_meta.json`, validates gauge and fixed coefficient, and restores the charge array. Never use unpatched stock `score_oof.build` for this arm.

Executed shell entry points `run.sh`, `score_test.sh`, `patch.sh`, `duplicate_audit.sh` activate mamba `syndiff`. JAX CPU work was pinned to CPUs 8–15, eight-core cap, BLAS/OMP threads=1. The final scorer guard replay is `score_test_final.log`; the earlier failing tests are retained to document the duplicate-ID discovery. Synthetic checkpoint files under `synthetic_replay/` are solely smoke-test constructions, not trained arms.

Conclusion: the frozen strict-fold charge construction and exact scorer pass the implementation gate for the one historically fixed-width **development** arm. No held brightness was estimated from held pixels, no width coefficient was fitted, and no scientific improvement or model-family change is established. Crossfit owns the actual fit, matched-support residual grids, magnitude/colour bounds and development-only interpretation.
