diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 507d53a..bba8906 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -243,8 +243,18 @@ authored version can be deleted. `VdGetSystemCommandBuffer` and `VdSetDisplayMode` — the moment a ramp-builder would ask. Control in the same log: 359 `VdRetrainEDRAM` lines, so an absent call would have shown. - 🟡 Still open: whether it then *writes* the ramp, and whether γ ≈ 1.4 is it. - That write is a GPU register operation (`DC_LUT`), invisible to kernel logs. + 🟡 **That it then writes the ramp is inferred, not observed** — but the chain + is closed: canary's swap-path gamma stage is a **pure 256-entry LUT** with no + other transfer (`apply_gamma_table.xesli`), and that LUT **defaults to + identity** (`CommandProcessor::Initialize`, whose comment says the linear + default is "what games set when starting with the sRGB return value"). Identity + cannot produce the measured γ ≈ 1.4, so a non-identity ramp was written. + ⚠️ The weak joint is that this assumes our composite reproduces the *pre-ramp* + framebuffer; what carries it is that a systematic ~1.4 across three screens is + not the shape of a compositor bug. A fixed sRGB stage does not fit either + direction (encode brightens; decode darkens far more). + **Practical upshot for you is unchanged:** the darkening is the game's own + display ramp, so it belongs in a port as a display profile, not baked in. ⚠️ Note the ramp depends on the display type the game is *told*; canary hard-codes TV/BT.709, which on hardware is a console setting. **So this is a display profile, not a fixed property of the game** — reasonable to expose as a diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index abc5c30..e51ec7f 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -631,3 +631,16 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the any title screen. A blocker that stops one experiment does not stop every experiment in the same area, and it is worth re-reading the parked list against what each item really requires rather than against the area it belongs to. +* **A default value is evidence.** Whether the game writes a gamma ramp looked + like it needed a GPU trace. It mostly did not: canary initialises the ramp + table to **identity** and applies it through a shader that is a pure LUT lookup + with no other transfer. An unwritten ramp is therefore a no-op, and any + non-identity transfer in the output implies a write. Reading what a field holds + when nobody has touched it turns "I cannot observe the write" into "the write + must have happened" — cheaper than instrumenting, though it stays an inference + and should be labelled one. +* **Name the weak joint of an inference in the same breath as the conclusion.** + The chain above assumes our composite reproduces the pre-ramp framebuffer, + which is the one step that could be wrong. Writing that down beside the + conclusion — rather than only the supporting facts — is what lets a later + reader attack it instead of inheriting it. diff --git a/docs/re/structures/ui-render-tone-curve.md b/docs/re/structures/ui-render-tone-curve.md index b4ffbc6..a0b839f 100644 --- a/docs/re/structures/ui-render-tone-curve.md +++ b/docs/re/structures/ui-render-tone-curve.md @@ -113,11 +113,48 @@ Per the export's own comment the returned type is "used in D3D SetGammaRamp/SetPWLGamma", so the game asks the question a ramp-builder asks, at the moment one would ask it. -🟡 **Still not established: that it then WRITES the ramp**, or that γ ≈ 1.34–1.49 -is that ramp. The write is a GPU register operation (`DC_LUT`), invisible to -kernel logging — this run had Gpu logging masked off, and individual register -writes are not logged in any case. A GPU trace records gamma ramps as a command -type (`TraceWriter::WriteGammaRamp`), which is the next place to look. +### 🟡 The ramp write: inferred from a closed chain, not directly observed + +The write is a GPU register operation (`XE_GPU_REG_DC_LUT_RW_INDEX` in +`CommandProcessor::WriteRegister`), invisible to kernel logging and unlogged in +any case. But two facts from the source close the reasoning: + +**1. The swap-path gamma stage is a pure LUT — nothing else.** +`apply_gamma_table.xesli` is the whole transform: + +``` +uint3 apply_gamma_input = uint3(texel_fetch(source, pixel).rgb * 255.0 + 0.5); +apply_gamma_output.r = texel_fetch_buffer(xe_apply_gamma_ramp, input.r).b; +… .g = …(input.g).g; … .b = …(input.b).r; +``` + +An index into a 256-entry table. No sRGB encode, no second transfer function. + +**2. The table defaults to IDENTITY.** `CommandProcessor::Initialize` fills it +with `value = i * 0x3FF / 0xFF`, and its own comment says so: *"Initialize the +gamma ramps to their default (linear) values — taken from what games set when +starting with the sRGB (return value 1) `VdGetCurrentDisplayGamma`."* An unwritten +ramp is a no-op. + +So: the only transform is a LUT; the LUT is identity unless the guest writes it; +the game queries the display gamma at init (measured above); and the captured +frame differs from our composite by γ ≈ 1.34–1.49, which identity cannot produce. +**⇒ the guest wrote a non-identity ramp.** + +⚠️ **This is an inference, and here is its weak joint.** It assumes our composite +faithfully reproduces the *pre-ramp* framebuffer, which it does not exactly — our +renderer has its own inaccuracies. What makes it hold up is the *shape*: a +systematic exponent near 1.4, fitted on flat patches, consistent across three +screens, is not the signature of a compositor bug. + +A fixed sRGB stage elsewhere in the presenter is the obvious alternative and does +not fit: an sRGB **encode** (≈ `^0.45`) brightens, and we measured darkening; an +sRGB **decode** (`^2.2`) darkens far more than 1.4. + +✅ **Direct observation is still available and cheap**, and needs the emulator only +to boot: a GPU trace records gamma ramps as their own command type +(`TraceWriter::WriteGammaRamp`), or one log line at +`XE_GPU_REG_DC_LUT_RW_INDEX` would settle it outright. Not done. ⚠️ And the ramp a game builds depends on the display type it is *told*. Canary hard-codes `2` (TV/BT.709); on hardware that is the console's display setting. So