Files
Sylpheed/docs/re/canary-processing-between-guest-and-capture.md
sylph-decoder b32bcb97ae re: CORRECT the resample consequence -- the captures are crops, not resamples
The port pre-registered a test and ran it: against live-splash-publisher.png
a cropped render scores RMSE 558.1 (0.85%) and a scaled one 10118.8
(15.4%). Cropping is 18x better.

Confirmed here independently, from a different observable: the committed
captures are 1279x675, 1280x690 and 1252x754. VARYING heights. A fixed
presenter resample produces one size; crops of differently-sized windows
produce exactly that spread. And ui-render-tone-curve.md had already
recorded every capture aligning at dy=0 dx=0 with correlation 0.9466, which
a 0.9375 vertical scale cannot produce -- the evidence was in the corpus
before I wrote the claim.

So the captures are crops of a 1:1 surface, and every RMSE, glyph count and
surface mean against them is pixels to pixels with no filter to caveat. The
blanket "everything measured off a PNG carries the resample" is withdrawn.

Not refuted: the cvar reading. Canary does letterbox by default. What is
refuted is my inference that the corpus's capture path went through it.
Whether the presenter is bypassed, the window is 1:1, or the tool crops
before saving is open and unestablished.

The error is the one this session keeps paying for: I read a configuration
and inferred a consequence FOR THE DATA without testing it against the
data. The test costs one render and one RMSE and I had every capture needed
to run it.

Also downgrades my refutation of the corpus's oracle principle from
"survives with a qualifier" to "survives, and my qualifier was wrong",
kept rather than deleted because the failure is the instructive part.

Section 1 (gamma: VdGetCurrentDisplayGamma is kStub, the splash shader has
no pow/ramp/lookup) and the two-path distinction stand unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-02 16:14:28 +00:00

144 lines
6.9 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.
# What Canary does between the guest's draw and a captured pixel
> # 🔴 CORRECTION 2026-09-02, same day — §2's CONSEQUENCE is REFUTED
>
> I wrote that the presenter's letterbox-and-scale is in the capture path, that
> it explains the 1279×675 surface, and that **"everything measured off a PNG
> carries the resample."** The port pre-registered a test and ran it: against
> `live-splash-publisher.png`, a **cropped** render scores RMSE **558.1 (0.85 %)**
> and a **scaled** one **10 118.8 (15.4 %)**. **Cropping is 18× better.**
>
> ✅ **Confirmed independently here, from a different observable.** The committed
> captures are **1279×675**, **1280×690**, **1252×754** — *varying* heights. A
> fixed presenter resample produces one size; **crops of differently-sized
> windows produce exactly this spread.** And `ui-render-tone-curve.md` already
> recorded every capture aligning at `dy=0 dx=0`, correlation 0.9466, which a
> 0.9375 vertical scale cannot produce. The evidence was in the corpus before I
> wrote the claim.
>
> **So the captures are CROPS of a 1:1 surface, not resamples**, and every RMSE,
> glyph count and surface mean against them is pixels to pixels with no filter to
> caveat.
>
> ⚠️ **What is NOT refuted:** the cvar reading itself. Canary *does* letterbox by
> default. What is refuted is my inference that the corpus's capture path went
> through it. Whether the presenter is bypassed, the window is 1:1, or the tool
> crops the letterbox before saving is **open and unestablished**.
>
> 🔴 **The error is the one this session keeps paying for.** I read a
> configuration and inferred a consequence *for the data* without testing it
> against the data — the same shape as reading a base-address change as a decode.
> The test that refutes it costs one render and one RMSE, and I had every capture
> needed to run it.
>
> §1 (gamma) and the two-paths distinction in §3 stand; §3's blanket caveat does
> not. Corrected in place below.
**Status: ✅ decoded from Canary's own source.** Instrument: ⟨canary-source⟩,
with ⟨capture⟩ for the dumped guest shader. 2026-09-02.
Answers question 3 of [`../agents/PLAYTEST-2026-09-02.md`](../agents/PLAYTEST-2026-09-02.md)
— *"present cadence, and any resolve, scale or gamma between the guest's draw and
a capture's pixels."*
---
## 1 — gamma: **Canary applies none**
`VdGetCurrentDisplayGamma` is the export the play-test warns about. Reading it:
```cpp
void VdGetCurrentDisplayGamma_entry(lpdword_t type_ptr, lpfloat_t power_ptr) {
*type_ptr = cvars::kernel_display_gamma_type; // default 2 = TV (BT.709)
*power_ptr = float(cvars::kernel_display_gamma_power);
}
DECLARE_XBOXKRNL_EXPORT1(VdGetCurrentDisplayGamma, kVideo, kStub);
```
**It is declared `kStub` and it transforms nothing.** It *reports* a gamma type to
the guest, which D3D would use to build a ramp. So the question becomes whether
the **guest** applies one — and for the splash it demonstrably does not. The
splash pixel shader, dumped from the running game, is four ALU ops:
```
tfetch2D r2, r1.xy, tf0
mul r1.___w, r2.wwww, r0.wwww
mul r0.xyz_, r2.xyzz, r0.xyzz
mul r1.xyz_, r0.xyzz, r1.wwww
max oC0, r1, r1
```
**No `pow`, no ramp, no lookup.** Nothing in the splash path applies gamma —
neither side of the emulation boundary.
## 2 — geometry: ⚠️ the presenter CAN resample, but the corpus's captures are CROPS
`presenter.cc` defaults:
| cvar | default | effect |
|---|---|---|
| `present_letterbox` | **true** | aspect preserved, bars added rather than stretching |
| `present_safe_area_x` / `_y` | **100** | nothing cropped |
So Canary *would* scale the guest's 1280×720 to fit the host window and letterbox
it. 🔴 **But the committed captures did not go through that**, per the correction
above — they are crops. The 1279×675 surface is a **cropped window grab**, not a
scaled one, which the varying capture sizes (1279×675, 1280×690, 1252×754) show
directly.
⚠️ **The `wait_title.sh` single-pixel failure is still real** and still explained:
a coordinate valid at 1280×720 lands outside a 1279×675 *crop* just as surely as
outside a scaled one. The failure was the fixed coordinate, not the transform.
## 3 — 🔴 So there are TWO measurement paths, and only one has Canary in it
| path | route | Canary processing |
|---|---|---|
| **pixels** | guest draw → EDRAM → resolve → front buffer → **presenter (scale + letterbox)** → X11 → `screenshot` → PNG | **a resample**, plus whatever the window system does |
| **vertex stream** | guest CPU writes a vertex buffer → **the draw logger reads guest memory directly** | **none** |
📌 **The per-frame alpha series is the second path.** The `k_8_8_8_8` colour is
read out of the guest's own vertex buffer *before* it reaches a shader, a render
target, a resolve or the presenter. **There is no Canary processing between the
guest's intent and that number** — which is why
[`splash-interpolates-every-frame.md`](splash-interpolates-every-frame.md) can
state per-frame alphas as the game's values rather than as pixels we measured.
🔴 **The blanket caveat I attached here is WITHDRAWN.** I claimed everything
measured off a PNG carries a resample. It does not: the captures are crops, and
pixel-exact comparison against them is pixels to pixels. The two-path
*distinction* stands — the vertex stream still has strictly less between the
guest and the number — but the practical gap is far smaller than I said, and a
large body of shared evidence needs no qualifier.
## Refutation attempt, recorded per the adversarial duty
**Target:** this corpus's own founding principle, stated at the top of three
documents — *"the oracle is the real game running in Xenia Canary, captured."*
**Result: it SURVIVES, and my proposed qualifier was WRONG.** I claimed a PNG
capture is "the oracle plus a resample". The port tested it and it is not — the
captures are crops, and the principle needs no amendment. Recorded as a failed
refutation rather than deleted, because the failure is instructive: I tried to
attach a caveat to a large body of evidence on the strength of a config file, and
the check that would have stopped me was one render and one RMSE.
**The vertex stream is still the cleaner instrument** — strictly less sits between
the guest and the number — but that is now a statement about robustness, not a
correction to anything measured.
## Reach
⟨canary-source⟩ for the gamma stub and the presenter defaults — these are facts
about *this build*, and a different `--present_*` or `kernel_display_gamma_type`
would change them. ⟨capture⟩ for the shader, which is the splash's; **other
screens' shaders have not been checked for gamma** and the negative in §1 covers
the splash only.
**Not measured here: the resample's actual filter.** I read the cvars that say
scaling happens, not the kernel that does it. What a 1280×720 → 1279×675 resample
does to a thin glyph is unquantified, and that is the number anyone doing
pixel-exact work against a PNG would need.