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.