What is failing
clean-build.yml's unix-usermod (float-precision) leg fails verify_dsp: six probes render differently on a single-precision MicroPython build than on the CPython target. The double-precision leg passes all 21 comparisons.
FAIL dynamics_probe.py cpython and micropython differ at output byte 2164
FAIL dynamics_extras_probe.py cpython and micropython differ at output byte 1999
FAIL dynamics_options_probe.py cpython and micropython differ at output byte 2011
FAIL biquad_component_probe.py cpython and micropython differ at output byte 137
FAIL granular_pitch_shift_probe.py cpython and micropython differ at output byte 586
FAIL echo_filter_probe.py cpython and micropython differ at output byte 167
21 comparisons, 6 failures, 0 pending
That cell is deliberate: -DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT, and the workflow says why — it "mirrors single-precision MCU targets (ESP32-P4/RP2040), where mp_float_t is 32-bit". So this is a real target, not a hypothetical one.
It is not a regression — the gate was red from birth
Worth stating plainly, because the run history looks like something broke:
14:43 95cd1c2 CI ref moves to MicroPython v1.29.0 → clean-build still green
17:54 ef71b86 last SUCCESS → v1.29.0 in use, both legs pass
18:14 79ee874 adds the verify_dsp step → FAILURE, and every run since
git merge-base --is-ancestor 95cd1c2 ef71b86 confirms the pin move precedes the last green run, and verify_dsp appears zero times in clean-build.yml at ef71b86. So the v1.29.0 pin move did not cause this. 79ee874 added a check that had never run before, and it found a pre-existing disagreement on its first attempt. Twelve consecutive failures since, all of them the same finding.
Likely mechanism, stated as a hypothesis
The bindings compute in mp_float_t and hand the result to kernel functions declared double. src/synthio/Biquad.c:
mp_float_t W0 = frequency * synthio_global_W_scale; // float on this build
...
audioif_biquad_cp_configure(&self->coefficients, self->mode, W0, Q, A);
against
void audioif_biquad_cp_configure(audioif_biquad_cp_coefficients_t *coefficients,
int mode, double W0, double Q, double A);
On the double-precision build mp_float_t is double, so the value reaching the kernel is the same one CPython computes. On the single-precision build it is rounded to float first and then promoted, so the kernel sees a different number — and Q15 coefficient rounding turns a last-bit difference into a different coefficient. That is consistent with biquad_component_probe failing at byte 137, very early.
audioif_dynamics_ms_to_coef(float ms, float sample_rate) takes float, so both builds truncate identically there; the dynamics failures therefore need their own explanation rather than this one, and the three of them failing together suggests a shared upstream input rather than the kernel boundary.
Not confirmed. The above explains the biquad case and does not explain dynamics, so at least one more mechanism is in play.
Reproduction
# from a MicroPython checkout at v1.29.0, with audioif's parent as USER_C_MODULES
make -C ports/unix CFLAGS_EXTRA="-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT" ...
python tests/parity/verify_dsp.py --micropython <that build>
A local verify_dsp run cannot see this: the desktop MicroPython in cmods/bin is double-precision, so the six local gates pass while this cell fails. That blind spot is why v0.4.0 was released on top of a red workflow.
Relationship to the other cross-target work
Same family as #79 (fused multiply-add across the remaining shared DSP files) and #55 (per-architecture differences), and it should be sequenced with them rather than fixed alone. All three are "the same C, different float behaviour per target", and the answer for each is either a code change that removes the freedom or a recorded per-target baseline — decided with measurements, not per-issue.
One ordering note: whatever fixes this will move board digests, exactly as #79 would, so audiocomponents' cost table is downstream of both.
What is failing
clean-build.yml'sunix-usermod (float-precision)leg failsverify_dsp: six probes render differently on a single-precision MicroPython build than on the CPython target. Thedouble-precisionleg passes all 21 comparisons.That cell is deliberate:
-DMICROPY_FLOAT_IMPL=MICROPY_FLOAT_IMPL_FLOAT, and the workflow says why — it "mirrors single-precision MCU targets (ESP32-P4/RP2040), wheremp_float_tis 32-bit". So this is a real target, not a hypothetical one.It is not a regression — the gate was red from birth
Worth stating plainly, because the run history looks like something broke:
git merge-base --is-ancestor 95cd1c2 ef71b86confirms the pin move precedes the last green run, andverify_dspappears zero times inclean-build.ymlatef71b86. So the v1.29.0 pin move did not cause this.79ee874added a check that had never run before, and it found a pre-existing disagreement on its first attempt. Twelve consecutive failures since, all of them the same finding.Likely mechanism, stated as a hypothesis
The bindings compute in
mp_float_tand hand the result to kernel functions declareddouble.src/synthio/Biquad.c:against
On the double-precision build
mp_float_tisdouble, so the value reaching the kernel is the same one CPython computes. On the single-precision build it is rounded tofloatfirst and then promoted, so the kernel sees a different number — and Q15 coefficient rounding turns a last-bit difference into a different coefficient. That is consistent withbiquad_component_probefailing at byte 137, very early.audioif_dynamics_ms_to_coef(float ms, float sample_rate)takesfloat, so both builds truncate identically there; the dynamics failures therefore need their own explanation rather than this one, and the three of them failing together suggests a shared upstream input rather than the kernel boundary.Not confirmed. The above explains the biquad case and does not explain dynamics, so at least one more mechanism is in play.
Reproduction
A local
verify_dsprun cannot see this: the desktop MicroPython incmods/binis double-precision, so the six local gates pass while this cell fails. That blind spot is why v0.4.0 was released on top of a red workflow.Relationship to the other cross-target work
Same family as #79 (fused multiply-add across the remaining shared DSP files) and #55 (per-architecture differences), and it should be sequenced with them rather than fixed alone. All three are "the same C, different float behaviour per target", and the answer for each is either a code change that removes the freedom or a recorded per-target baseline — decided with measurements, not per-issue.
One ordering note: whatever fixes this will move board digests, exactly as #79 would, so
audiocomponents' cost table is downstream of both.