Skip to content

feat(engine): report a take shorter than its window - #15

Merged
This-Is-NPC merged 5 commits into
masterfrom
feat/weigh-the-take
Sep 8, 2026
Merged

This-Is-NPC merged 5 commits into
masterfrom
feat/weigh-the-take

Conversation

@This-Is-NPC

Copy link
Copy Markdown
Owner

Before

  • A take could come back shorter than the scene that was performed, and the run reported it as a success. The recorder exited cleanly, the container was whole, the file was not empty and the film played at real speed with no gap in it — so every check a recorder made for itself passed. What was missing was the end, which is where a scene puts the thing it was made to show.
  • The only guard on a finished take was info.Size() == 0. A short take is not an empty one.
  • wf-recorder was given twenty seconds to close after its interrupt, and the file was fetched whatever the outcome. A recorder caught mid-close was copied as a finished take and handed back as a good one.
  • The framebuffer recorder already measured the rate its grabs achieved, so its takes are real time by construction. Nothing said so, and the docs sent every scene that did not end its own session to the inside recorder.

After

  • Backstage clocks the recorder from warm to stop and reads the clip. A clip materially shorter than that window is an error naming both lengths, and the take is kept because a short take is still evidence.
  • The check lives in the engine, so it covers all three recorders rather than one.
  • A recorder still closing when its time runs out is reported instead of passed off as whole, and every guest take prints how long its recorder took to close and how much it wrote while closing.
  • The docs name the defect, keep what is measured apart from what is not, and state the recorder choice as a trade rather than a default.

Summary of changes

Aspect Change
Detection checkTake compares clip length against the wall-clock recording window; error when short, clip kept, both lengths named
Placement In the engine, not a recorder, so inside, framebuffer and gpu are all covered
Window Wall-clock, not the sum of step delays — props, run steps and slow guests take as long as they take, and --speed changes the delays but not the clock
Threshold 2 seconds or 2% of the window, whichever is larger
Reuse Duration moved from internal/production to internal/recorder; production imports the engine, so the engine could not reach it without a cycle
Instrumentation WF.Stop reports the close time and the bytes written while closing
Failure surfaced WF.Stop returns an error, with the path, when the recorder never left before the deadline
Docs The defect, the measurements, the unestablished readings, and a corrected recorder trade-off in four pages

Files updated

  • internal/engine/take.go
  • internal/engine/take_test.go
  • internal/engine/engine.go
  • internal/engine/engine_test.go
  • internal/recorder/length.go
  • internal/recorder/wf.go
  • internal/recorder/wf_test.go
  • internal/production/speed.go
  • internal/production/speed_test.go
  • docs/what-does-not-work.md
  • docs/design.md
  • docs/how-to-record-inside-a-vm.md
  • docs/cli.md
  • docs/internal/ARCHITECTURE.md

Validation

Scenario Outcome
Eleven measured takes of one guest, replayed through the threshold Separated correctly: the one take held to its end was 0.27s under its window and passes; every take that lost its end was 5–20s under and flags
Tightest real cases — two terminal takes losing about a twentieth of themselves Flag, which is the boundary worth guarding
A fifty-minute framebuffer take, 3075.2s against a 3072s window Passes; a take running past its window is never a complaint
A clip that cannot be measured, e.g. no ffprobe on the host Noted on stderr, run not failed — a host without ffprobe must still be able to film
A rehearsal (Record: false) Not weighed; no file is written
End to end through Engine.Run with a short clip Run returns an error, the recorder is still stopped, the clip is still on disk
go build, go vet, go test ./... Pass
Each of the four commits, built and tested in isolation from git archive Pass

Deviations

Criterion Deviation Rationale
Root cause fixed Not fixed — detected only What the recorder runs out of is not established. The evidence rules out the fetch (the file on the guest is short by the same amount) and guest memory pressure (five spare gigabytes, no swap in use), and rules out the capture rate (the same scene lost the same five seconds at 12, 20 and 30 fps). Shipping detection first stops the silent failure without guessing at a cure.
One intent per commit Four commits, one branch The refactor has to land before the feature that depends on it; the recorder fix and the docs are separate intents.
takeSlack as a var Not a const So a test can weigh a one-second take without first sleeping for a real scene's worth of seconds.

Risks / follow-ups

  • The threshold is empirical. 2s or 2% separates every take measured so far, but the terminal takes sit closest to it. If the loss turns out to have a fixed part from closing plus a part proportional to the encoder's backlog, that is the end where the threshold would move. The two terminal cases are already in the test table for exactly that.
  • A long production now fails on a short scene. A scene error aborts the whole production, so a marginally short take will stop a multi-scene run partway. That matches the existing behaviour for a failed step, and the clips already recorded are kept, but it is a behaviour change worth knowing before a long run.
  • A recorder still closing at its deadline is now an error where it used to be silent. The file is still fetched and the path still returned, so nothing is lost, but a run that used to pass will now fail.
  • Root cause still open. The reading that fits is that wf-recorder, which encodes in software because these guests have no render node, was that far behind real time when it was stopped and did not write what it had not yet encoded. It has not been confirmed. The new close-time and bytes-written log lines are what would confirm it on the next take.

References

  • Commits: 17c3852, 8bcd701, 867a11e, d54724b
  • Branch: feat/weigh-the-take

Duration lived in internal/production, which imports the engine, so the
engine could not ask how long a clip is without an import cycle. It is
the one question every recorder's output can be asked, so it moves
beside the recorders, where both callers reach it the same way.
A recorder that stops before the scene does leaves a clip that passes
every check there is: the process exits cleanly, the container is whole,
the file is not empty, and the film plays at real speed with no gap in
it. What is missing is the end, which is where a scene puts the thing it
was made to show. The run called it a success.

Backstage holds both numbers and now compares them. It clocks the
recorder from warm to stop, reads the clip, and reports a clip
materially shorter than that window as an error naming both lengths. The
take is kept, because a short take is still evidence.

The window is wall-clock and not the sum of the step delays. A prop, a
run step or a slow guest takes as long as it takes, and --speed changes
the delays but not the clock.

Slack is two seconds or two percent of the window. Over eleven measured
takes, the one take held to its end came back a quarter of a second
under, and every take that lost its end came back five to twenty seconds
under. The check sits in the engine, so it covers all three recorders.
Stop waited twenty seconds for the recorder to leave after the interrupt
and then fetched the file whatever the outcome, so a recorder caught
mid-close was copied as a finished take and handed back as a good one.
It is now an error, carrying the path, because the file is still worth
looking at.

Each take also prints how long the recorder took to close and how much
it wrote while closing. Those two numbers are the only thing separating
a recorder that was merely slow from one that ran out of time, and
nothing else was recording them.
what-does-not-work.md gains the defect with the measurements behind it,
and keeps what is not established separate from what is: what the
recorder runs out of, and whether the loss has a fixed part from closing
plus a part proportional to how far behind the encoder is.

design.md and how-to-record-inside-a-vm.md both told the reader to use
the inside recorder for every scene that does not end its own session.
That is no longer the trade. The framebuffer recorder assembles at the
rate it measured, so it is real time by construction and has no end to
lose, which makes it the safer choice for a long still scene whose last
beat is the point.

Asking for fewer frames a second does not help, and the docs now say so
rather than suggesting it: the same scene lost the same five seconds at
twelve, at twenty and at thirty frames a second.
The slack was two seconds or two percent of the recording window,
whichever was larger, and the eleven takes that calibrated it were all
under three minutes. In that range the two seconds is the term that
decides and the percentage never weighs.

It weighs on a long take. Two percent of a fifty-one minute session is
sixty-one seconds, so a take that lost a full minute of its ending would
have passed the guard in silence -- and that is the take nobody reshoots
casually, an hour of clock, ending on the beat the film exists for.

The percentage is now capped at five seconds, the smallest loss ever
measured, so the slack never grows past a gap that has really happened.
@This-Is-NPC
This-Is-NPC merged commit 15f7676 into master Sep 8, 2026
1 check passed
@This-Is-NPC
This-Is-NPC deleted the feat/weigh-the-take branch September 8, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant