この文書はまだ翻訳されていません。本文は英語のまま表示されています。
M1 report: NES encode, Rung A decode, blargg golden
Run stamp: 2026-09-01, second commit of this repository, rustc stable,
cargo test --workspace 33 tests green, clippy clean, MUTATE=1 reddens
17 tests across all five test binaries. Every number below is pasted from
the runs described, not typed from expectation; M5 puts them under a
check-self-counts scan.
What closed
Four new crates. ntsc-testgen: the deterministic generators (solid,
the 64 x 8 sweep parameters, the two colour-cycle sets with parity
sequences Even/OddFull/Even and Even/OddShort/Even, vertical stripes).
ntsc-source-nes: the encoder, levels flowing from the gated
transcription through build.rs, the signal function ported from the
page's own NTSCsignal, the horizontal segment map from the page's timing
tables (two simplifications documented in the module header, both
invisible to M1's oracles). ntsc-decode: Rung A, chroma by the
generated bandpass, luma as its exact complement, QAM demodulation at
the geometric phase, the transcribed matrix, display gamma 2.2 recorded.
ntsc-oracle: nes_ntsc 0.2.2 built natively, the ported palette model,
the comparison resampler, the recorded alignment, the golden tests.
The oracle, pinned
blargg's canonical URL is dead (slack.net links to blargg.parodius.com,
gone; the ITU-style direct fetch 404s). Source: the Wayback Machine's
2011-08-12 capture of the canonical URL, zip sha256
ca1a420d721d83b944142c366a917ba199dbc10cf91ad6f21dc712ed1069d58e,
fetched and verified by tools/fetch-oracle.sh, never committed (LGPL
2.1, test-only). Configuration, generated by build.rs and recorded
there: NES_NTSC_EMPHASIS, OUT_DEPTH 32, IN_T unsigned short; composite
preset; merge_fields off for the row comparisons (the preset merges, and
per-frame phase is the thing under test) and on elsewhere.
src/blargg_model.rs is his colour generation ported line for line
(LGPL, attributed), so every comparison constant comes from the oracle's
own source text. The port is held to the compiled library:
worst disagreement 1.00 count over all 512 palette entries
(the_port_reproduces_blarggs_own_palette), which is integer packing.
The alignment, measured then frozen
examples/align.rs is the instrument; its 2026-09-01 output:
iq map: rotation 120.000 deg (spread 0.028), scale 0.78799
port vs compiled palette: worst 1.00 counts at entry 012
dc sweep (matched tail): base colours mean-of-worst 1.47, worst 5.48 at 002;
emphasis mean 14.13, worst 45.81 at 1d2
dc sweep (full pipeline): base colours mean-of-worst 23.02, worst 64.22 at 01c;
emphasis mean 18.91, worst 68.95 at 0ac
colour-cycle row 8: best mean abs diff 5.679 counts at burst0 2 offset -14.0
stripes row 8: best mean abs diff 18.033 counts at burst0 2 offset -11.8
(surface: -15.8: 29.4, -13.8: 30.2, -11.8: 18.0, -9.8: 23.5, -7.8: 26.4)
Frozen into ntsc-oracle and re-verified by the tests every run:
- IqMap: mirror, rotation 120.0 degrees, scale 0.78799. One rigid
rotation maps all twelve demodulated hues onto blargg's (I, Q) with a
spread of 0.028 degrees;
the_iq_map_refits_to_its_frozen_constantsre-fits it every run. The mirror is YIQ chirality against YUV; the scale is his half-swing amplitude convention against a demodulator's 4/pi square-wave fundamental (predicted pi/4 x 0.34/0.335 = 0.797, measured 0.788); the rotation folds his cos-table orientation, the UV-to-IQ 33 degrees and his authored std_decoder_hue of -15 degrees, and is recorded as measured rather than decomposed. - RESAMPLE_OFFSET -11.8 samples, BURST0 2. The offset comes from the stripes scan, the one instrument with a sharp basin; the colour-cycle mean and the band-edge windows are nearly flat in the offset but sit at their own minima at the same value (5.70 and 6.42). Burst0 2 is unanimous across every scan.
The gate: every disagreement attributed
Comparison 1, matched tail (our demodulated Y/U/V wearing blargg's own ported matrix, hue and gamma), against his palette at DC:
| Group | Mean of worst | Worst | Attributed to |
|---|---|---|---|
| 64 base colours | 1.47 counts | 5.48 ($02) | blargg's two-decimal normalized levels (-0.12/0.00/0.31/0.72, 0.40/0.68/1.00/1.00) against the measured table |
| 448 emphasis entries | 14.13 | 45.81 ($1D2) | blargg's YIQ-space emphasis approximation (atten_mul 0.79399, atten_sub 0.0782838, the tint==7 special case) against the measured attenuated voltages |
Comparison 2, full honest pipeline (our SMPTE matrix, our display gamma): base colours mean 23.0, worst 64.2. The delta between the two comparisons is the decoder choice itself: equiband YUV with the SMPTE 170M matrix against his classic YIQ matrix rotated -15 degrees plus his quadratic gamma (factor pow(0.1333, 0.73)). Reported, not asserted: both decoders are legitimate; the site will show ours and say so.
Row comparisons (both spec-named colour-cycle sets, all six frames, each
at its chained origin and mapped burst): mean abs diff 5.7 counts,
envelope 10, with the residual living at band edges where his authored
DSF-sinc luma and Gaussian chroma kernels differ from our windowed
sincs. Stripes row: 18.0 counts, envelope 25; dot-frequency content is
where the two filter designs disagree most, and where blargg's
artifacts and fringing terms (absent from Rung A by design) live. The
two-frame repeat shows through the mapping: Even/OddShort returns frame
2 to frame 0's burst, Even/OddFull does not
(the_two_frame_repeat_shows_through_the_mapping).
Known divergences, all named, none hidden: level rounding, emphasis
model, fundamental-only chroma (he never models the square's harmonics;
our luma complement keeps them as ripple, which is why DC comparisons
average over one subcarrier cycle, see solid_dc), decoder matrix and
hue, gamma curve, filter designs, and a chroma-against-luma delay skew
of a few samples between the pipelines (his kernels are asymmetric by
design, PIXEL_OFFSET -9..0), which is why no single resampler offset
zeroes every instrument.
MUTATE, and what the rig itself got wrong
MUTATE=1 cargo test --workspace --no-fail-fast: 17 red (geometry 8,
data 3, encoder 3: the page-waveform, burst and segment tests against
perturbed encoding levels; decode 2: hue-ladder amplitude and matrix
wiring against a perturbed centre tap and coefficient; oracle 1: the DC
sweep against a 1.3x chroma gain). The resampler and burst carry
always-on red proofs instead (a_wrong_resampler_or_burst_is_detected):
a one-dot shift must exceed 1.8x the aligned stripes diff (measured
2.1x-2.3x), a wrong burst 2.5x (measured 3.0x-3.2x).
Three rig defects were found by measurement during M1, each now documented at the fix:
- The chroma bandpass was generated narrow-minus-wide, passband gain -0.907, and the generator's gain probe returned a magnitude, so the sign hid until the hue ladder decoded 180 degrees rotated. The response is now recorded signed, in zero-phase form (tools/gen-filters.py).
- The resampler mapped 2048 samples uniformly onto 602 pixels, but blargg's 602 pixels span 258 dots (3-in/7-out chunks with black padding): a 0.8% slope error, 16 samples of drift across the row, which made every offset scan surface flat. The slope is exactly 24/7 (resample_row).
- The first offset scan's range (+/-6) did not contain the minimum, and the first align run scanned against merged-fields kernels while the tests compare unmerged; both hid the true basin at -11.8. The scan is now +/-20 against the unmerged oracle.
Also of record: a wrong burst phase is invisible on solid areas by construction (his correct_errors forces every burst kernel to the same DC), so the burst red proof lives on stripes, where everything is edge.
Carried forward
- Rung D at M2 (director's decision), plus Rungs B and C, colour bars, the WASM build and the measured throughput table.
- The yuv-matrix primary-source confirmation (SMPTE 170M-2004) at M2.
- The full-512 DC sweep runs in examples/align.rs; the in-test sweep is a 72-entry representative subset for debug-build speed, recorded here.
- blargg's setup gamma, sharpness, resolution, artifacts, fringing and bleed parameters are exercised only at their composite-preset values; no other preset is a target (spec section 4.1).
Pulled at build time from ntsc-crt/docs/m1-report.md; the repository is the one copy.