From 515ad205e5c7c277c02d39ffb85113062fc9a936 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Wed, 2 Sep 2026 16:11:09 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t --- ...ry-processing-between-guest-and-capture.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/re/canary-processing-between-guest-and-capture.md diff --git a/docs/re/canary-processing-between-guest-and-capture.md b/docs/re/canary-processing-between-guest-and-capture.md new file mode 100644 index 00000000..b71ea51e --- /dev/null +++ b/docs/re/canary-processing-between-guest-and-capture.md @@ -0,0 +1,103 @@ +# ✅ 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`](../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: **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`](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.