Read the first instrumented take: the judder did not reproduce, no element is slow

The measurement unit existed to name which of the six scene elements eats ~140ms per
composite. The creator's Release recording (ty-20261016-1841-559-0002.mp4, 174.5s,
1920x1080@60) names none of them — the whole composite costs ~1ms:

  composites     301 per 5s = 60/s, every slot, 0 dropped, 0 stalls after warm-up
  avg render     1.0-1.1ms (worst 2.5ms) = ~7% of the 16.67ms budget
  parts          "#5 HD Pro Webcam C920 0.4ms, #5 ... raster 0.2ms, backdrop 0.2ms"
                 - the other four elements never reach the 0.1ms print floor
  C4 cache       151R/150H: half the ticks were a whole-frame BlockCopy
  webcam         30.0/s at the resolver, longest gap 50.0ms (one 60Hz frame of
                 sampling phase, not a drop)
  the FILE       29.6 distinct webcam images/s, 82% of images held exactly 2 frames,
                 longest hold 267ms (the healthy 30fps-in-60fps signature)

The two stalls are not composite cost: 124ms on the very first render (warm-up) and
53ms entering the "Chat" scene (a rebake).

So the historic slow numbers are not a slow scene but different configurations:
- Oct 9 19:48 (only bad take still on disk) was the VERTICAL tier:
  "FramePump started (1080x1920 @ 60 fps) scene='Live' comp=608x1080" - the 607x1080
  centre crop upscaled to 2M px, 92 renders/5s, avg render 45.0ms, worst 278ms.
- Oct 5 12:01 (141ms) logged no scene=/comp= at all - the suffix did not exist yet,
  so the map's "21:9 -> 1920x803" explanation stays a hypothesis, not a reading.
- Every slow window shows "cache xR/0H" (the whole-frame cache never reusing) while
  this take shows 151R/150H, and one Oct 5 take latches mid-take (1.5ms -> 3.0ms ->
  45.2ms in 15s, then stays). That latch is the lead for the next take; it is a
  hypothesis and the parts line is what will decide it.

Corrections to the map (same commit, stale facts replaced not appended):
- "It was never Debug-vs-Release" is WITHDRAWN: same 1920x1080 tier, Oct 9 = 10.4ms
  vs this Release take's 1.0ms, and Build <hash> carries no configuration - every
  pre-2026-10-16 render number is config-unknown.
- The "same scene shape" 10:01/12:01 comparison is superseded: it was an eye
  comparison of takes whose geometry was never logged.

New: scripts/webcam-cadence.py - the ground-truth probe for what the FILE holds,
beside what the pump says it did. It took three wrong readings to get right (H.264
re-encoding defeats exact-pixel comparison; a round-clip crop's corners leak 60fps
background; a fixed crop across a scene switch reads background), all recorded in
MyMistakes.md as a recipe, including an ffmpeg-native cross-check
(tblend=difference + signalstats.YAVG - the same metric, the technique already in
this repo's A/V-sync recipe) which agreed: 52.2% duplicates / 28.6 distinct/s vs
50.7% / 29.6/s on different windows of the same take.

Next: no fix is allowed yet - the creator makes a Release take in the failing
geometry (vertical 1080p60 tier first), and that take's parts line names the stage.

Docs in the same commit: ai.md (new "What the first instrumented take said" section,
the two corrections, the parts-line denominators, the tool in the inventory),
TASKS.md (reading outcome), HANDOFF.md (rewritten), MyMistakes.md (recipe).

Gates: clean build 0 warnings, full suite 428/428, scope check passed.
This commit is contained in:
2026-10-16 19:05:01 -07:00
parent 84cbec2d19
commit cdf82f688d
5 changed files with 325 additions and 57 deletions
+66 -3
View File
@@ -758,7 +758,10 @@ pure pixel work in the composite, not frame lookup. Two consequences, both load-
143–155ms between sessions with nothing logged explaining it. That comparison was made by EYE on
two takes because the log carried no per-element breakdown and no scene dump — both now exist
(see "Per-element composite timing + scene dump" below); naming the culprit is a reading, not a
guess, and guessing is explicitly out of bounds.
guess, and guessing is explicitly out of bounds. **Superseded 2026-10-16:** "same scene shape" was
an eye comparison of takes whose geometry was never logged, and the next bad take still on disk is
the *vertical* tier (below). A Release 1920×1080 take now measures 1.0ms with no element over
0.4ms — see "Per-element composite timing + scene dump" → what the first instrumented take said.
**Counter subtlety, so it is not re-derived:** the pump's `webcam` line read 13/s while the file held
6.8/s. Not a bug — the counter stamps a frame when the resolver FIRST sees it, and one composite takes
@@ -803,6 +806,10 @@ allowed until a real reading names the culprit.
- Gate test: `ytLive.Tests/CompositeTimingTests.cs` (one `[Fact]`) — a REAL composite of a full-canvas
element plus a 64×64 one must rank the full-canvas element first, carry the `raster` sub-stage and
the fixed stages, and the dump must name both elements with z-index/type/rect and echo the split.
- **`scripts/webcam-cadence.py`** — the other half of a reading: what the FILE holds (distinct webcam
images/s + hold histogram), beside what the pump says it did. Recipe and its three traps (H.264
re-encoding, the round-clip crop, scene switches): `MyMistakes.md` → "Measuring webcam CAMERA cadence
in a recording".
**Read takes in Release.** The creator launches `dotnet run -c Release`; Debug pixel paths are
several× slower, so a Debug number is not a reading of this pipeline (see Run).
@@ -814,6 +821,57 @@ per-source timers via `util/profiler.h`). Same shape here: one timer per element
as an average, plus the stage split. The `#<i> <name> raster` sub-stage is the extra this case needs —
it separates "re-sampled every tick" from "slow paste" without another session.
### ✅ What the first instrumented take said (2026-10-16, Release) — the judder DID NOT reproduce
Take `ty-20261016-1841-559-0002.mp4` (174.5s, 1920×1080@60, Release build `e95154d9` — a **recording**,
not preview; scene `Live`, switched to `Chat` at 28s, back to `Live` at 44s) and its log window:
| Reading | This take |
|---|---|
| composites | **301 per 5s = 60/s**, every slot; 0 dropped, 0 stalls after warm-up |
| avg render | **1.0–1.1ms**, worst 2.5ms — ~7% of the 16.67ms budget |
| `parts` | `#5 HD Pro Webcam C920 0.4ms, #5 … raster 0.2ms, backdrop 0.2ms` — the other four elements are under the 0.1ms print floor |
| C4 full-composite cache | **151R/150H** per window — half the ticks were a whole-frame `BlockCopy` |
| webcam at the resolver | **30.0/s**, longest gap **50.0ms** (one extra 60Hz frame = sampling phase, not a drop) |
| the file itself | **29.6 distinct webcam images/s, 82% of images held exactly 2 frames**, longest hold 267ms (`scripts/webcam-cadence.py`) |
Nothing in this take is over budget and **no element is expensive — there is no culprit here to fix.**
The two stalls in the take are not per-composite cost: 124ms on the very first render (warm-up) and
53ms on the switch into `Chat` (a rebake).
**Scale of the disagreement:** the historic slow readings are 141ms (Oct 5 12:01) and 45ms (Oct 9
19:48) — 40–140× the ~1ms this take needed per composite. Three facts about those takes, all read off
the same log:
- **Oct 9 19:48 (`ty-20261009-1948-121-0000-2.mp4`) was the VERTICAL tier**, not a 16:9 one:
`FramePump started (1080×1920 @ 60 fps) scene='Live' comp=608x1080` — the 607×1080 centre crop
bilinear-upscaled to 1080×1920 (2M px), 92 renders/5s, avg render 45.0ms, worst 278ms, 92 stalls,
`cache 92R/0H`. The file on disk is 11.6s and every frame is identical (a static desktop), so it
cannot be read for cadence — only its cost.
- **Oct 5 12:01 (`…1201-556-0001.mp4`, no longer on disk) logged `FramePump started (1920×1080 @ 60
fps)` with NO `scene=/comp=` suffix** — that suffix did not exist yet, so its source rect was never
recorded and the map's "21:9 → 1920×803" explanation stays a hypothesis.
- **A take can latch mid-take.** Oct 5 12:29 goes 1.5ms (avg render) → 3.0ms → **45.2ms within 15
seconds and stays there** (`cache 10R/0H`, then `98R/0H`). So the slow state is not a fixed property
of a scene geometry. The consistent signature of every slow window is `cache xR/**0H**`: the C4
whole-frame cache never reused once, so every tick composited. The healthy take shows the other
stable state (composite 1ms → every second tick is byte-identical → whole-frame reuse). **The latch
is the thing to test on a reproducing take** — it is a hypothesis, not a reading; the `parts` line
is what will decide it.
**What this does NOT say.** This take is the 1:1 master tier and its scene logs
`full-render split=0 elements=6 dynamic=4` (the bad-period takes logged `dynamic=5`), so it never
exercises the failing geometry. "The healthy path is healthy" is not "the judder is gone". Reproducing
it needs a Release take in the failing configuration (vertical tier first, then the 21:9 display
config) — that take's `parts` line names the stage, and only then is a fix unit allowed.
**Reading the `parts` line's denominators:** like `avg render`, it is divided by `statRenders`, which
counts C4 REUSES as well as real composites (the 301 above = 151 real + 150 whole-frame copies). So in
a healthy 30fps-camera take both averages are ~2× optimistic; the `cache NR/NH` pair on the same line
says how much of the window was real composite work. `CompositeTimings.Describe` drops stages under
0.1ms by default, and **a missing or short parts line is itself a reading** — a fully-static window
(every tick a C4 hit) composites no elements at all.
### Render budget — getting a composite inside 16.67ms (UNITs 4-5, 2026-10-05)
The pump emits one encoder frame per 16.67ms slot and **pads with a duplicate when a composite
@@ -825,8 +883,13 @@ so a slow stage names itself, and logs the scene name + compositor source rect a
21:9 display shrinks the source rect to 1920×803 ("fit", not "fill"), so the opaque-backdrop 1:1 row
walk is skipped, the base is prefilled with opaque black (~2M byte writes), and the backdrop is
resampled per-pixel (~1.54M pixels with an alpha branch). Every recorded A/B lines up:
21:9 → 141ms, 16:9 → 30-33ms, 2560×1440 (fits to 1920×1080 exactly) → fast path. It was never
Debug-vs-Release; today's Debug run on 16:9 with the game up measured 33ms.
21:9 → 141ms, 16:9 → 30-33ms, 2560×1440 (fits to 1920×1080 exactly) → fast path.
**Corrected 2026-10-16 — the "never Debug-vs-Release" half of that is withdrawn.** A Release
1920×1080 take measures **1.0–1.1ms** where an Oct 9 take of the same tier measured 10.4ms, and
`Build <hash>` carries no build configuration, so the config of every pre-2026-10-16 take is
UNKNOWN: no historic render number may be compared against a Release take. The 21:9 explanation
itself stays a hypothesis — the 141ms take's start line predates the `scene=/comp=` suffix, so its
source rect was never recorded. A take can also latch mid-take (see the instrumentation section).
**Unit 4 — cheap wins, all pixel-identical:**
- Opaque 1:1 fast paths expanded to the whole-canvas opaque case (a `BlockCopy`, not a per-pixel