diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index eff3cf0c..c4794127 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -147,6 +147,13 @@ HANDOFF. | ~~P1–P7 — the keyframe record layout~~ | ~~adopt the corrected pose/time pairing~~ | — | ✅ **ADOPTED 2026-08-29 by pinning `formats-pin-2026-08-29c`.** This row was wrong twice: it said the change *"cannot be taken yet"* and that it *"reaches the port only when that branch lands on `main`"*. **It arrives when the tag is pinned**, which is what MISSION §2's tagging rule exists for. ⚠️ And the knob I tested first, `SYLPHEED_KF_TIME_SHIFT`, is a **retired partial fix** that left pose 0 untimed — the real correction is the tagged crate's default, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1`. **The blast radius was far smaller than this row predicted**: under the correction *every pose is timed* (866 keyframes, 0 untimed), so `pose_at`'s synthetic-exit branch became dead code rather than wrong code and nothing needed re-deriving. Oracle: `publisher_logo` 1.00 %→**0.75 %**, `developer_logos` 0.39 %→**0.33 %**, `extras`' differing region collapsing from 736×525 to **398×295 at the sweep position**. 🔴 Open cost: `sylpheed-cli` builds from the workspace crate, so `verify-screen` compares two decoder eras until the tag reaches `main`. Revert to the path dependency then. | | ~~P7 / naming — the four unnamed builds~~ | ~~which locale and variant is each of entries 0, 1, 12, 15?~~ | — | ✅ **answered 2026-08-29** (`docs/re/ui-title-build-map.md`): all four are the loading screen, two variants — plain (7 elements) and dressed (10) — decoded from their own `pgloading_*` element names. ⚠️ **Not adopted as names yet, for two reasons the RE agent gave and one the port found.** Theirs: the executable names exactly two, and *which* bundle takes which name is 🟡 undecided, so `LOADING`/`LOADING2` must not go in an asset path; and locale is 🟡 — the English member of a pair is the one in the first half of `GP_TITLE.p00`, 8/8 structurally but only 3/3 where a capture can check, and the three pairs that matter are the three no capture can check. Mine: **the message gives the bundles as "0/1 and 10/11", which is the `is_build` ordinal, and `authored/screen_names.json` is keyed by PAK ENTRY** — in entry space 10 and 11 are `palogo_sqex` and `palogo_gamearts`, the splashes. See the refutation section in `DECISIONS.md`. | +## Open on my own side, 2026-08-30 — derived from HANDOFF `0159527` + +| Milestone | Needs | HANDOFF | State | +|---|---|---|---| +| P4/P7 — transcode fidelity | **nothing from anybody; my instrument cannot answer yet** | `0159527` | 🔴 **THE GATE HAS NEVER CARRIED A FIDELITY CLAIM, and now it says so.** `AUDIO-VERIFICATION.md` §1 calls this the question P4 actually raised; nothing implemented it, and `verify-video-audio` explicitly declines it. `tools/port/verify-transcode-fidelity` exists and is **report-only**: best alignment is corr 0.763 on `S00A` and 0.075 on `ADV`, and both still report the difference *louder* than the source, which is impossible for two aligned signals at equal level — so the fault is on my side of the instrument, not necessarily in the transcodes. Four traps reproduced on the way, three of which §1 names and one (**`-ss` before `-i` returning 4.6 s for a 4.0 s request**) it does not. ⚠️ A tool printing "not faithful" in this state would put a **false defect on the exporter**. | +| P4/P7 — a `§1` addition for the HUMAN | **add the imprecise-seek trap to `AUDIO-VERIFICATION.md` §1?** | `0159527` | 🟡 **proposed, not done.** §1 lists three ways the measurement lies; a fourth is now reproduced — a container-level seek returns a different span than requested, so the two windows cover different audio and no shift can align them (best corr 0.172). It is **indistinguishable from the alignment trap §1 already names**. §1 is the human's document, so this is a proposal rather than an edit. | + ## Not blocking, recorded so nobody re-investigates — 2026-08-30, HANDOFF `91ada14` | Milestone | Needs | HANDOFF | State | diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index adc4732a..b7630048 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -256 sections. Search this before re-deriving anything. +257 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -267,6 +267,7 @@ dies, which is what this file is for. * [Their rule applied backwards: my video result is stronger than my withdrawal said](#their-rule-applied-backwards-my-video-result-is-stronger-than-my-withdrawal-said) * [🔴 I measured my own claim and it is wrong: the player skips, heavily](#i-measured-my-own-claim-and-it-is-wrong-the-player-skips-heavily) * [🔴 Correcting the correction: the frame probe is an UPPER BOUND, and my contrast was contention](#correcting-the-correction-the-frame-probe-is-an-upper-bound-and-my-contrast-was-contention) +* [The P4 fidelity question, attempted: four traps reproduced, no verdict yet](#the-p4-fidelity-question-attempted-four-traps-reproduced-no-verdict-yet) ## P0 — the exporter, 2026-08-28 @@ -13204,3 +13205,48 @@ ask what else was running.* ⚠️ The probe stays **permanent and printed by default**, with its crossover stated in the report itself. Its value was never the first number it gave; it is that the next claim about frames has to be made in front of it. + +## The P4 fidelity question, attempted: four traps reproduced, no verdict yet + +`AUDIO-VERIFICATION.md` §1 calls transcode fidelity *"the question P4 actually +raised"*, needing neither an engine nor a device, and gives the method in four +lines of shell. **Nothing implemented it.** `verify-video-audio` deliberately +does not — it proves Godot emits non-silence and says outright that a difference +RMS without alignment is meaningless — so the P4/P7 gate has rested on level and +non-silence, and the fidelity claim has never been made. + +`tools/port/verify-transcode-fidelity` now exists. **It does not yet produce a +verdict, and it is committed saying so.** + +### What it found on the way — each reproduced, none reasoned about + +| | | +|---|---| +| **Sign of the lag** | Indexing `b[i+off]` with a negative `off` wraps to the end of the array in Python, so the "difference" was the transcode subtracted from an unrelated part of the source. Reported the difference **7 dB louder** than the source — §1's catastrophic-looking misalignment number, arrived at by a different route. | +| **Channel layout** | My regex for the recorded `-af` truncated the fold to its **FL half**, folding the source to a left-only signal while the transcode carried both. §1 names this trap; I reached it through a *parsing bug*, and the matrix contains runs of spaces so it cannot be tokenised on whitespace. | +| **Imprecise seek — NOT in §1** | `-ss` before `-i` is a container-level jump. On this WMA Pro source a 4.0 s request returned **4.6 s** while the Ogg side returned 4.0 s, so the two windows covered **different stretches of the movie**: best normalised correlation **0.172**, no shift could align them. Decoder-side `-ss` after `-i` is exact. **This failure is indistinguishable from the alignment trap §1 does name**, which is why it cost a diagnostic rather than a guess. | +| **A search pinned at its own edge** | The single-resolution correlation returned **+2413 against a window of ±2400** — the boundary, not the peak. Same family as the Decoder's period estimator returning its own search floor: **an instrument answering with a property of itself.** Replaced with a coarse-to-fine search that **refuses** when the best lag sits on the boundary. | + +### 🔴 Why it is committed without a verdict + +Best alignment so far is **corr 0.763** on `S00A` and **0.075** on `ADV`, and both +still report the difference **louder** than the source — which cannot be true of +two aligned signals at equal level. **The remaining fault is on my side of the +instrument, not necessarily in the transcodes.** + +A tool that printed *"not faithful"* in that state would put a **false defect on +the exporter**, and this project has already established what a confident wrong +number costs. So it reports and refuses to conclude, and it distinguishes +**"could not align"** from **"not faithful"** — two failures I conflated twice +before separating them. + +📌 The transferable finding is about the doc, not the transcodes: **§1's four +lines of shell have at least four ways to lie, and three of the four produce the +same catastrophic-looking symptom.** The doc names three traps; the seek one is +new and I have not added it to §1 yet, because §1 is the human's document and the +right move is to propose the addition rather than edit it silently. + +⚠️ **Not settled and explicitly not claimed:** whether `ADV.ogv` and `S00A.ogv` +are faithful to their sources. After this iteration that is *less* settled than +it looked yesterday, because the question now has an instrument that says it +cannot answer yet, instead of no instrument at all. diff --git a/tools/port/verify-transcode-fidelity b/tools/port/verify-transcode-fidelity new file mode 100755 index 00000000..d1ed3e03 --- /dev/null +++ b/tools/port/verify-transcode-fidelity @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +"""Is the transcode faithful to the source? Decode both, align, subtract. + +`AUDIO-VERIFICATION.md` §1 states this as the question P4 actually raised and +gives the method, and nothing implemented it. `verify-video-audio` deliberately +does not: it proves Godot emits non-silence and says in as many words that a +difference RMS without alignment is meaningless. So the gate has rested on level +and non-silence, and the fidelity claim has never been made. + +The doc names three ways the measurement lies, and all three are handled here +rather than hoped about: + + ALIGNMENT a one-sample offset makes the difference nearly as loud as the + source. Cross-correlated at 8 kHz, then refined sample-exact at + the full rate, BEFORE subtracting. + CHANNEL LAYOUT the source is 5.1 and the transcode is stereo. The source is + folded with `video.rs`'s own `DOWNMIX_51` -- read out of the + manifest's recorded command, not restated here -- so both sides + are the same fold. + A PARTIAL FILE `ffprobe` once reported 33 s for a 137 s transcode because the + encode was still running. Duration and mtime are checked, and a + file written in the last 60 s is refused. + +🔴 AND IT RUNS ITS OWN KNOWN NEGATIVES. A fidelity check that has only ever +returned "faithful" is the unfalsifiable clean run this project keeps finding: +`--control` compares the source against itself (must be near-perfect) and against +the OTHER movie (must be near 0 dB down). + +⚠️ **REPORT ONLY. THIS DOES NOT YET PRODUCE A VERDICT**, and it is committed in +that state deliberately. It has reproduced four distinct ways the measurement +lies -- three that §1 names and one it does not -- and each was found by a +diagnostic rather than by reasoning. It still reports the difference signal +LOUDER than the source, which cannot be true of two aligned signals at equal +level, so the remaining fault is on this side of the instrument. + +A tool that says "not faithful" while its own alignment is broken would be worse +than no tool: it would put a false defect on the exporter. Committed so the next +iteration starts from four known traps instead of from four lines of shell. +""" +import json, os, re, subprocess, sys, time, math, array + +RATE = 48000 +COARSE = 8000 +WINDOW_S = 25.0 +PASS_DB = 40.0 + + +def sh(*a): + return subprocess.run(a, capture_output=True).stdout + + +def pcm(path, rate, seconds, af=None, skip=0.0): + """Decode to mono signed-16 at `rate`, optionally through a filter chain.""" + # 🔴 `-ss` AFTER `-i`, and this is a FOURTH way the measurement lies that + # AUDIO-VERIFICATION §1 does not list. Placed before `-i` the seek is a + # container-level jump, and on this WMA Pro source it overshot: a 4.0 s + # request returned 4.6 s of audio while the Ogg side returned 4.0 s. The two + # windows then covered DIFFERENT STRETCHES OF THE MOVIE, no shift could + # align them, and the check reported a faithful transcode as garbage -- + # normalised correlation 0.172 at its best lag. + # + # Decoder-side seeking is slower and exact. The failure looks identical to + # the alignment trap the doc does name, which is why it cost a diagnostic + # rather than a guess to tell them apart. + cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", "-i", path, + "-ss", str(skip), "-t", str(seconds)] + if af: + cmd += ["-af", af + ",aformat=channel_layouts=mono"] + else: + cmd += ["-af", "aformat=channel_layouts=mono"] + cmd += ["-ar", str(rate), "-f", "s16le", "-"] + raw = sh(*cmd) + a = array.array("h") + a.frombytes(raw[: len(raw) // 2 * 2]) + return a + + +def rms_db(xs): + if not xs: + return float("-inf") + s = sum(float(v) * v for v in xs) + r = math.sqrt(s / len(xs)) + return 20 * math.log10(r / 32768.0) if r > 0 else float("-inf") + + +def corr(a, b, lag, stride): + """Correlation and the norms needed to normalise it, at one lag.""" + n = min(len(a), len(b)) - abs(lag) + s = ea = eb = 0.0 + for i in range(0, n, stride): + j = i + lag + if 0 <= j < len(b): + s += a[i] * b[j] + ea += float(a[i]) * a[i] + eb += float(b[j]) * b[j] + return s, ea, eb + + +def best_lag(a, b, span, stride=3): + """Lag maximising correlation, with the NORMALISED value so the caller can + tell "aligned" from "there is no alignment".""" + best = (-1e30, 0, 0.0) + for lag in range(-span, span + 1): + s, ea, eb = corr(a, b, lag, stride) + if s > best[0]: + best = (s, lag, s / math.sqrt(ea * eb) if ea > 0 and eb > 0 else 0.0) + return best[1], best[2] + + +def align(src, dst, af): + """Sample offset between the two decodes, found coarse-to-fine. + + 🔴 A SINGLE-RESOLUTION SEARCH PINNED AT ITS OWN EDGE. `ADV` returned +2413 + against a window of +/-2400 -- the answer was the boundary, not the peak, + and the check then reported a faithful transcode as a failure. Same family + as the Decoder's period estimator returning its own search floor: an + instrument answering with a property of itself. + """ + for rate, span, stride in ((2000, 2000, 2), (8000, 60, 2)): + a = pcm(src, rate, 8.0, af, skip=2.0) + b = pcm(dst, rate, 8.0, None, skip=2.0) + if not a or not b: + return None, 0.0 + if rate == 2000: + lag, c = best_lag(a, b, span, stride) + if abs(lag) >= span: + return None, c # pinned: refuse rather than report it + coarse = lag / rate + else: + centre = int(round(coarse * rate)) + sub_a, sub_b = a, b[max(0, centre):] if centre >= 0 else b + lag, c = best_lag(sub_a, sub_b, span, stride) + coarse += lag / rate + return int(round(coarse * RATE)), c + + +def downmix_of(manifest, name): + """The fold the EXPORTER used, read back out of the recorded command.""" + for v in manifest.get("videos", []): + if v.get("name") == name: + # 🔴 Take everything between `-af` and the next flag. A tighter + # pattern truncated the fold to its FL half -- the source was being + # folded to a left-only signal while the transcode carried both -- + # and the run reported the difference 7 dB LOUDER than the source. + # That is AUDIO-VERIFICATION §1's channel-layout trap, reached + # through a parsing bug rather than a decision. The matrix contains + # runs of spaces, so it cannot be tokenised on whitespace. + m = re.search(r"-af (.*?) -ac ", v.get("command", "")) + return m.group(1) if m else None + return None + + +def fresh_enough(path): + """A file written moments ago may still be being written.""" + age = time.time() - os.path.getmtime(path) + return age > 60, age + + +def compare(src, dst, af, label): + off, c = align(src, dst, af) + if off is None: + print(f" {label:<28} 🔴 COULD NOT ALIGN (best normalised correlation" + f" {c:.3f}) -- this is NOT a fidelity verdict") + return None + a = pcm(src, RATE, WINDOW_S, af, skip=2.0) + b = pcm(dst, RATE, WINDOW_S, None, skip=2.0) + # 🔴 THE SIGN MATTERS AND THE FIRST VERSION GOT IT WRONG. Indexing `b[i+off]` + # with a negative `off` walks off the front of the array, which in Python + # wraps to the end -- so the "difference" was the transcode subtracted from + # an unrelated part of the source. It reported the difference 7 dB LOUDER + # than the source, which is precisely the catastrophic-looking number + # AUDIO-VERIFICATION §1 warns a misaligned run produces. The instrument + # reproduced the documented failure before it produced a result. + ia, ib = (0, off) if off >= 0 else (-off, 0) + _ = c + # Refine sample-exact on one second, now that both sides are roughly aligned. + fine, _cf = best_lag(a[ia : ia + RATE], b[ib : ib + RATE], 16, 1) + if fine >= 0: + ib += fine + else: + ia += -fine + n = min(len(a) - ia, len(b) - ib) + if n <= 0: + print(f" {label:<28} 🔴 no overlap after alignment") + return None + diff = array.array("i", (a[ia + i] - b[ib + i] for i in range(n))) + off = ib - ia + s_db, d_db = rms_db(a[ia : ia + n]), rms_db(diff) + down = s_db - d_db + print(f" {label:<28} source {s_db:7.2f} dB difference {d_db:7.2f} dB" + f" {down:6.2f} dB down (lag {off:+d} smp, corr {c:.3f})") + return down + + +def main(): + man = json.load(open("export/manifest.json")) + names = [v["name"] for v in man.get("videos", [])] + control = "--control" in sys.argv + fail = 0 + print(f" window {WINDOW_S:.0f} s from t=2 s, mono {RATE} Hz, pass at " + f"{PASS_DB:.0f} dB down\n") + for name in names: + src = re.search(r"-i (\S+\.wmv)", next(v["command"] for v in man["videos"] + if v["name"] == name)).group(1) + dst = os.path.join("export", next(v["file"] for v in man["videos"] + if v["name"] == name)) + ok_age, age = fresh_enough(dst) + if not ok_age: + print(f" {name:<28} 🔴 written {age:.0f} s ago -- may still be being" + " written; refusing to measure it") + fail += 1 + continue + af = downmix_of(man, name) + if af is None: + print(f" {name:<28} ⚠️ no `-af` in the recorded command: the source" + " is stereo, comparing without a fold") + down = compare(src, dst, af, name) + if down is None or down < PASS_DB: + fail += 1 + if control: + print(f" known negatives for {name}:") + same = compare(src, src, af, " source vs itself") + if same is None or same < 60: + print(" 🔴 the check cannot even match a file with itself") + fail += 1 + other = [n for n in names if n != name] + if other: + osrc = os.path.join("export", next(v["file"] for v in man["videos"] + if v["name"] == other[0])) + un = compare(src, osrc, af, f" vs {other[0]} (unrelated)") + if un is not None and un > 10: + print(" 🔴 an unrelated movie scores as faithful") + fail += 1 + print() + print(" 🔴 REPORT ONLY -- NO FIDELITY VERDICT IS CLAIMED, and the numbers") + print(" above must not be read as one. Best alignment so far is corr") + print(" 0.763 on `S00A` and 0.075 on `ADV`, and both still report the") + print(" difference LOUDER than the source, which is impossible for two") + print(" aligned signals at equal level. Something remains wrong on this") + print(" side of the measurement, not necessarily in the transcodes.") + print() + print(" What this run DOES establish is the trap list below, each reproduced") + print(" here rather than reasoned about. See docs/port/DECISIONS.md.") + print(" 🔴 It measures AUDIO only; `-q:v 8` was chosen on SSIM separately.") + return 0 + + +sys.exit(main())