Repository navigation
fix: fitted dead-layer note follows the applied sum-peak model; refits repeat the reported dead layer - #68
Merged
Merged
Conversation
…aks as explained With the sum/escape model on, the unlisted-line check proposed on the bare spectrum and named the modelled pile-up and escape peaks as elements. SumEscapeResult.componentCounts (the fitted c S(A) + f E(A)) is now carried by the quantification and handed to ElementProposer.propose(explained:), which adds it to every fit as a fixed offset. nil (the default, and the model off) runs the proposer as before. Planted pools (tools/edx-sim proposer_sim, model on): 100 counts/px no longer names Ar, Ag, Pd; 300 counts/px no longer names Rh, Ar, In, Sn, Pd, Ag, Cd, Ru, Bi, K; a planted Fe escape with the free escape columns off no longer names La, Ce, Cr, Cs. A planted K K-alpha between two sum peaks is still named first. Model off: unchanged. Done: sum/escape peaks are explained counts for the proposer and the check. Next: docs rows for parity.md and open-items.md item 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pair is significant The pooled quantification fitted one Gaussian per line and no sum (pile-up) peaks, exSpy's model. On the public synthetic AlMgSi demo (planted truth: the simulation recipe of sim_edx.py v2.0, not a measurement) that fit read noise-free Si K-alpha 15-18 % low on the Al-rich map and fixture pools and 57 % low on the highest-rate pool, held a planted Cu K-alpha at 0 at high rate, and had a reduced chi2 of 2.1-31 on the Poisson counts. The Al+Al sum peak (2.97 keV) and its neighbours pull the continuum segment above the Al K edge, and Si K-alpha with it. FitPolicy.sumPeaks (SumPeakRule, default .whenSignificant) now tests the strongest line's self pair alone with EDSSumEscapeFit; when its pile-up coefficient is 3 sigma above 0 (sigma scaled by sqrt(max(chi2_red, 1)) of that fit) the sum peaks of the four strongest lines are fitted and kept, otherwise the result is the old fit bit for bit. The self pair is the test because the cross pairs absorbed an unlisted Cu K-alpha (Ni K-alpha + O K-alpha) on a synthetic spectrum with no pile-up. The footer names the decision with its number, and names the alpha lines within one FWHM of the self pair (Ar K-alpha, Ag L-alpha under Al+Al): a real line there is read as pile-up. .off (FitPolicy.exspyEquivalent) is the old fit; a caller passing sumEscape keeps full control. The unlisted-line and fit-range refits repeat the reported decision; the proposer axis refinement runs without sum peaks. Noise-free Si K-alpha against planted: -0.0 to -4.0 % on five pools, -7.9 % at the highest rate; Cu K-alpha 92-97 % of planted at high rate (0-42 % before); reduced chi2 on the Poisson counts 0.70-1.08. The absorption-truth Si pin moves from -0.203 to -0.067 (+- 0.051), and the old pin stays on the .off fit. The Al K-alpha tail stays a detector calibration: measured once with TailEstimate on a high-count spectrum and typed, it brings Mg K-alpha from -22 to -32 % to within 5 % (matrix map -15 %). New fixture edx-demo-pools.json from tools/edx-sim/demo_pools.py. The golden scenarios pin the explicit off model; their numbers are unchanged, only the two new nil fields are added. Results stay validation none. Reviewed (numerics, two independent reviews): sigma scaling, the coincident-line caveat, the axis refinement cost and the wording fixed. BREAKING CHANGE: PooledQuantifier.run fits sum peaks by default when the data support them, so nets, at% and wt% change on spectra with pile-up. Migration: pass a policy with fit.sumPeaks = .off (or FitPolicy.exspyEquivalent), or sumEscape: SumEscapeModel(), to keep the old numbers. A stored QuantificationPolicy without the new field decodes as .off and its footer names the non-default policy. Done: sum peaks on by default in the pooled fit, with planted-truth tests on the demo pools. Next: the parity and open-items rows once the open PRs on those files land, a check of the default on real data with published values, and the sum-peak question for the auto ID possible candidates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The default continuum cuts its Bernstein segments at the Al K edge with a jump between two channels. A detector cannot record that: every channel is the incoming spectrum convolved with the resolution (about 75 eV FWHM at 1.56 keV), and the Al K-beta line sits on the edge energy itself. FitPolicy.continuumEdgeStep = .broadened weights each segment by the detector Gaussian step at its edges (Phi_a - Phi_b, sigma from the line-width law at the edge, the line model resolution) and continues each polynomial past its edge at its end value. The weights telescope to 1 and stay non-negative. .sharp stays the default and is the earlier form value for value; the default policy JSON is byte for byte unchanged (the key is written only when not the default; old stored policies decode to .sharp). Measured on the public Leeds pure-Al wedge (Snelson, Collins, MacArthur, doi 10.5518/1826, CC BY 4.0), same refined axis: the model fell 351 counts more than the data across the edge channel pair (42 broadened), the data sat 1677 counts above the model in 1.53-1.65 keV (907) and the model 39 % above the data in 1.65-2.2 keV (19 %). On a generated Al film with a broadened edge a weak Si K-alpha reads -38.7 % sharp and -25.4 % broadened. Done: broadened edge step as an option, tests on a generated spectrum. Next: re-measure the registered synthetic results with a convolved generator and make .broadened the default; the order-5 segment above the edge is the remaining Si lever. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both add FitPolicy fields with a tolerant decoder; the resolved policy keeps sumPeaks and sumPeakSignificance as that branch encodes them and writes continuumEdgeStep only when it is not the default. Done: stacked on the pooled sum-peak branch, gate green. Next: open the PR against that branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or resolution A detector records every photon with its Gaussian resolution, so an absorption edge in the continuum reaches the spectrum as a blurred step (about 75 eV FWHM at the Al K edge for 130 eV at Mn K-alpha), never as a jump between two channels. The generators drew their lines as Gaussians but planted the continuum, and its Al K edge, unblurred. A fit whose step is broadened by the detector (FitPolicy.continuumEdgeStep = .broadened) was therefore measured on synthetic spectra that no detector could record. tools/edx-sim/detector_blur.py rebuilds the forward model continuum of the same pixels and replaces it by its blurred copy (photon by photon, sigma from the line-width law at each energy, counts conserved): expected - C + blur(C). - absorption_truth_sim.py, weakline_sim.py, proposer_sim.py: forward.py itself is unchanged; the rebuilt continuum equals forward.py to 5e-16 in 9.5-19 keV. - demo_pools.py: the demo generator (sim_edx.py v2.0) uses the same continuum; each pool is rebuilt from the truth arrays and checked against the generator in 9.5-19 keV (within 1e-9, the script stops otherwise). The pools counts stay the demo files Poisson draws: the blur moves at most 1.2 counts in a channel, 0.1 sigma or less. - realistic_l_sim.py: its 3 % Si K step is a normal-CDF step of the detector width. Fixtures moved: eds-absorption-truth.json, eds-weakline-sim.json, eds-proposer-sim.json, eds-realistic-l-sim.json, edx-demo-pools.json (expected spectra only; planted areas, truth and counts unchanged). With the continuum step still sharp, one registered number moved: the weak-line bias on generator S reads -297 +- 23 at 882 planted Mg K-alpha and -251 +- 21 at 2899 (was -310 +- 23 at both); the badge text quotes the new run. Generator A is unchanged. Done: generators blur the continuum, fixtures regenerated. Next: make the broadened step the default and re-measure the registered synthetic results under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ector by default FitPolicy.continuumEdgeStep and ContinuumForm.edgeStep now default to .broadened: each continuum segment is weighted by the detector Gaussian step at its edges (sigma from the line-width law at the edge, the line model resolution), as a detector records an edge. .sharp, the cut at the edge channel, stays an option and is the earlier default value for value. exSpy has no edge in its background (one polynomial), so no parity path changes. Why: on the public Leeds pure-Al wedge (Snelson, Collins, MacArthur, doi 10.5518/1826, CC BY 4.0; Titan 200 kV, Super-X, 214 793 px, the refined axis) chi2 per channel in 0.8-2.6 keV falls from 6.63 to 3.10, the data excess in 1.53-1.65 keV from 1677 to 907 counts and the model excess in 1.65-2.2 keV from +39 % to +19 %. The synthetic generators now blur their continuum the same way (previous commit), so the planted results were re-measured under the new default (synthetic truth is planted, not measured): - demo pools, noise-free, with the default sum peaks, Si K-alpha sharp -> broadened: recipe A -4.1 -> -1.3 %, matrix map -4.3 -> -1.6 %, L5 -8.4 -> -2.4 %, L6 -1.2 -> -1.1 %, L7 -0.03 -> -0.04 %; absorption-truth pool -1.5 % (bounds kept). - weak-line badge (30 seeds): -270 +- 31 at 900 Mg (A), -272 +- 23 at 882 (S), -325 +- 38 at 3000 (A), -226 +- 22 at 2899 (S); the selection still keeps Kramers (9,5). - absorption-truth Si pin: -1.67 % +- 0.036 with sum peaks (was -6.7 +- 0.051), -17.2 % without (was -20.3); the 501-count Mg envelope is kept. - realistic-L Ti nets 2979/3134/2982/3074/3057 (mean 3045), range statement Ti -6.4 ... -11.7 %, -1.8 ... -3.2 sigma (bands widened to -5.5 % and -1.7 sigma). - proposer null variance re-pinned; demo L5: the sum peaks now take Mg from -24.5 % to -15.6 % (bound 0.10 there, 0.03 elsewhere). - REGISTERED BAR LOWERED: the proposer 1 % Mg x100 cell on the Kramers continuum is a candidate in 5 of 6 draws (was 6): seed 2 shows no residual peak at Mg for the loop; either change alone loses it. - ProposerMgDiagnosisTests: set M is the sharp model family, so its control and the +3 eV comparison run on .sharp (M 0.70, S+3 0.55); broadened M 0.695, S+3 0.702. In-test generators blur their continuum too (SpectroTestSupport DetectorBlur): generator A of SpectroscopyFitTests and the proposer generator. Both blurs (Swift and tools/edx-sim/detector_blur.py) keep the input within one kernel reach of an axis end, where counts from beyond the axis are missing (review finding); the forward and demo fixtures were regenerated for that rule (expected spectra only, a few counts at the ends). The policy now always writes continuumEdgeStep; a stored policy without it reads .sharp (the form it was run with) and gets the non-default footer line. Diagnosis of the remaining misfit above the edge (Leeds, 1.56-1.65 keV, +718 counts broadened): it is the Al K-beta line, tied at eXSpy 0.0132 of K-alpha. Fitted free it reads 0.028 (0.030 at 1.5575 keV) and the excess goes to -121, the 1.65-2.2 keV misfit to +5 %, chi2 to 2.35; a free Gaussian scanned 1.54-1.63 keV has its chi2 minimum at 1.56-1.58 keV, the K-beta energy. Not shipped: tied at EPQ 0.0315 it breaks the planted synthetic sets (they plant 0.0132), and a free K-beta reads 0.011 of a planted 0.0132 on the synthetic film. An extra continuum split at 1.65-1.70 keV also removes the excess but worsens the synthetic film Si (-11.3 -> -16.1 %); a low-energy tail (incomplete charge) worsens both. Nothing else changed for it. Migration: a fit with the default policy moves wherever an edge (the Al K edge by default) lies in the fitted range. To reproduce an earlier number, set FitPolicy.continuumEdgeStep = .sharp. The default policy canonical JSON gains "continuumEdgeStep":"broadened". Done: broadened step is the default, registered synthetic results re-measured. Next: an Al K-beta weight option for the quantification fit (EPQ or free), measured on a synthetic set that plants the EPQ weight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The goldens record every output of the six fixture scenarios bit for bit, so they move with the default continuum step (now broadened by the detector), with the regenerated synthetic fixtures (their continuum blurred by the detector) and with the new continuumEdgeStep field in each dump of the fit settings and policy. The base branch had not re-recorded them for that field. Recorded with SPECTRO_GOLDEN_DIR, the array companions expanded with SPECTRO_GOLDEN_ARRAYS_DIR; the full suite, the parity target included, is green afterwards. Done: goldens re-recorded. Next: CI on the stacked branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…adened-step branch Conflicts resolved additively: FitPolicy keeps continuumEdgeStep and gains main continuumDeadLayer, which the custom Codable now encodes and decodes (absent reads nil); PooledQuantification keeps appliedSumEscape and gains continuumDeadLayerMicrons. The spectro goldens were re-recorded on the merged tree. Full suite green, the parity target included (306 + 83 tests). Done: main merged. Next: CI on the stacked branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FitPolicy.lineWeights and FitSettings.lineWeights choose the table that ties each K family's K-beta to its K-alpha:
.exspy (default, the parity path, every number unchanged) or .epq (NIST EPQ LineWeights.csv: Al 0.0315, Mg 0.0174,
Si 0.0571, Cu 0.146 of K-alpha against eXSpy's 0.0132, 0.01, 0.0278, 0.132). EDSLineWeights becomes Codable
("exspy", "epq"); the existing case epqK encodes as "epq" and gains the alias .epq. Every line model of the fit
(EDSFit, the sum/escape fit, the axis refinement, the plot model, the unlisted-line check) takes the choice; a
non-default table is named in the footer, and a stored policy without the field reads .exspy.
The default stays .exspy: on the public Leeds standards the EPQ tie fits the pure-Al wedge and Mg K-beta on MgO
better, but EPQ Si K-beta worsens MgO at 1.78-1.95 keV (numbers in docs/parity.md).
tools/edx-sim/xray_model.py plants K-beta at either table (EDX_SIM_KBETA, default epq, the physical weights);
with EDX_SIM_KBETA=exspy the committed fixtures regenerate byte-identical, so no fixture moves.
Tests: LineWeightTableTests (red before the policy reached the fit: Si K-alpha read -43.6 % on a film planted
at EPQ whatever the policy).
Done: the .epq option, the generators' table switch, the measurement. Next: decide per element (Al and Mg EPQ,
Si open) if a mixed table is wanted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…umber moves The golden dump lists every field of the quantification's fit settings, so the new lineWeights field adds one line per quantification (= .exspy) to demo-edx-tiny-dm4, eds-realistic-l-sim, eds-unlisted-sim and eds-weakline-sim. Recorded with SPECTRO_GOLDEN_DIR and diffed: those added lines are the only change; every number and array is identical. The parity target passes. Done: the opt-in EPQ table, measured, default kept at eXSpy. Next: a per-element table (EPQ for Al and Mg) if the Si K-beta question is settled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-peak branch Conflicts resolved keeping both sides: - FitPolicy.init takes sumPeaks, sumPeakSignificance and continuumDeadLayer. - FitPolicy CodingKeys and init(from:) now carry continuumDeadLayer. The branch had a hand-written decoder, so without this the dead-layer term was dropped from the encoded policy, its footer line and canonicalJSON (caught by testPooledQuantifierRunsTheTermTypedAndFittedAndNamesIt). - PooledQuantification keeps appliedSumEscape and continuumDeadLayerMicrons; the result sets both. - Spectro goldens: both new nil lines kept. Re-recorded from the merged code: byte-identical to the resolved files, no number moves. Done: the sum-peak branch is on current main, fast gate and SpectroGoldenTests green. Next: stack the continuum-edge branch on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the sigma terms) into the edge-step branch Conflict in QuantificationPolicy.swift resolved keeping both sides: - FitPolicy.init takes continuumEdgeStep and continuumDeadLayer. - CodingKeys, the hand-written encode(to:) and init(from:) carry both: continuumEdgeStep written only when not sharp, continuumDeadLayer only when set, so the default canonical JSON is unchanged. Done: the edge-step branch is on the updated sum-peak branch and main, fast gate green. Next: re-record the goldens for the edgeStep line this branch adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…no number moves The fit-settings dump in each golden scenario gains one line per quantification, background.continuum.edgeStep = .sharp, from the field this branch adds; the branch had not re-recorded for it. Recorded with SPECTRO_GOLDEN_DIR from the merged code: 9 added lines, no other line differs, the array companions are unchanged. Done: SpectroGoldenTests green on the edge-step branch. Next: stack the broadened-default branch on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved for this branch: QuantificationPolicy keeps the broadened default and always encodes continuumEdgeStep (the edge-step side carried the sharp default and the omit-when-sharp encoding this branch replaced). The spectro goldens were re-recorded from the merged code: byte-identical to this branch, which already carries the edgeStep lines with the broadened values. The merged tree equals this branch. Done: the broadened-default branch is on the updated edge-step branch and main, fast gate green. Next: stack the EPQ line-weight branch on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranch No conflicts; the merged tree equals this branch (the commits below only re-merged the stack onto main). Done: the line-weight branch is on the updated stack and main, fast gate and parity target green. Next: CI on the stacked PRs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd refits repeat the reported dead layer The fitted Si dead-layer warning now says it was fitted without the sum/escape model whenever the final fit ran one, also when the model came from FitPolicy.sumPeaks with no sumEscape passed (the clause was read from input.sumEscape alone, so the default whenSignificant policy that keeps sum peaks left it out). The fit-range sensitivity refit and the unlisted-line refit now repeat the reported fit's data-driven choices through one helper: the sum-peak decision as before, the fitted dead-layer thickness typed back in, and the file's axis locked when the reported fit's refinement failed on an unlocked axis. Before, each refit fitted a thickness of its own on its own range (0.814 um at 10 keV against 0.376 um at 20 keV on the dead-layer pool), so the range statement mixed a range change with a dead-layer change. Tests: the warning and the range refit are red first; a canonicalJSON round trip with every FitPolicy field non-default, the default without a continuumDeadLayer key, and missing-key reads (dead layer nil, sumPeaks off). Doc comments on the FitPolicy encoder and decoder now match the code; the continuumDeadLayer note says its 0.21 um measurement used sum peaks off. Done: the three should-fixes and the nits from the merge review. Next: CI on the PR, then it lands with the stack below it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now holds the sum-peak work as one squash commit whose tree equals the sum-peak branch this branch was built on. The one conflict, in QuantificationPolicy.swift, keeps the sum-peak code as on main and adds this branch's continuumEdgeStep field, its encoder and its decoder default. No golden moves in the merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SwiftLint trailing_newline and SwiftFormat consecutiveBlankLines flagged line 184. Lint (SwiftLint 0.65.1, SwiftFormat 0.64.4) is now clean on the whole tree. Done: lint green on this branch. Next: merge it forward into the stacked branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns) into issue-8-broadened-default Only the spectro goldens conflicted (eds-realistic-l-sim and eds-unlisted-sim, text and array companions). The merge keeps this branch side; the next commit re-records them from the merged code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bound Recorded from the merged code. Against this branch side, the only change is the one main made: the O+O self-pair sum-peak questions on the Eu and Sm M lines in eds-realistic-l-sim and eds-unlisted-sim drop out (conflicts empty, sumPeakQuestion false, the withholding CSV hash and the conflict energy arrays follow). No other number moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…issue-66-epq-line-weights
…issue-8-merge-review-fixes
…e-66-epq-line-weights Main equals the old broadened-default tip, so every conflict keeps the branch side (EDSFit.swift, QuantificationPolicy.swift). docs/parity.md keeps the branch side to avoid a duplicated edge-step paragraph from the auto-merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…issue-8-merge-review-fixes
Paullo9
enabled auto-merge (squash)
November 1, 2026 13:43
Brings in the squash-merged EPQ line-weight table (#67), the sum-peak explanation for auto ID (#61) and the docs move (#76). One conflict, a doc comment on FitPolicy.init(from:): kept this branch wording, which names every key the decoder defaults (sumPeakSignificance included); main wording listed fewer. Goldens re-recorded with SPECTRO_GOLDEN_DIR after the merge: all six scenario files are byte-identical to the merged fixtures, so no number moved and no fixture changes in this merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Nov 1, 2026
Keeps one copy of the #59 sum-peak migration note (it appeared five times) and adds #65's breaking change: the broadened continuum step is the new default, and FitPolicy.continuumEdgeStep = .sharp keeps the old numbers. Collapses the duplicate #46, #47 and CI lines. Notes under #68 that refits on the opt-in fitted dead layer repeat the reported thickness, so their numbers can change. The version is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paullo9
added a commit
that referenced
this pull request
Nov 1, 2026
* chore(main): release 0.13.0 * docs: tidy the 0.13.0 CHANGELOG entry Keeps one copy of the #59 sum-peak migration note (it appeared five times) and adds #65's breaking change: the broadened continuum step is the new default, and FitPolicy.continuumEdgeStep = .sharp keeps the old numbers. Collapses the duplicate #46, #47 and CI lines. Notes under #68 that refits on the opt-in fitted dead layer repeat the reported thickness, so their numbers can change. The version is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: macem-release[bot] <349909138+macem-release[bot]@users.noreply.github.com> Co-authored-by: Paullo9 <paullobpreis@icloud.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Paul · project thread
Part of #8
Stacked on #67 (
issue-66-epq-line-weights, the top of the #59 → #64 → #65 → #67 stack). The base is that branch, so the diff shows only this change. Merge it after #67.What
A numerics review of the stack's merge commits found three should-fixes and some nits. This PR fixes them.
PooledQuantification.swift). When the Si dead layer is fitted, the warning should say it was "fitted without the sum/escape model, which the final fit includes". Before, it said so only when the caller passed a non-offsumEscape. With nosumEscape, the choice falls topolicy.fit.sumPeaks(default.whenSignificant), and the warning left the clause out even when that policy kept the sum peaks. Now the clause is added after the sum-peak decision, fromapplied != nil. The warning keeps its old place in the list.FitRange.swift,UnlistedLineCheck.swift). Both refits now go through one helper,PooledQuantifier.pinningReportedChoices. It repeats the sum-peak decision, as before. When the reported fit fitted the dead layer, it passes that thickness as.given(microns:). When the reported fit ran on an unlocked axis and its refinement failed, it locks the refit to the file axis; the review raised this case. Before the fix, each refit fitted its own thickness on its own range: on the dead-layer pool, 0.814 µm on 0.2–10 keV against 0.376 µm on 0.2–20 keV.canonicalJSONdecodes to an equal policy and re-encodes to the same string. It also checks three things: the default policy's JSON has nocontinuumDeadLayerkey, a stored policy without that key decodes to nil, and one withoutsumPeaksdecodes to.off.continuumDeadLayerdoc now says its 0.21 µm measurement was made with sum peaks off.Tests
ContinuumDeadLayerTests.testTheFittedDeadLayerWarningNamesTheSumEscapeModelWhenThePolicyKeepsItContinuumDeadLayerTests.testTheRangeSensitivityRefitKeepsTheReportedDeadLayer. Before the fix, the refit nets differed from the pinned-thickness nets: Si 1585.5 against 1278.3.QuantificationPolicyTests.testEveryNonDefaultFitPolicyFieldRoundTripsThroughCanonicalJSONwas green from the start: it is coverage and changes no behaviour.swift test -c release --parallel --num-workers 1 -j 1 --skip SpectroKitParityTests: 509 tests green, 3 known issues that were already there. No golden moved, so the parity target was not run.CI pending.
🤖 Generated with Claude Code