The same argument for the geometry that had two units on one name (#755)
* The same argument for the geometry that had two units on one name
#752 put the unit in the name where a pressure, a temperature or a
humidity could be read two ways. Measured over the imported API, four
geometric quantities were in the same state, and one of them was two
different dimensions.
diameter was metres in twenty public parameters and millimetres in one,
cnossos_rail.wheel_transfer, where CNOSSOS-EU Table G-3b tabulates 920,
840 and 680. angles was degrees in ten and radians in four while angle
was radians in eleven, so the plural and the singular of one word
disagreed. period was a lattice pitch in metres in the slit and
metadiffuser panels and a repetition time in seconds in the synchronous
average. gradient was a road slope in per cent in CNOSSOS-EU and a
vertical sound-speed gradient in s^-1 in refraction and sound speed.
They become diameter_m and diameter_mm, angle_rad, angles_rad and
angles_deg, period_m and period_s, road_slope_percent and gradient_per_s.
periods, a count of repetitions sitting beside a period that is a length,
becomes repetitions; the two places where it really means evaluation
periods keep the word and are in the guard's exemptions with the reason.
GaussianPulse.width is a half-width in seconds and the perfectly matched
layer of the same module measures its own width in cells. It says
half_width_s now.
No value changes: the conformance report is the same 995 checks with the
same numbers. The guard grows a second tuple, matched against the whole
name, and says in its own comment that the seventy-five compound names
are a second pass rather than an exemption: WestonRegimeBoundaries has
critical_angle in radians one screen above a function that takes it in
degrees. Against main the guard reports 71; here none.
* Re-render the three clips the renamed scenes drew
Refuse at the door what used to arrive as a NaN (#642)
Every entry point that takes a signal resolves it through one line, and that
line let three things past. A None became an array of one NaN, so
octave_filter(None, 8000) answered with eight bands of NaN and refused
nothing. A None inside a list became a NaN sample, and one poisoned sample in
a few thousand came back as a whole ruined spectrum with nothing said, which
a caller could not tell from a broken instrument. Anything else numpy could
not convert escaped in numpy's own words, or as a TypeError where the entry
points document a ValueError.
None of this is a new policy. The tree already refused a non-finite sample
nearly everywhere: two signal entry points and the calibration one say so in
their own words, and the filter family and broadcast were the outliers. The
resolver now says it once, for all of them, and the message names the
parameter that carried the value: 'ref_signal' where that is the name, not a
generic 'x'.
The suite decided the part that was in doubt. Refusing a non-finite sample
broke twelve tests and not one of them was a caller with a real NaN-bearing
signal: ten already refused such a sample and only disagreed about the
wording, one carried a written instruction to rewrite it if the library ever
validated the input itself, and the last asserted that a NaN propagates
through the filter bank, which is the defect recorded as expected behaviour.
It does propagate, which is exactly why it is not something to hand back.
Eleven call sites wrapped the resolver in np.asarray, which converted the
None to a NaN before either guard could see it; without dropping those the
naming would have been cosmetic at eleven of the entry points. Nine per-entry
guards became unreachable once the resolver refused first, and are gone
rather than left as dead lines.
One behaviour changes beyond the refusals: a masked array whose masked slot
hides a NaN is refused now. The mask never survived the conversion, so that
NaN was already reaching the computation; the refusal surfaces it.
Fit each weighting at its own sample rate instead of oversampling around it (#646)
* Fit each weighting at its own sample rate instead of oversampling around it
The frequency weightings were realised by bilinear-transforming the printed
prototype at 3 to 8 times the sample rate and resampling around the result. The
transform's frequency compression is what forced the oversampling, and the
resampling then cost more than the compression it was bought to avoid: its
anti-alias filter sits with its cutoff on the input Nyquist, the signal crosses
it twice, and that is a fixed 1.65 dB at 20 kHz for a 44.1 kHz input however
high the factor goes.
Fitting the prototype at the sample rate removes both. Worst error over the
corpus goes from 18.4 dB to 0.060 dB, and A-weighting at 44.1 kHz from 2.24 dB
out at 20 kHz to 0.002. It also runs 25 to 50 times faster with an eighth of
the memory, because there is no longer an oversampled intermediate: the whole
path is one cascade of second-order sections and nothing else.
The deliverable is the design routine, not coefficients. Every parameter is a
frequency, each factor is Routh-Hurwitz stable by construction so no step can
leave the unit disc, and the residual is in decibels so the Jacobian stays
bounded. The result is bit-identical across processes and BLAS thread counts,
and a test re-derives it rather than pinning an array.
Three published tolerances that used to fail now pass, one of which nothing in
the tree documented: AU at 32 kHz was 13.3 dB outside IEC 61012's only mask and
verified to no class at all. A and C now earn IEC 61672-1 class 1 at every rate
the library supports, where they fell to class 2 at 32 kHz and to no class at
16 kHz.
Also here, because they arrived with it:
The conformance harness emits docs/conformance.json, and the report, the site
page, the Spanish twin, the quoted counts and the PR comment all derive from
it. They used to derive from the rendered markdown: three separate regexes
parsed the report's English headline as a wire format, each telling the reader
to go and update the other two. The published "369 standards" counted citation
strings rather than documents, so one book cited at two equations counted
twice; the artefact separates 114 normative designations from 84 further
sources. Outcome.passed was a numpy.bool_ on nine checks, which no consumer
noticed because the only use was an if.
IEC 651:1979 Table V brings the Type 0 laboratory class to the weightings, the
way IEC 61260:1995 brings class 0 to the band filters. It is worth having for a
reason beyond completeness: class 1 is +2.5/-16 dB at 16 kHz and +3/-inf at
20 kHz, so it cannot see an error at the top of the band, which is exactly
where the old design was wrong. Type 0 is +2/-3 at both. A and C pass it using
seven per cent of the budget.
* Take the seven review findings, and refuse the one that would open a hole
Six are what they look like. The pair matcher closed over its loop variable,
which is a hazard even where the closure is consumed before the next iteration,
so the distances are materialised and the lambda goes; four raises blocks
carried their fixture construction inside, so a failure there would have
satisfied a test about something else; and the determinism check on the
markdown renderer compared one expression against itself, which does call
twice and does test what it claims, but reads as a tautology.
The seventh is refused. The band guard is written `not low < high` and the
suggestion is `low >= high`. Those differ on NaN, which the negated comparison
refuses and the inequality admits, and a band of NaN reaching the fit is how a
silent wrong answer starts. It is spelled out with isfinite instead, which is
what the guard meant all along.
A flag is written by name everywhere, and the clip gate learns which changes cannot move a frame (#760)
#759 closed the flags a caller could reach. This closes the rest, so ruff's
FBT family is selected outright instead of documented as absent: 202
signatures take the star, and the 296 calls that passed a bare True name the
parameter.
Every one of the 156 call sites the change forced was named rather than left
positional. Two of them no static gate sees: _cached_weighting_sos and
_cached_weighting_filter are lru_cache'd, so mypy reads the call through
_lru_cache_wrapper and checks nothing, and only the suite found them.
The one place a keyword-only parameter is not a rewrite: Callable[[...], R]
cannot say that a parameter is keyword-only. ColumnsBuilder in
_report/_insulation_fiche.py and a new LeftCellBuilder in
_report/ansi_s12_2.py become protocols, which say what the alias could not.
FBT003 came down to ax.grid(True, alpha=0.3) in 295 of its 296 sites.
matplotlib declares that parameter visible, so the sites say visible=True and
a test pins the name, because a matplotlib release that moved it would
otherwise surface as a wall of unrelated red. The one that was left is
np.bool_(True) in a test, a cast rather than a flag; it is np.True_ now.
The clips are not re-rendered, and none of them needed to be.
animation_fingerprint already normalises away what cannot move a frame, the
docstrings, the import order, the TYPE_CHECKING blocks, with the reason on
each. Where the keyword-only star sits is the same kind of thing: it decides
how a caller may write the call, not what the function computes. With that
normalisation the 43 fingerprints are identical either side of the signature
sweep, measured, not assumed. The manifest is re-stamped because the
fingerprint itself changed, and the stamp records the same clips it always
described.
scripts/check_public_flags.py goes: ruff now says the same thing on every
definition, and a guard that cannot fire is noise.
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
The filter bank returns a result, not a tuple whose length depends on a keyword (#810)
* The filter bank returns a result, not a tuple whose length depends on a keyword
octave_filter() and OctaveFilterBank.filter() returned two items normally and
three when sigbands=True asked for the band waveforms. That is why the pair
carried twelve @overload declarations to be typable at all, and why
`_, _, bands = bank.filter(...)` was a line a reader had to count commas in.
Both return an OctaveFilterResult now, with levels, frequencies and bands, the
way every other computation in this library already did. require_levels() and
require_bands() return the value or raise naming the argument that was missing,
which beats the None a caller would otherwise meet several lines later. Like
every other result it draws itself, and the band spectrum is in the guide.
* Every snippet that mentions the filter bank runs against the new return
Four the snippet gate caught. The rewriter that converted the guides appended
the accessor to the end of the line rather than the end of the call, so a
trailing comment swallowed it and the variable kept the whole result;
README_PYPI.md is a separate file neither rewriter walked; the snippet that
demonstrates the missing-bands error raised it for real, and now catches it and
prints the message; and the header snippet of llms.txt is written by hand
inside generate_llms.py, where no documentation sweep would ever find it.
* The nominal labels carry the decimal comma, and the stale clip is redrawn
The renderer sets its own tick labels for a nominal spectrum, and
set_xticklabels installs fixed strings that localize_axes never reaches, so a
Spanish nominal plot showed 31.5 where the rest of the corpus writes 31,5. That
was the one way round the decimal-point gate, which reads the generated SVGs.
The bands docstring conflated the type that is preserved with the units it is
in: an uncalibrated Signal comes back as a Signal carrying None, a calibrated
one as a Signal carrying 1.0, and a dBFS bank hands back plain arrays.
One test indexed the result rather than unpacking it, which the AST sweep did
not see because it only looked at tuple targets; a second sweep for subscripts
found no others anywhere in the tree. anim_modulation_transfer is redrawn
because the code that draws it changed.
* The per-channel legend entry is a named constant, shared by the two renderers that use it
* The stateful block-processing snippets read bands through the accessor too
A Spanish figure writes its decimal comma on every panel it draws, and two guards keep it that way (#777)
Every helper that writes text takes a language and defaults to English, so a call that forgets to pass it on raises nothing and puts an English decimal point into a Spanish figure. The plotting modules, the figure generators and the fiches now hand the caller's language to every helper that takes one, and localize_axes is called once per axes a plot builds rather than once per figure: a twin axis, a colorbar and the z of a 3-D panel each carry a formatter of their own, and each was shipping in English beside a panel already in Spanish. What decides whether an axis is localised is its formatter and not its scale, so a log axis of distances still writing its own numbers is reached and a pinned frequency axis is left to format_frequency_axis. A zoom inset and a contour colorbar, which the save-time pass of the figure generators cannot reach, are localised where they are built. The band column of the room criterion sheet and of the whole sound-power fiche family is written in the sheet's language too.
scripts/check_language_forwarding.py fails on any call to a helper that takes a language, made from anywhere a language is in scope, that does not pass it on. It reads module and class bodies as well as functions, follows a helper through a local alias and a functools.partial, names the shapes it cannot reach, keys an exemption by its line so one approved call cannot cover another, and reports a method name only some of whose namesakes take the language, which silently switched the untyped-receiver check off.
scripts/check_figure_decimal_point.py reads the committed Spanish figures and fails on a tick label that is a number with a point in it, whichever pass was meant to write the comma. It found six figures, regenerated here. Both run from the Makefile and in CI.
Say which error each test is waiting for (#610)
* Say which error each test is waiting for
`PT` is selected. The 241 `pytest.raises(ValueError)` calls that named no
message now name one, and the fragment they name is the part that does not
move: the parameter the guard is about, not a bound, not the list of allowed
values, not wording that belongs to numpy.
Reading the guards turned up two `match=` that already existed and pinned
nothing, neither of them reported by the linter because both do pass a
`match=`: one waited on `frequencies`, a word three messages in its module
share, and one waited on `must be positive`, which every positivity guard in
its module shares.
One test could only pin numpy's shape-mismatch text because
`reception_plate_power` broadcast two of its arguments without checking them
first. It has its own guard now, through a new shared helper, so the caller is
told which argument was the wrong length.
Alongside it, 44 `parametrize` decorators take the shape the other 467 use,
and ten `assert a is not None and b is not None` became two assertions.
* Regenerate the reference page for the guard that now names its argument
* Refuse the one-element array the band axis would have stretched
A scalar means the same value in every band and a band-long array means one
value per band. Broadcasting alone also accepted a one-element array and
repeated it across every band, which is the shape a caller lands on after
computing one value where the bands needed several, and four copies of one
measurement read exactly like a measurement of four bands.
The changelog's counts were measured before earlier work moved them: the
suite holds 2075 `pytest.raises`, of which 1795 already named a message. The
39 that still name none are the classes that are their own assertion.
Read the quasi-peak value method, not an r.m.s. through its network (#647)
* Read the quasi-peak value method, not an r.m.s. through its network
ITU-R BS.468-4 clause 3 reserves dBqps for the quasi-peak value method,
and NOTE 1 says an r.m.s. meter reading the same network gives figures
that are not directly comparable. The network was already exposed and the
curve landed in #645; the detector itself was missing.
Clause 2 prints no time constant, so the three are solved for against the
eleven reference readings of Tables 2 and 3 rather than quoted. How well
eleven windows pin three constants is measured and stated with the
procedure that produced it, because the answer depends on the question:
with the other two frozen, charge holds from 1.014 to 1.894 ms, discharge
from 233 to 367 ms and the reading device from 96 to 200 ms, so the
reading device is what the tables pin down least.
Four ballistics that all pass all eleven windows still read 2.61 dB apart
on a click and 0.03 dB apart on a steady tone. That is a property of the
standard, so it is documented as a lower bound on any reading here.
Four misprints found on the way are recorded in docs/ERRATA.md, none of
which changes what this library computes. IEC 60268-1:1985 Figure A1
prints the last shunt capacitor as 41.47 nF where BS.468-4 Figure 1a
prints 31.47 nF, and the annex contradicts itself two pages earlier:
against Table AI the printed value breaks that table's own tolerance
column at seven frequencies.
MicrophoneNoise(weighting="CCIR") stays metadata and weighted_thd stays
an r.m.s. through the network; both are now documented as such.
* Key the quasi-peak caches by the ballistics, and remeasure what they identify
The published statement about how far each of the three time constants can
move before one of the eleven windows fails could not be measured. The chain
caches the clause 2.6 calibration factor and the steady 5 kHz reference on the
sample rate alone, and both run through the detector, so moving the constants
left the caches answering with a reference computed for a different
instrument. The symptom was that the same value passed or failed depending on
what order the probes ran in.
It was correct while nothing could change the constants, and wrong for a
module whose documentation publishes a claim about exactly that. The
ballistics now travel through the chain as an argument and into both cache
keys, and verify_quasi_peak_dynamics takes them too, so the claim is
re-derivable rather than a number to be believed.
Remeasured on that footing, all eleven windows hold from 1.02 to 1.90 ms of
charge (1.88), 230 to 370 ms of discharge (1.61) and 96 to 200 ms of reading
device (2.09), each edge a value that conforms rather than one rounded past
the boundary. The earlier figures were taken through the contaminated path and
were wrong in both directions; one published edge did not even conform.
Two things follow that were not said before. The ranges are marginal and not a
box: the fastest charge with the shortest indicator misses by 1.11 dB. And the
spread between conforming instruments is larger than reported, because the
first measurement read the needle before it had finished rising. Sampling the
region and keeping the 243 sets that meet all eleven windows gives 4.61 dB on
a transient, more than the widest window the tables draw, against 0.00 dB on a
steady tone, which is the calibration doing what it promises.
Four tests hold the range from both sides, so it can be neither widened nor
quietly narrowed, and one asks in the order that used to give a wrong answer.
* Build the fixture before the block that expects it to raise
Ten pytest.raises blocks across nine files built their argument inside the
block. If the construction itself raised, the test would pass for the wrong
reason and the call it exists to check would never run. Sonar flags this as
S5778 but only on new code, so eight of the ten would never have been
reported: the two it did report were in a file that happens to be new.
Also removes a redefinition in _plot/signals.py, where _TIME_LABEL and
_LAG_LABEL are each assigned twice at module level. Both pairs carry the same
string so nothing is wrong today, but editing the first has no effect, which
is the kind of thing that is discovered the hard way. A sweep of the rest of
src finds no other case.
* Say on every page that this documentation is ahead of the release
The site documents the tree, which is ahead of what is on PyPI: pages here
describe modules, entry points and names that 3.3.0 does not have, so a reader
who installs the published version and follows a page gets an ImportError and
no reason for it. A standing banner says so.
Starlight's banner is per page, set in frontmatter, and its config-level form
takes one string with no way to vary it by locale. Half this site is Spanish,
so the component picks the text from the route's language. A page that sets
its own banner still wins: that one is about the page, this one is about the
site.
* Say the verdict in words, show how much of each tolerance is spent
The conformance tables marked every row with a tick or a cross, which can only
say two things. There are four verdicts: a check can also be by design, as the
Chebyshev and Bessel architectures are against a mask they cannot meet, or not
applicable. Those two had nowhere to go and were carried in a display string
where nothing could count them. A word says all four, reads the same in a
plain-text diff and needs no colour.
The Markdown report also never showed the number that matters most for a
metrology library: how much of a clause's published tolerance the deviation
actually consumes. The artefact has carried it since the report became
structured data, the site page has shown it, and the report itself did not.
It is there now on 469 of the 566 rows, with the dash on the other 97
explained rather than left to be guessed: those clauses state no two-sided
tolerance, so there is no budget to spend.
The same column appears twice in the PR comment and was explained under the
second table, which the reader reaches after meeting the column under the
first. The note moves to the head of the section.
Two responsive defects, both measured in a browser at a 390 px viewport rather
than reasoned about:
- The domain filter on the conformance page is sized by its longest option,
"2D FDTD wave simulation (Attenborough & Van Renterghem 2021, Ch. 4)", which
asks for 552 px inside a 374 px row. A flex item's automatic minimum size is
its content, so it refused to shrink and took the document to 582 px. With
min-width: 0 it is 390.
- Inline KaTeX had no scroll box, only display math did, and KaTeX never breaks
a formula. One acceleration formula on the errata page is 1011 px wide and
carried that document to 1067 px.
Wide tables were not part of it: they already scroll inside their own
containers, and only looked guilty because getBoundingClientRect reports an
element's full width whether or not its container clips it.
* Draw the verdicts and the count, instead of spelling them in emoji
The report says Pass, Fail, By design and n/a in words now, which fixed the
meaning an emoji could never carry, but left the tables with nothing to scan.
This adds the pictures back as SVG the repository owns and generates: four
verdict marks and a summary banner under .github/badges, redrawn by
`make conformance` from docs/conformance.json so the count on the banner is
always the tree's own. Nothing points at them yet; the documents come next.
A mark is one file with no dark twin, and that is a measured decision rather
than a shortcut. Each fill clears 3:1 on GitHub light, GitHub dark, GitHub dim
and the documentation site's ground alike, so the same file is right in every
theme, the way a shields.io badge has been for a decade. Pairing them instead
would need a <picture> element on every row, which costs 163 kB on the 566-row
table against 9.6 kB for reference-style images of a single-file mark. The
banner is the opposite case, once per page, where the pair costs 278
characters and buys a card that sits properly on its page, so it ships as a
light and a dark variant on the _dark naming the README and ThemeImage.astro
already pair on.
Shape carries the meaning alongside colour: a filled disc, a hexagon, a
rounded square and a hollow ring stay apart from each other in greyscale and
to a reader who cannot separate the hues. Every file states its verdict in a
<title> as well, because an image of a tick is not a verdict to anyone who
cannot see it.
The blue moved off Primer's #0969da, which measures 2.89:1 on GitHub Dim, onto
#1f6feb at 3.24:1. The whole contrast table is measured by the tests rather
than asserted in a comment.
Glyphs are outlines of the committed fonts, as the brand assets already are,
so the shared conversion moved into scripts/svg_text.py and generate_brand.py
now calls it; the brand output is byte for byte what it was. The banner is
drawn ten units to the pixel so every coordinate is a whole number, which
leaves no float to be formatted two ways on two machines. Two runs, three hash
seeds, a different timezone and a Turkish locale all write identical bytes.
Also fixes an assertion left pointing at a tick that no longer renders.
* Say why inline math has no scroll box, having measured what one costs
The commit before this gave inline KaTeX the scroll box display math already
had, on the reasoning that a formula wider than the column should scroll rather
than push the page sideways. The reasoning is right and the fix is not
available: overflow does nothing on an inline box, so the box has to become
inline-block, and that changes how every formula in the corpus takes part in
line layout.
Measured over the 991 inline formulas on the four most formula-heavy pages,
990 of them move. Their x and their width are untouched, but each box grows
from 22 px to 24 px tall, which lifts the line it sits on, and down a long page
the shift accumulates to 3645 px. Every variant gives the same 24 px, because
the growth is inline-block itself and not the overflow: vertical-align baseline,
a negative vertical-align, and dropping the overflow entirely all do. Only
overflow: clip keeps the document height, and it clips the formula instead of
scrolling it, which is worse than the defect.
Two formulas on one page do not justify taxing the typography of the 990 that
are already the right width, so the rule is gone and the comment says why, with
the numbers, so the next reader does not re-derive it. A formula wide enough to
need a scroll box gets fixed where it is written, as display math.
* Draw the verdict beside the word, wherever the report is read
The indicators the last commit generated were sitting in .github/badges
with nothing pointing at them. Now the three places that publish a
conformance verdict cite them, and each does it the way its own renderer
allows.
docs/CONFORMANCE.md gets a mark in front of every Status cell and in the
filter-class showcase, as reference-style images with one definition per
mark at the foot of the document. Measured on the real 566-row table, the
three ways of writing a per-row indicator cost +10.6 kB by reference,
+58.0 kB inline, and +162.8 kB for a per-theme <picture> on every row; the
mark is one file that clears 3:1 on every ground, so the last option buys
nothing. The head of the document gets the summary banner as the <picture>
pair the README already uses, and a legend that lists the marks the page
actually draws. Every mark keeps the word beside it: the word is the
verdict, the picture illustrates it, and the word is what survives a raw
view, a plain-text diff and an image that never arrives.
The pull-request comment gets the banner pinned to the head SHA, so it
reports the branch under review rather than main, and loses the last of the
emoji: the tick, the cross and the warning sign on its own test-status line
are now marks and words.
The site inlines the same four marks as an SVG sprite read off the
committed files at build time, which costs no request and lets the Spanish
page say "Por diseño" where the English one says "By design". It also fixes
a live defect there: the status cell asked whether the verdict was "pass"
and drew everything else as a red cross announced as "Fail", so a by-design
row was published as a failure, and the "failing rows only" filter listed it
as one. Both now know all four verdicts.
The README carries the banner under the paragraph that makes the
conformance claim, linked to the report.
Two measurements changed a decision. Starlight's own green is 1.53:1 on the
light card, so the site bar uses the marks' measured fills instead. And the
green and the red are within 1.02:1 of each other, so a failing bar reads as
a full one in greyscale; both bars now leave a sliver of track between the
segments.
* Lead every conformance row with its verdict, in words
On the documentation site the verdict was the eighth and last cell of a table
wider than the content well, drawn as a mark with the word hidden from sight,
and the box around that table never scrolled. A table whose columns cannot fit
is laid out at its min-content width anyway, but Chromium reports its border
box as the containing block's width, so the scroll container saw scrollWidth
equal to clientWidth, offered no scrollbar and clipped everything past its
right edge. Measured at 1280 px, the status cell ended 106 px outside the box
and scrollLeft stayed pinned at 0; at 360 px it ended 863 px outside. A pixel
census of untouched screenshots found exactly one verdict mark painted in the
viewport at twelve of the fourteen width and language combinations, and that
one was the mark in the section heading. Below 1440 px no data row carried a
verdict a sighted reader could reach, while the Markdown mirror printed the
word in every row.
min-width: min-content gives the container something to scroll, the verdict
moves to the first column, and its word is drawn rather than hidden. The mark
needed display: inline-block as well, because Starlight's content reset makes
every svg a block and had been placing the mark on a line above the word it
belongs to in every cell that draws one; and the column after the verdict
carries the gutter, because a nowrap verdict runs past its own cell where the
table is squeezed and padding on the overflowing cell cannot hold a gap open.
The mark stays aria-hidden, so a screen reader still hears the verdict exactly
once.
Measured afterwards at 360, 390, 768, 1024, 1280, 1366 and 1440 px in both
languages: the status cell is inside the container at all fourteen, the
container reports real overflow of 28 to 867 px and scrolls to the last
column, the page body never scrolls sideways, the verdict and its mark share a
line with at least 13 px to the citation beside them, and the marks painted in
the viewport go from one to one per visible row.
The palette's "clears 3:1 on every ground" was measured against five grounds,
four of which were not grounds. GitHub stripes even table rows, so half of the
report's rows sit on a stripe rather than a canvas, and this site overrides
Starlight's page colour, so the #17181c that three comments named is not a
ground anywhere here. Against the nine real grounds, eight clear 3:1 and
GitHub's dimmed table stripe does not: the four fills measure 2.75 to 2.82
there. They stay as they are, because no colour can do better. Clearing 3:1 on
#2d333b needs a relative luminance of at least 0.1972 while a white glyph
reading at 4.5:1 on the fill allows at most 0.1833, so the two windows do not
overlap; the glyph wins, because it is the part of the mark that is read and
every surface prints the verdict in words in the same cell. The test now
measures all nine grounds, pins the one exception, and proves the arithmetic
behind it.
What a picture pair follows was stated backwards in one direction and
correctly in the other, so both were measured across all four crossings of
desktop theme and GitHub theme. On github.com the pair follows the GitHub
theme: GitHub upgrades it into a themed-picture element that rewrites the
source's media to "not all" or to a pair of prefers-color-scheme queries.
Rendered without that JavaScript, as the /markdown API output under GitHub's
own stylesheet is, the media stays as authored and the desktop decides, so two
of the four crossings serve the wrong twin. The docstring now says both, and
says why the choice is safe either way: the banner paints its own card, whose
ink reads at 14.84:1 light and 15.91:1 dark, and which separates from a
mismatched page at 17.3:1 and 17.8:1.
The committed report drops its summary banner. That file is regenerated from
the tree it sits in and gated byte for byte, so it claims to be that tree's
report, while an image can only be cited at a fixed ref; on any branch that
adds or removes a check, a picture of main's count sat directly above a
sentence carrying the branch's. The sentence carries every number the picture
did. In its place the report gains the ten checks closest to their published
limit, which the pull-request comment and the site page have both ranked since
they were written and the published report never showed.
Two smaller corrections. The pull-request comment no longer reports a missing
test artefact as "0 tests, 0 failures (all green)". And the report's legend and
the renderer's docstring no longer say the mark sits beside its word in
Markdown: measured under GitHub's own table stylesheet, the Status column
settles at about 53.5 px of content box against the 54 px a mark plus its word
needs, so the word wraps under the mark in 563 of 566 cells at 1012 px and 493
at 1400, and a non-breaking space does not change it.
* Make the measurements in the comments the ones this tree produces
Five sentences quoted figures the tree does not give, and the branch
contradicted itself on two of them.
The scroll comment said the container reports 106 px of overflow at 1280 px.
It reports 116. The 106 is what it reported before the gutter rule sixty lines
below, which the same comment says grows the table by 10 px, so the file
disagreed with itself by exactly the amount it documented.
The byte figures were taken against a baseline that had neither the banner nor
the legend, and came out 0.6 kB high: +10.6, +58.0 and +162.8 where the
committed report gives +10.0, +57.3 and +162.2. Two other places in the same
branch already quoted the right ones.
The wrap counts came from one harness and do not survive another, which is
fair enough because the count depends on the width the harness gives the
container: 566 of 566 in one and 563 in the other. The wrapping is the
finding, so the sentence states that and stops pretending to a tally.
The line-box note said the cell is no wider. True in English and false in
Spanish, where the longer word costs 4.8 px. Both twins are peers, so the
sentence now says which is which.
And the report and the PR comment do not share two functions; they build their
markup separately and meet at asset_url, which is the one that matters,
because a path that is wrong there is wrong on both.
The same argument for the geometry that had two units on one name (#755)
* The same argument for the geometry that had two units on one name
#752 put the unit in the name where a pressure, a temperature or a
humidity could be read two ways. Measured over the imported API, four
geometric quantities were in the same state, and one of them was two
different dimensions.
diameter was metres in twenty public parameters and millimetres in one,
cnossos_rail.wheel_transfer, where CNOSSOS-EU Table G-3b tabulates 920,
840 and 680. angles was degrees in ten and radians in four while angle
was radians in eleven, so the plural and the singular of one word
disagreed. period was a lattice pitch in metres in the slit and
metadiffuser panels and a repetition time in seconds in the synchronous
average. gradient was a road slope in per cent in CNOSSOS-EU and a
vertical sound-speed gradient in s^-1 in refraction and sound speed.
They become diameter_m and diameter_mm, angle_rad, angles_rad and
angles_deg, period_m and period_s, road_slope_percent and gradient_per_s.
periods, a count of repetitions sitting beside a period that is a length,
becomes repetitions; the two places where it really means evaluation
periods keep the word and are in the guard's exemptions with the reason.
GaussianPulse.width is a half-width in seconds and the perfectly matched
layer of the same module measures its own width in cells. It says
half_width_s now.
No value changes: the conformance report is the same 995 checks with the
same numbers. The guard grows a second tuple, matched against the whole
name, and says in its own comment that the seventy-five compound names
are a second pass rather than an exemption: WestonRegimeBoundaries has
critical_angle in radians one screen above a function that takes it in
degrees. Against main the guard reports 71; here none.
* Re-render the three clips the renamed scenes drew
Results keep their own copy of the arrays they were given (#939)
np.asarray hands back the very array it is given when that array is already
float64, and so does every validation helper built on it, so a result built
from such an array kept the caller's array, or a view of it: changing that
array afterwards changed a result already computed, and nothing raised. Every
array a function or a method of the library keeps from its arguments is now a
read-only copy of its own, through the private helper read_only_copy, and the
objects that keep arrays to hand out later (SilencerChain, AnpDatabase,
FDTD2D, ElasticFDTD2D, window_metrics) do the same; a Signal keeps a writeable
copy of its samples. The values do not move. scripts/check_array_aliasing.py
(make array-aliasing and a CI job of its own) follows each parameter of every
function of the package to the places an array is kept and fails on one kept
without a copy of its own.
Hold the reading and measurement chain to values other people published (#578)
* Version real measurement audio with published values as oracle data
Five licence-verified third-party sources under tests/data/audio/, each
redistributable and each carrying a value its authors published: the NTi
XL2 calibration take and five pass-by excerpts of the Antwerp campaign
(CC BY 4.0), a Sound Devices 702T BWF from the wavinfo project (MIT), a
genuine RF64 from EBU libbw64 (Apache-2.0), the omnidirectional channel
of a St. Nicholas Chapel impulse response (CC BY 4.0), and the 2015
five-minute LAeq series of two Dublin City Council monitors (CC BY 4.0).
The folder README states per file the source, the licence, the required
attribution and the published values, and marks what was changed (cuts,
repackaging) versus what is byte-identical.
* Hold the reading and measurement chain to values other people published
One oracle module per source: the XL2 calibration take must land on its
published 113.7 dB through the bext-declared full scale; the five
pass-by excerpts must reproduce the campaign's LA,eq, LA,max, L10
within 0.3 dB and its median loudness N50 within 5 percent; the Sound
Devices bext and the EBU RF64 are transcription oracles for the chunk
parser, asserted by exact equality; the chapel impulse response must
agree with the deposit's ISO 3382-1 parameter table within the
standard's own just noticeable differences; and the Dublin monitoring
year must average to the annual report's night-time values before
feeding lden, ldn and composite_rating_level. Each module states its
source, the transcribed published row and why each tolerance is what
it is; L90 and the pass-by level rise stay unasserted because they pin
a windowing convention rather than a measurement.
* Close the calibration chain in the io guide on a real meter's file
The guide derived the sensitivity from a synthesized calibrator take;
the new worked example reads the repository's committed NTi XL2
calibration recording, takes the full scale the instrument itself
declared in its bext chunk, and lands on the 113.7 dB the dataset
publishes, with no hand-typed sensitivity anywhere. Both language
mirrors carry the example and the llms dumps are regenerated.
* State the audio oracle folder's real size in the data index
* Record the committed audio oracles in the changelog
* Date the retrievals in the audio oracle README
* Let the calibration example stand alone when copied
The worked example opens with its own imports, which promises the block is
self-contained, and then leans on names two earlier blocks brought in. On
the page it runs; pasted alone it raises NameError on the first real line.
The two missing imports join the block's own, in all three mirrors, and the
llms copies follow. Verified by running the block exactly as a reader would
paste it: Leq = 113.7 dB.