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
+46
View File
@@ -382,6 +382,52 @@ delay + ~100ms pipeline = exactly the measured 2.11–2.22s. The lag ALSO scales
"time since app launch" up to the ring depth — a take right after a restart reads as
~370ms while a fully-warmed app reads ~2.2s. Same code, wildly different numbers.
### Measuring webcam CAMERA cadence in a recording — the MAD method (RECIPE, 2026-10-16)
Derived while reading the first instrumented take. Answers "did the camera's cadence reach the
FILE?" — the ground truth beside the pump's own counters. Tool: **`scripts/webcam-cadence.py`**
(`python3 scripts/webcam-cadence.py TAKE.mp4 --x X --y Y --size 180 --start SECONDS`).
Three wrong readings were produced before it worked; each is a trap to skip:
1. **Exact pixel equality is useless.** H.264 re-encodes even a *fully static* region slightly
differently every frame, so `cur == prev` reported **57.9 distinct images/s** on a take whose
camera delivers 30/s — every frame "new". Use a THRESHOLD on the mean absolute difference
(MAD) between consecutive frames: the distribution is bimodal (codec noise < 0.1, real camera
motion > 1), so read the threshold off the histogram (0.5 for a moving subject) instead of
guessing.
2. **The crop must sit INSIDE a round clip.** The webcam is `clip=Round`, radius `r = min(w,h)/2`
= 145 for a 526×290 box. A square crop's corners lie at `s/√2` from the centre, so `s = 240`
→ 170 > 145 → the corners sampled the 60fps BACKGROUND behind the circle and the probe counted
60/s. Keep `s ≤ 1.41·r` (180 is safe for r=145). **The crop rect comes from the pump's
`FramePump scene dump:` line** (`rect=1425,748 526x290` → centre 1688,893).
3. **One scene at a time.** Element rects differ per scene, so a crop carried across a scene
switch sits on background and reports a multi-second "hold" that never happened (a 13.8s
"freeze" in the first pass was just the `Chat` scene). Pick a window from the dump timestamps
with `--start`.
4. **A still subject also produces long holds.** Threshold counting cannot distinguish "the
camera had nothing new to send" from "we repeated a frame" — read it BESIDE the pump's
`FramePump stats` lines (`N renders/5s` = composites/s, `webcam N/s` = arrivals), which do not
depend on subject motion.
**Healthy shape:** the hold histogram peaks at **2 frames** (30fps camera in a 60fps container:
every image twice → ~50% duplicates), distinct/s ≈ the camera's own rate, longest holds in the
low hundreds of ms. Oct 16 Release take: 29.6/s, **82% of images held exactly 2 frames**, longest
267ms. A peak at 4+ frames, or distinct/s approaching the composites/s figure, is the judder.
**Cross-check with ffmpeg's own filters (no numpy, uses this file's A/V-sync technique):** the metric
above is the mean absolute frame-to-frame difference, which is exactly what
`tblend=all_mode=difference,signalstats,metadata=print` reports as `YAVG`. Same crop, 40s window:
```bash
ffmpeg -v error -ss 45 -t 40 -i TAKE.mp4 \
-vf "crop=180:180:1598:803,scale=64:64,format=gray,tblend=all_mode=difference,signalstats,metadata=print:file=-" \
-f null - | grep YAVG | sed 's/.*YAVG=//' # count values <= threshold
```
It agreed with the script on the same take (52.2% duplicates / 28.6 distinct/s vs 50.7% / 29.6/s —
the windows differ slightly), so either path is trustworthy; use the script when the full take and
the hold histogram matter, this pipeline for a fast spot check.
### Feeding a rawvideo pipe at 60fps: deadline pacing + row-blit budget
Derived 2026-09-03 (take-3 diagnosis — the stats seam from `97ffc42` named the stage