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
This commit is contained in:
103
docs/re/canary-processing-between-guest-and-capture.md
Normal file
103
docs/re/canary-processing-between-guest-and-capture.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user