Files
Sylpheed/docs/re/canary-processing-between-guest-and-capture.md
sylph-decoder 515ad205e5 re: what Canary does between the guest's draw and a captured pixel
Answers question 3 of the 2026-09-02 play-test, from Canary's own source.

Gamma: Canary applies NONE. VdGetCurrentDisplayGamma is declared kStub and
only reports kernel_display_gamma_type (default 2, TV/BT.709) to the guest
so D3D can build a ramp. And the guest does not apply one either, for the
splash: its dumped pixel shader is four ALU ops -- tfetch2D, three muls, a
max -- with no pow, no ramp, no lookup.

Geometry: yes, the presenter resamples. present_letterbox defaults true and
present_safe_area_x/y default to 100, so the guest's 1280x720 is scaled to
fit the window and letterboxed with nothing cropped. That accounts for a
number the corpus has carried without explaining: captures measure the game
surface at 1279x675, and a single-pixel oracle placed at 1280x720
coordinates read the copyright line instead.

The structural consequence is the useful part. There are two measurement
paths and only one has Canary in it:

  pixels        guest draw -> EDRAM -> resolve -> front buffer ->
                presenter (scale + letterbox) -> X11 -> screenshot -> PNG
                => carries a resample
  vertex stream guest CPU writes a vertex buffer -> the draw logger reads
                guest memory directly
                => carries nothing

The per-frame alpha series is the second path, which is why it can be
stated as the game's values rather than as pixels we measured. Everything
measured off a PNG carries the resample: every RMSE against a capture,
every glyph count, every surface mean.

Refutation attempt on this corpus's own founding principle, "the oracle is
the real game running in Canary, captured": it SURVIVES but needs a
qualifier it has never carried. A PNG capture is the oracle plus a
resample. Harmless for ordering, counts, durations and change; a filter in
the path for anything pixel-exact. The vertex stream is the stronger oracle
and should be preferred where the question can be asked of it.

Not measured: the resample's actual filter. I read the cvars saying scaling
happens, not the kernel doing it.

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

4.8 KiB
Raw Blame History

✅ What Canary does between the guest's draw and a captured pixel — and which of our measurements it touches

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 — "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:

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: yes, the presenter resamples

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 the guest's 1280×720 is scaled to fit the host window and letterboxed. That is a resample, and it explains a number the corpus has carried without accounting for it: captures measure the game surface at 1279×675, not 1280×720. A single-pixel oracle placed at 1280×720 coordinates read the copyright line instead — which is exactly the failure wait_title.sh was found to have.

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 can state per-frame alphas as the game's values rather than as pixels we measured.

⚠️ Everything in this corpus measured off a PNG carries the resample. That includes every RMSE against a capture, every glyph count, every surface mean, and the motion-census numbers the play-test quotes for the port. It does not make them wrong — a resample preserves change, which is what a motion census measures — but it does mean a pixel-exact comparison is comparing through a filter nobody has characterised.

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, but it needs a qualifier it has never carried. A capture is the oracle plus a resample whenever it is a PNG. For ordering, counts, durations and change — the quantities TEMPORAL-VERIFICATION.md already tells us to prefer — the resample is harmless. For anything pixel-exact it is a filter in the path, and the corpus has been treating PNG captures and vertex-stream captures as the same kind of evidence when one has an uncharacterised transform in it and the other has none.

The vertex stream is the stronger oracle and should be preferred wherever the question can be asked of it.

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.