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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user