port: localise the menu residual -- nothing is displaced, and the split I expected is absent

tools/port/edge-residual-map tiles the frame at 64 px and runs the shift
discriminator inside each tile, which is what edge-residual-kind's reach statement
said it could not do. Division agreed with the Decoder: the map is mine, the
element inventory is theirs.

The first control failed and that is the useful part. A known +2 px displacement
localises perfectly but reads back +0.839 -- the slope is a linearisation and
saturates. So there are two controls now: +1 px asserts localisation AND magnitude
(+0.949), +2 px asserts localisation and sign only. A hot tile's slope is a floor
on the displacement, never a ceiling.

Result: every dx and dy in the top ten tiles is under 0.1 px. Nothing in the hot
region has moved, locally or globally, so the misplaced-soft-element candidate now
has no support on this screen. Hot tiles cluster at x 384-704, y 64-256 plus an
outlier at 640,576; the tool names nothing and the list has gone to the Decoder.

And a null: I added a flat-pixel column expecting two families, edge-only against
hot-everywhere. The hot tiles run continuously 1.24..3.37 across a median of 1.84.
What nearly manufactured the split was carrying over the frame-wide POOLED ratio
of 3.16 as the baseline -- pooling is dominated by the tiles with the most edge
pixels. Same quantity, wrong population; caught because the baseline was computed
before the claim rather than after.
This commit is contained in:
Sylpheed port agent
2026-08-31 05:01:00 +00:00
parent 8101a93229
commit 978f4f9943
2 changed files with 247 additions and 1 deletions

View File

@@ -9,7 +9,7 @@ dies, which is what this file is for.
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
325 sections. Search this before re-deriving anything.
326 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)
@@ -336,6 +336,7 @@ dies, which is what this file is for.
* [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)
* [The residual map: no local displacement either, and the split I expected is not there](#the-residual-map-no-local-displacement-either-and-the-split-i-expected-is-not-there)
<!-- /INDEX -->
## P0 — the exporter, 2026-08-28
@@ -15798,3 +15799,68 @@ 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.
## The residual map: no local displacement either, and the split I expected is not there
The Decoder proposed the division and it is the right one: *"the map is yours and
the element inventory is mine."* `tools/port/edge-residual-map` tiles the frame at
64 px and runs the same shift discriminator **inside each tile**, which is the
thing `edge-residual-kind` said it structurally could not do.
### 🔴 The first control failed, and that is the useful part
A known **+2 px** displacement localises perfectly — the displaced region is the
top four tiles — but reads back **+0.839 px**. The slope is a linearisation,
`residual ≈ dx · gradient`, valid only while `dx` is small against the width of an
edge. **The estimator saturates.** Reporting that as a distance would have
understated a real displacement by more than half.
So there are now **two** controls, each asserting only what it can:
| control | localises | magnitude |
|---|---|---|
| **+1 px** (linear regime) | ✅ top 4 tiles | ✅ **+0.949** |
| **+2 px** (saturating) | ✅ top 4 tiles | ⚠️ +0.839 — **a lower bound** |
📌 **A hot tile's slope is a floor on the displacement, never a ceiling.**
### The result
| tile | edge | flat | e/f | dx | dy |
|---|---|---|---|---|---|
| 512,128 | **18.80** | 10.20 | 1.84 | **0.001** | **0.000** |
| 576,64 | 15.11 | 9.09 | 1.66 | 0.012 | +0.029 |
| 384,256 | 14.12 | 4.40 | 3.21 | +0.003 | +0.003 |
| 512,192 | 11.91 | 3.53 | 3.37 | 0.038 | 0.090 |
| 448,128 | 11.78 | 9.53 | 1.24 | +0.084 | +0.036 |
median tile 5.13 · hottest 3.66× median · **every dx and dy under 0.1 px**
🔴 **No tile in the top ten is displaced.** Against a control that reads a true
1 px at +0.949 and finds a 2 px one even while understating it, **nothing in the
hot region has moved.** The peer's third candidate — a misplaced soft element —
now has no support anywhere on this screen, globally or locally.
The hot tiles cluster: **x 384704, y 64256**, a wide upper-centre band, plus one
outlier at **640,576**. Those are coordinates. **This tool names nothing** — what
sits under them is the Decoder's, and I have sent them the list.
### ⚠️ And a structure I expected, went looking for, and did not find
I added the `flat` column expecting two families: tiles hot *only* at edges (an
edge-rendering difference) against tiles hot *everywhere* (a local tone the global
LUT mis-serves). Reading the first ten rows, that split looked obvious.
It is not there. The hot tiles run **continuously from 1.24 to 3.37** across a
median of **1.84**.
📌 **What nearly manufactured it:** I had the frame-wide *pooled* edge/flat ratio,
**3.16**, from the earlier work, and against 3.16 the rows at 1.21.8 look like a
distinct low family. But the pooled figure is dominated by the tiles carrying the
most edge pixels; **the per-tile median is 1.84**, and against *that* the same rows
are unremarkable. **Same quantity, wrong population** — the week's pattern again,
caught this time only because I computed the baseline before writing the claim
rather than after.
**So the hot region is not one anomalous element with a character of its own**, and
that is a finding, not an absence of one.

180
tools/port/edge-residual-map Executable file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""WHERE does the edge residual sit, and is that region locally shifted?
RENDER=<png> tools/port/edge-residual-map [screen]
`edge-residual-kind` fits the whole frame and excludes a GLOBAL translation. Its
own reach statement says the thing it cannot do: one misplaced element is a small
share of 38 752 edge pixels and would not move a whole-frame number. This tiles
the frame and runs the same discriminator INSIDE each tile, so a single displaced
element shows up as one hot tile with a local slope -- which is invisible to the
global fit by construction, not by accident.
Division of labour, agreed with the Decoder 2026-08-31: the residual map is the
port's (it needs the render beside the capture), the element inventory is theirs
(it needs the disc). This tool produces the map and NAMES NOTHING.
THE CONTROL IS A KNOWN LOCAL SHIFT. A map that cannot localise a displacement it
was told about cannot be trusted to have found one it was not. Exit 0 the report
is trustworthy, 2 the control failed and the report is suppressed. No 1.
"""
import math, os, subprocess, sys, tempfile
CAPS = "docs/re/captures/title-builds"
SCREEN = sys.argv[1] if len(sys.argv) > 1 else "main_menu"
W, H = 1279, 675 # top-left crop of the guest surface; never scaled
TILE = 64
EDGE = 12
MIN_EDGE_PX = 150 # below this a tile's slope is noise
# The controls displace this region and the map must find it there.
CTRL_BOX = (448, 320, 640, 448) # x0, y0, x1, y1
# TWO controls, because ONE OF THEM FAILED AND TAUGHT ME THE LIMIT. The slope is
# a linearisation, residual ~ dx * gradient, which holds only while dx is small
# against the width of an edge. A +2 px displacement localises perfectly but reads
# back +0.8..+1.25, so the estimator SATURATES. Control A checks magnitude in the
# regime where magnitude means something; control B checks that a displacement too
# large to measure is still FOUND. Reporting a saturating slope as a distance
# would understate a real displacement by more than half.
CTRL_A_DX = 1 # linear regime: localisation AND magnitude
CTRL_B_DX = 2 # saturating: localisation and SIGN only
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 tiles(a, b):
"""Per-tile mean |residual| on edge pixels, and the local shift slope."""
lut = lutfit(a, b) # ONE global LUT: tone is global, displacement is not
out = {}
for ty in range(0, H - 1, TILE):
for tx in range(0, W - 1, TILE):
gx = []; gy = []; rs = []; flat = []
for y in range(max(1, ty), min(H - 1, ty + TILE)):
o = y * W
for x in range(max(1, tx), min(W - 1, tx + TILE)):
i = o + x
ax = (a[i + 1] - a[i - 1]) * 0.5
ay = (a[i + W] - a[i - W]) * 0.5
d = float(lut[a[i]] - b[i])
if abs(ax) + abs(ay) < EDGE:
flat.append(abs(d)); continue
gx.append(ax); gy.append(ay); rs.append(d)
n = len(rs)
if n < MIN_EDGE_PX:
continue
mabs = sum(abs(v) for v in rs) / n
mflat = (sum(flat) / len(flat)) if flat else 0.0
mr = sum(rs) / n
def slope(u):
mu = sum(u) / n
suu = sum((v - mu) ** 2 for v in u)
if suu <= 0:
return 0.0
return sum((u[k] - mu) * (rs[k] - mr) for k in range(n)) / suu
out[(tx, ty)] = (mabs, slope(gx), slope(gy), n, mflat)
return out
def top(t, k=8):
return sorted(t.items(), key=lambda kv: -kv[1][0])[:k]
def show(t, label, k=8):
print(f" {label}")
print(f" {'tile':>12} {'edge':>7} {'flat':>7} {'e/f':>6} "
f"{'dx':>7} {'dy':>7} {'edge px':>8}")
for (tx, ty), (m, sx, sy, n, mf) in top(t, k):
ef = (m / mf) if mf > 0.01 else float('inf')
print(f" {tx:4d},{ty:4d} {m:7.2f} {mf:7.2f} {ef:6.2f} "
f"{sx:+7.3f} {sy:+7.3f} {n:8d}")
def shift_box(a, box, dx):
x0, y0, x1, y1 = box
out = bytearray(a)
for y in range(y0, y1):
for x in range(x0, x1):
out[y * W + x] = a[y * W + min(W - 1, max(0, x - dx))]
return bytes(out)
tmp = tempfile.mkdtemp()
cap = f"{CAPS}/live-{SCREEN.replace('_', '-')}.png"
render = os.environ.get("RENDER", "")
for p in (cap, render):
if not p or not os.path.exists(p):
print(f" 🔴 missing: {p or 'RENDER=<png>'}"); 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, f"{tmp}/c.gray")
print("CONTROLS -- the render against itself with ONE REGION displaced.\n"
"The map must put that region on top; magnitude only in the linear regime.\n")
x0, y0, x1, y1 = CTRL_BOX
bad = []
def control(dx, check_magnitude):
t = tiles(r, shift_box(r, CTRL_BOX, dx))
show(t, f"known +{dx} px shift inside x {x0}-{x1}, y {y0}-{y1}", 4)
hits = [(k, v) for k, v in top(t, 4)
if x0 - TILE < k[0] < x1 and y0 - TILE < k[1] < y1]
if not hits:
bad.append(f"+{dx} px: displaced region not in the top 4 tiles")
return
best = max(hits, key=lambda kv: kv[1][0])[1][1]
if best <= 0.3:
bad.append(f"+{dx} px: local slope {best:+.3f} has the wrong sign or is flat")
elif check_magnitude and abs(best - dx) > 0.4:
bad.append(f"+{dx} px: local slope {best:+.3f} does not recover it")
print(f" -> localised, local slope {best:+.3f} px"
f"{'' if check_magnitude else ' (saturating -- a LOWER BOUND)'}\n")
control(CTRL_A_DX, True)
control(CTRL_B_DX, False)
if bad:
print(" 🔴 CONTROL FAILED: " + "; ".join(bad))
print(" A map that cannot find a displacement it was told about cannot be")
print(" trusted to have found one it was not. Report suppressed.")
sys.exit(2)
print(" ✅ controls pass: a 1 px displacement is localised and measured, a 2 px\n"
" one is localised with its magnitude understated. So a hot tile with a\n"
" real slope is a floor on the displacement, never a ceiling.\n")
print(f"THE REAL PAIR -- {SCREEN}\n")
rt = tiles(r, c)
show(rt, f"{SCREEN}: hottest tiles, whole-frame LUT applied", 10)
ms = sorted(v[0] for v in rt.values())
med = ms[len(ms) // 2]
efs = sorted(v[0] / v[4] for v in rt.values() if v[4] > 0.01)
med_ef = efs[len(efs) // 2]
hot = max(rt.items(), key=lambda kv: kv[1][0])
print(f"\n median tile |resid| {med:.2f} hottest {hot[1][0]:.2f} "
f"at {hot[0][0]},{hot[0][1]} ({hot[1][0]/med:.2f}x median)")
print(f" median tile edge/flat {med_ef:.2f}")
efs_hot = [v[0] / v[4] for _, v in top(rt, 10) if v[4] > 0.01]
print(f" hot tiles span edge/flat {min(efs_hot):.2f}..{max(efs_hot):.2f}, "
f"straddling that median")
print(" 📌 SO THE COLUMN DOES NOT SPLIT THEM. I added it expecting two families --")
print(" tiles hot only at edges (an edge-rendering difference) against tiles")
print(" hot everywhere (a local tone the global LUT mis-serves). The hot tiles")
print(" run continuously across the median instead, so the hot region is NOT")
print(" one anomalous element with a character of its own. Note the frame-wide")
print(" pooled edge/flat is 3.16 while the per-tile median is 1.84: pooling is")
print(" dominated by the tiles carrying the most edge pixels, and reading a")
print(" per-tile threshold off it would have manufactured the split.")
print("\n ⚠️ THIS TOOL NAMES NOTHING. A hot tile is a coordinate, not an element.")
print(" What sits under it is the Decoder's to say -- they hold the disc.")