This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/ui-render-tone-curve.md
Sylpheed RE agent ee73de50be re(ui): put a number on the render-vs-capture tone difference
Closes an observation I left dangling last iteration ("the capture is ~4x
darker than the render") and puts a figure on the  INDEX's texture row
already carried: exact gamma/sRGB fidelity untested because a hue
comparison cannot see it.

Geometry first: cross-correlating the main-menu capture against our
render over +/-6 px puts the best alignment at exactly dy=0 dx=0,
correlation 0.9466. Only the tone differs.

Two methods failed before one worked, and both failures are recorded.
Three dark patches gave "4x darker" -- the whole-frame best linear scale
is 0.914, so three patches from one region are not a transfer curve. A
pixel-wise fit over 854,685 pixels then produced a NON-MONOTONIC transfer
(render 96-127 mapping brighter than render 128-159) with mean abs error
10-14 for every candidate model. That is edge misalignment, not a tone
curve: at correlation 0.947 a bright pixel routinely lands on a dark one.

Flat patches fix it -- 16x16 blocks with std < 8 in BOTH images, a
threshold chosen from the counts (0/83/404/1055/1788 at std<3/5/8/12/20):

  main menu  404 patches  gamma 1.491  err 0.28   (best linear 0.276, 0.34)
  EXTRAS     382 patches  gamma 1.493  err 0.22   (best linear 0.273, 0.28)
  title      506 patches  gamma 1.338  err 1.08   (best linear 0.842, 9.02)

Reach, stated because it is narrow: those patches span only render values
~0-60, where gamma and a plain scale are nearly indistinguishable -- the
two menus decide nothing (0.28 vs 0.34, 0.22 vs 0.28) and only the title
separates them. Nothing constrains midtones or highlights.

The held-out control FAILED TO DISCRIMINATE and is reported as such: the
splash's 2918 flat patches are pure black (render 0-4), so every model
scores ~0.00. That is a test with no power, not corroboration.

Confound left open: this compares our composite to what canary DISPLAYS,
and canary applies kernel_display_gamma_type = 2 (BT.709). The exponent
may be its output stage. The discriminating run -- set it to 0, recapture,
refit -- needs one emulator session reaching the main menu and was not
done.

Classified measured, not decoded; HANDOFF says plainly that a port
applying it is authoring.
2026-08-29 01:05:25 +00:00

83 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🟡 Our composite is brighter than the emulator's frame — measured, not decoded
**Status:** 🟡 **measured, with a narrow reach and a live confound.** Closes an
observation left dangling by
[ui-8ax-fullres-background](ui-8ax-fullres-background.md) ("the capture is ~4×
darker than the render"), and puts a number on the ❔ that
[INDEX](../INDEX.md)'s texture row already carried: *"exact fidelity
(gamma/sRGB curve, premultiplied alpha, per-channel scale) is untested, since a
hue comparison cannot see it."*
⚠️ **This is not a decode.** A port applying it is authoring a value.
## First: the geometry is right
Cross-correlating `live-main-menu.png` against our render over ±6 px finds the
best alignment at exactly **dy = 0, dx = 0**, correlation **0.9466**. So the
composite is in the right place at the right size and only the *tone* differs.
(The capture is 1279×675 and top-aligned; that is the screenshot tool's crop.)
## 🔴 The first two methods were wrong, and both failed visibly
* **Three dark patches** gave "capture ≈ 4× darker". Over the whole frame the
best linear scale is **0.914**. Three patches from one region are not a
transfer curve.
* **A pixel-wise fit** over 854 685 pixels produced a non-monotonic transfer
(render 96127 → capture *143*, brighter than render 128159 → 132). That is
the signature of **edge misalignment**, not of a tone curve: at a 0.947
correlation a bright render pixel routinely lands on a dark capture pixel.
Mean abs error was 1014 for every model, which is the tell that none of them
fit.
Both are recorded because the second is the interesting failure — a fit whose
*residual* is large everywhere is not a model to choose between, it is a method
to throw away.
## The method that works: flat patches only
16×16 patches where **both** images have `std < 8`, so local edges cannot
contribute. The threshold is not arbitrary — at `std < 3` there are **zero**
patches, and the count runs 0 / 83 / 404 / 1055 / 1788 for `std <`
3 / 5 / 8 / 12 / 20.
| screen | flat patches | gamma exponent | mean abs err | best linear | its err |
|---|---|---|---|---|---|
| main menu | 404 | **1.491** | 0.28 | 0.276 | 0.34 |
| `EXTRAS` | 382 | **1.493** | 0.22 | 0.273 | 0.28 |
| title | 506 | **1.338** | 1.08 | 0.842 | 9.02 |
So `capture ≈ 255·(render/255)^γ` with **γ ≈ 1.34 1.49**.
## ⚠️ The reach — and it is narrow
* **The flat patches are almost all dark**: render values ~060. Over that range
a gamma and a linear scale are nearly indistinguishable — on the two menus the
errors are 0.28 vs 0.34 and 0.22 vs 0.28, which decides nothing. **Only the
title separates them** (1.08 vs 9.02), because its flat regions reach ~60.
* **The held-out control could not test it.** Running the same fit on the
developer splash gives 2 918 flat patches whose render range is **04** — pure
black. Every model scores ≈ 0.00 there. That is a control that failed to
discriminate, not a control that passed.
* **Nothing here constrains midtones or highlights**, which is exactly where a
γ = 1.4 curve does its visible work.
## ❔ The confound: this may be the emulator, not the game
The comparison is our composite against **what Xenia Canary displays**, and
canary applies its own output transform:
`kernel_display_gamma_type = 2` — BT.709 (HDTV) — in this container's config
(`0` linear, `1` sRGB, `3` a power set by `kernel_display_gamma_power`).
So the measured exponent may belong to canary's display stage rather than to the
game or the console. **The discriminating experiment is cheap and was not run:**
set `kernel_display_gamma_type = 0`, re-capture the same screen, and re-fit. If
the exponent collapses toward 1.0 it is canary's; if it survives it is upstream.
That needs one emulator run reaching the main menu.
## What a port should do with this
Treat it as **authored**, not transcribed. If the goal is to match the emulator —
which is what every capture in this corpus is — a γ ≈ 1.4 darkening of the
composite gets closer, and is best applied where it was measured (the dark
background), not extrapolated to the whole range on this evidence.