From f4351b0b8c9e2a4859e21894ea049b471dd1d96e Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Mon, 31 Aug 2026 04:56:58 +0000 Subject: [PATCH] port: run the peer's shift-vs-blur discriminator -- the menu is not misregistered They proposed the test and said they had not run it. It needs the port's render beside the capture, so it was mine: a shift makes the signed residual track the gradient (and the fitted slope is the shift in pixels), a blur makes it track the laplacian. New tool, tools/port/edge-residual-kind. Controls first, and they are not optional: a known +1 px shift reads back as +0.938 px (r +0.789), a known blur as r -0.896 on the laplacian, neither leaking into the other's channel. Result: -0.010 px, -0.009 px, laplacian r +0.103. Flat. Any whole-frame translation is under a hundredth of a pixel, and the weak blur term has the opposite sign to the blur control. Misregistration is excluded. Reach: whole-frame fit. One misplaced element is a small share of 38 752 edge pixels and would not move these numbers -- this excludes a GLOBAL translation, not a local one. A misplaced soft element remains untouched. Exit codes 0 or 2, no 1 -- it classifies, it does not judge. The control-failure branch verified by raising the thresholds: report suppressed, exit 2. Also: GP_DIALOG 2/3 restored to authored/flow.json. I declined to re-add it on a relay; they have now taken the ja capture (HANDOFF at 5a7f34d) -- 1.82 % of pixels in four bands, EASY/NORMAL/HARD not among them because JP keeps Latin script. My objection stands as an objection: adjacency never implied a language pair, and 26 of 65 adjacent pairs differ in button count. The claim now rests on a capture instead of on that inference. --- authored/flow.json | 19 +++++ docs/port/DECISIONS.md | 73 +++++++++++++++++- tools/port/edge-residual-kind | 138 ++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 1 deletion(-) create mode 100755 tools/port/edge-residual-kind diff --git a/authored/flow.json b/authored/flow.json index 344793d3..462545f6 100644 --- a/authored/flow.json +++ b/authored/flow.json @@ -327,6 +327,25 @@ "zero rivals disc-wide, plus the oracle capture. The pairing was decoration on a", "conclusion that stands without it, which is exactly why it travelled unchecked.", "", + "✅ RESTORED 2026-08-31, ON A MEASUREMENT RATHER THAN A RELAY. The Decoder took", + "the `ja` capture of DIFFICULTY that was missing and 2/3 ARE English/Japanese:", + "EN vs JP differ in 1.82 % of pixels in FOUR BANDS AND NOWHERE ELSE -- the", + "heading (DIFFICULTY -> the JP heading), the ring by 2 px, the BACK label, and", + "the footer. EASY/NORMAL/HARD are NOT in the differing set: the Japanese release", + "leaves the three difficulty names in Latin script, which is why the disc figure", + "is only 2.77 % of bytes against 1.82 % of pixels.", + "", + "📌 MY OBJECTION WAS NOT WRONG AND IS NOT WITHDRAWN. It was that IDENTICAL", + "ELEMENT SETS DO NOT IMPLY A LANGUAGE PAIR -- 26 of 65 adjacent pairs differ in", + "button count, so adjacency proves nothing. That argument still holds; what has", + "changed is that the conclusion now rests on a direct locale capture instead of", + "on that inference. A bad argument for a true claim is still a bad argument, and", + "the claim was correctly out of this file until somebody went and looked.", + "", + "⚠️ REACH, THEIRS: one JP boot, one screen, does not generalise. GP_TITLE 4/7 is", + "known to differ by MORE than text -- entry 7 carries nine sprites entry 4 lacks.", + "Nothing in the port keys off locale today; this is recorded, not consumed.", + "", "🔴 RECORD LAYOUT CORRECTED 2026-09-01, and I had copied the wrong one. I wrote", "\"(handler, id, name pointer)\"; it is {id, name_ptr, handler} -- the same three", "fields shifted one word, so every record was being credited with the PREVIOUS", diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 39d5013a..8803cd17 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -323 sections. Search this before re-deriving anything. +325 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) @@ -334,6 +334,8 @@ dies, which is what this file is for. * [A workflow defect of mine, on its fourth occurrence](#a-workflow-defect-of-mine-on-its-fourth-occurrence) * [The menu residual, decomposed — and half of 13.06 is tone](#the-menu-residual-decomposed--and-half-of-1306-is-tone) * [Refutation: the peer's tone/geometry positive control rests on a number of mine that cannot carry it](#refutation-the-peers-tonegeometry-positive-control-rests-on-a-number-of-mine-that-cannot-carry-it) +* [The menu's edge residual is **not** a misregistration — the Decoder's discriminator, run](#the-menus-edge-residual-is-not-a-misregistration--the-decoders-discriminator-run) +* [`GP_DIALOG` 2/3 restored to `authored/flow.json` — on a measurement this time](#gp_dialog-23-restored-to-authoredflowjson--on-a-measurement-this-time) ## P0 — the exporter, 2026-08-28 @@ -15727,3 +15729,72 @@ edge concentration is spatial. The menu's is 3.2×. concentration is the signature of sub-pixel misregistration, of antialiasing, or of a genuinely misplaced soft element, and this measurement does not separate those three. I am not guessing between them. + +## The menu's edge residual is **not** a misregistration — the Decoder's discriminator, run + +They proposed the test and said plainly they had not run it: *"the discriminator +would be whether the edge excess is signed — a misplaced element gives a residual +with a consistent direction along the edge, antialiasing does not. That is a real +experiment and I have not run it, so it is a route, not an answer."* + +📌 **It needed my render beside the capture, so it was mine to run.** Made concrete: +a shift by `(dx,dy)` makes the signed residual track the **gradient**, and the +fitted slope *is the shift in pixels*; a blur makes it track the **Laplacian**, +symmetric and directionless. New tool: `tools/port/edge-residual-kind`. + +### The controls come first, and they are not a flag + +| render vs a deliberately damaged copy of itself | d/dx | d/dy | laplacian | +|---|---|---|---| +| known **+1 px horizontal shift** | **r +0.789, slope +0.938 px** | −0.008 px | −0.306 | +| known **blur, no shift** | +0.003 px | +0.004 px | **r −0.896** | + +✅ **A 1-px shift reads back as +0.938 px.** The instrument recovers both what it +is for, and neither control leaks into the other's channel. + +### The result + +| `main_menu` render vs oracle capture | value | +|---|---| +| horizontal shift | r −0.027, **slope −0.010 px** | +| vertical shift | r −0.027, **slope −0.009 px** | +| blur / sharpness | r +0.103 | + +🔴 **Flat on all three. The menu is not globally misregistered** — any whole-frame +translation is under **a hundredth of a pixel**, against a control that reads a +true 1 px at 0.938. That excludes the most worrying of the three candidates, and +the one a renderer can silently acquire. The blur channel at +0.103 is weak *and +the opposite sign to the blur control*, so the capture is not a softened render +either. + +⚠️ **Reach, and it is the whole reach:** this is a **whole-frame** fit. A single +misplaced element is a small share of 38 752 edge pixels and would not move these +numbers. **This excludes a global translation, not a local one.** Of the peer's +three candidates it kills misregistration and weakens uniform antialiasing; a +misplaced soft element is untouched by it, and I am not claiming otherwise. + +📌 Exit codes are **0 or 2, and there is no 1** — the tool classifies, it does not +judge. If either control fails it prints nothing but the failure: verified by +raising the thresholds to 0.99, which suppresses the report and exits 2. A guard +nobody has watched fail is decoration. + +## `GP_DIALOG` 2/3 restored to `authored/flow.json` — on a measurement this time + +I withdrew *"an EN/JP pair"* as an unchecked relay and **declined to re-add it** +because nothing depended on it. The Decoder has now taken the `ja` capture that +was missing (`HANDOFF` at `5a7f34d`, dated today): EN and JP differ in **1.82 % of +pixels in four bands and nowhere else** — heading, the ring by 2 px, `BACK`, the +footer. `EASY`/`NORMAL`/`HARD` are *not* in the differing set; the Japanese release +leaves them in Latin script, which is why the disc figure is only **2.77 % of +bytes**. + +📌 **My objection was not wrong and is not withdrawn.** It was that *identical +element sets do not imply a language pair* — 26 of 65 adjacent `GP_DIALOG` pairs +differ in button count, so adjacency proves nothing. That still holds. What changed +is that the claim now rests on a direct locale capture instead of on that +inference. **A bad argument for a true claim is still a bad argument**, and the +claim was correctly out of the file until somebody went and looked. + +⚠️ Their reach, carried across: one JP boot, one screen, does not generalise — +`GP_TITLE` 4/7 differs by more than text. Nothing in the port keys off locale +today, so this is recorded, not consumed. diff --git a/tools/port/edge-residual-kind b/tools/port/edge-residual-kind new file mode 100755 index 00000000..9d15e0d6 --- /dev/null +++ b/tools/port/edge-residual-kind @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""What KIND of error is left at the edges after tone is accounted for? + + tools/port/edge-residual-kind [screen] # default main_menu + +`verify-capture`'s `diff` column thresholds at 25 % and so only sees GROSS +displacement. Fitting a per-level LUT removes everything a tone effect can +explain. What is left on the main menu is concentrated 3.2x on edge pixels +(DECISIONS.md, 2026-08-31) -- and three things produce that: a misregistration, +an antialiasing difference, or a genuinely misplaced element. + +THE DISCRIMINATOR IS THE SIGN, and it is the Decoder's, from their reply on +2026-08-31: a shift gives a residual with a CONSISTENT DIRECTION along the edge, +an antialiasing difference does not. Made concrete: + + * shifted by (dx,dy): residual ~ dx*d/dx + dy*d/dy -- and the fitted SLOPE + IS THE SHIFT IN PIXELS + * blurred/sharpened : residual ~ -k * laplacian -- symmetric, no direction + +EXIT CODES. 0 the report is trustworthy, 2 A CONTROL FAILED so the numbers below +it mean nothing. There is no 1: this tool classifies, it does not judge. A +correlation this tool reports is worthless without the two controls above it, +which is why they are not optional and not a flag. +""" +import math, os, subprocess, sys, tempfile + +CAPS = "docs/re/captures/title-builds" +SCREEN = sys.argv[1] if len(sys.argv) > 1 else "main_menu" +# The captures are a 1279x675 top-left crop of the 1280x720 guest surface, so the +# render is cropped to match and NOTHING IS SCALED -- resampling would manufacture +# exactly the edge signal this tool measures. See verify-capture, same reason. +W, H = 1279, 675 +EDGE = 12 # |grad| above which a pixel is an edge +PASS_SHIFT, PASS_BLUR = 0.70, -0.70 + + +def gray(png, out): + subprocess.run(["convert", png, "-colorspace", "Gray", "-depth", "8", + "gray:" + out], check=True) + return open(out, "rb").read() + + +def lutfit(a, b): + tot = [0] * 256; cnt = [0] * 256 + for i in range(len(a)): + tot[a[i]] += b[i]; cnt[a[i]] += 1 + return [(tot[v] // cnt[v]) if cnt[v] else v for v in range(256)] + + +def analyse(a, b): + lut = lutfit(a, b) + gx = []; gy = []; lp = []; rs = [] + for y in range(1, H - 1): + o = y * W + for x in range(1, W - 1): + i = o + x + ax = (a[i + 1] - a[i - 1]) * 0.5 + ay = (a[i + W] - a[i - W]) * 0.5 + if abs(ax) + abs(ay) < EDGE: + continue + gx.append(ax); gy.append(ay) + lp.append(float(a[i + 1] + a[i - 1] + a[i + W] + a[i - W] - 4 * a[i])) + rs.append(float(lut[a[i]] - b[i])) + n = len(rs) + if n < 1000: + print(f" 🔴 only {n} edge pixels -- nothing to classify"); sys.exit(2) + mr = sum(rs) / n + + def fit(u): + mu = sum(u) / n + suu = sum((v - mu) ** 2 for v in u) + srr = sum((v - mr) ** 2 for v in rs) + sur = sum((u[k] - mu) * (rs[k] - mr) for k in range(n)) + return (0.0, 0.0) if suu <= 0 or srr <= 0 else (sur / suu, sur / math.sqrt(suu * srr)) + return n, fit(gx), fit(gy), fit(lp) + + +def row(label, res): + n, (sx, rx), (sy, ry), (sl, rl) = res + print(f" {label} (n={n})") + print(f" horizontal shift : r={rx:+.3f} slope={sx:+.3f} px") + print(f" vertical shift : r={ry:+.3f} slope={sy:+.3f} px") + print(f" blur / sharpness : r={rl:+.3f} coef ={sl:+.3f}") + return rx, ry, rl + + +def shifted(a, dx): + out = bytearray(a) + for y in range(H): + for x in range(W): + out[y * W + x] = a[y * W + min(W - 1, max(0, x - dx))] + return bytes(out) + + +def blurred(a): + out = bytearray(a) + for y in range(1, H - 1): + o = y * W + for x in range(1, W - 1): + i = o + x + out[i] = (a[i] * 4 + a[i + 1] + a[i - 1] + a[i + W] + a[i - W]) // 8 + return bytes(out) + + +tmp = tempfile.mkdtemp() +cap_png = f"{CAPS}/live-{SCREEN.replace('_', '-')}.png" +if not os.path.exists(cap_png): + print(f" 🔴 no capture: {cap_png}"); sys.exit(2) +render = os.environ.get("RENDER") or f"{tmp}/render.png" +if not os.path.exists(render): + print(f" 🔴 no render at {render} -- set RENDER="); sys.exit(2) +subprocess.run(["convert", render, "-crop", f"{W}x{H}+0+0", "+repage", + f"{tmp}/crop.png"], check=True) +r = gray(f"{tmp}/crop.png", f"{tmp}/r.gray") +c = gray(cap_png, f"{tmp}/c.gray") + +print("CONTROLS -- the render against a deliberately damaged copy of itself.") +print("A correlation below is meaningless unless these two recover what was done.\n") +ra = analyse(r, shifted(r, 1)) +rxa, _, rla = row("known +1 px HORIZONTAL shift", ra) +rb = analyse(r, blurred(r)) +_, _, rlb = row("known BLUR, no shift", rb) +bad = [] +if rxa < PASS_SHIFT: bad.append(f"shift control r={rxa:+.3f} < {PASS_SHIFT}") +if rlb > PASS_BLUR: bad.append(f"blur control r={rlb:+.3f} > {PASS_BLUR}") +if bad: + print("\n 🔴 CONTROL FAILED: " + "; ".join(bad)) + print(" The discriminator cannot see what it is for. Report suppressed.") + sys.exit(2) +print(f"\n ✅ controls pass -- a 1 px shift reads as {ra[1][0]:+.3f} px\n") +print(f"THE REAL PAIR -- {SCREEN}\n") +rx, ry, rl = row(f"{SCREEN} render vs oracle capture", analyse(r, c)) +print() +if max(abs(rx), abs(ry)) < 0.15 and abs(rl) < 0.3: + print(" => NEITHER a global shift NOR a uniform blur.") + print(" ⚠️ REACH: this is a WHOLE-FRAME fit. One misplaced element is a small") + print(" share of the edge pixels and would not move these numbers. This") + print(" excludes a global translation; it does not exclude a local one.")