Skip to content

chantier: Remotion gaps — the S-effort wave (5 workstreams) - #168

Merged
LeadcodeDev merged 5 commits into
mainfrom
chantier/remotion-gaps-s
Aug 10, 2026
Merged

chantier: Remotion gaps — the S-effort wave (5 workstreams)#168
LeadcodeDev merged 5 commits into
mainfrom
chantier/remotion-gaps-s

Conversation

@LeadcodeDev

@LeadcodeDev LeadcodeDev commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Follows the audit chantier (#142, merged by #163) and the re-scoring of the Remotion differential. Tracking: issue #167.

The five S-effort gaps at the top of the re-scored list, plus two defects found while verifying them.

Workstream Subject Issue
A Distribute all 47 rules instead of 30 #165
B Resolve font-size in relative units
C Icon preloading, disk cache, loud video-decode failure #166
D Hardware-accelerated encoding
E Spring with a pinned duration, and rest-time reporting

The two corrections that matter most

Workstream A repairs a pipe, not a payload. rustmotion skills install is the only channel through which a generating agent receives the rules. It distributed 30 of 47. The absentees included the three CLAUDE.md cites by name and html-css-mental-model.md, which PR #156 had just corrected because it taught JSON forms that delete the component from the video. That file had never been in the list: the fix reached nobody.

The literal list is deleted, not completed: a build.rs walks the rules tree, making "every rule is embedded" true by construction. Adding the 17 by hand would have left the next rule just as invisible. Verified by running the binary into an empty directory: 49 files written, 47 of them rules.

Workstream B closes a silent black frame. A font-size in a relative unit resolved to 0px without failing: validate returned exit 0 and "Valid scenario", and the render did not contain the text. The contextual accessor existed and was tested — nothing called it.

Every component now has a test that paints and asserts non-transparent pixels, not merely that a number came back non-zero. Measurement and paint were checked together, since the geometry pass reads the measured box: a component measuring 0 while painting 32px would have moved the bug rather than fixed it.

What workstream C uncovered

Icon preloading could not work: the painter keyed its cache on the oversampled render size, the preloader on the target size — for a 40x40 icon one wrote 80x80 and the other read 40x40. The mechanism meant to avoid concurrent network requests added one per thread, after wasting a download.

The trap avoided: fixing the key without fixing the rasterization would have turned a useless preload into a harmful one — the painter would finally find a bitmap, at half resolution. Both sites now go through a single icon_cache_key; the drift is no longer expressible.

Verification — a difference in rigour worth stating

Unlike the previous chantier, the workstreams were not verified in isolation on their own branches. The orchestration derailed: I gave each agent its own CARGO_TARGET_DIR to avoid cargo lock contention, which multiplied a Skia-dependent build by five. The machine hit a load average of 75, the agents detached their compilations and then stalled waiting on them, and none delivered a report.

I took the verification back myself, sequentially, on the tree where all five coexist. Three defects fell out that no agent would have seen without compiling:

  • a cmd_render call site left at 9 arguments after the signature grew to 10 — the tree did not compile;
  • a redundant trim_start() before split_whitespace(), rejected by clippy -D warnings;
  • fmt was dirty in all five workstreams.

Final state:

The tests cover the traps named in the briefs: overdamped spring settling, undamped spring bounded, ffmpeg argument position asserted without depending on the machine running CI, and the two icon cache keys coinciding.

Found along the way, not fixed

rustmotion skills install run inside the rustmotion repository itself duplicates CLAUDE.md: the merge does not detect that the target is the source. Niche, but a developer testing the command in place will hit it. Worth a separate issue if it earns one.

…ered

`rustmotion skills install` is the only channel through which a generating
agent receives the rules. `SKILL_FILES` embedded 30 of the 47 on disk.

The 17 absentees were not a random sample. They included `geometry-safety.md`,
`world-view.md` and `audio-reactive.md` — the three CLAUDE.md cites by name —
and `html-css-mental-model.md`, which PR #156 had just corrected because it
taught JSON forms that delete the component from the video. That file had
never been in the list, before or after the fix: the content was repaired and
the pipe was not.

The list is now generated by a build script that walks the skills tree, so
"every rule is embedded" holds by construction rather than by memory. Adding
17 entries by hand would have left the next rule just as invisible.

Two details the generation had to preserve: the table is still ordered with
SKILL.md first, and `show()` no longer depends on that — it locates SKILL.md
by path, where it previously indexed position 0.

Verified by running the installed binary into an empty directory: 49 files
written, 47 of them rules. A test now compares what is embedded against the
directory and fails naming any file that drifts out.
…eroing it

A `font-size` in a relative unit resolved to 0px without failing. `validate`
returned exit 0 and "Valid scenario" on a `text` with `"font-size": "2rem"`,
warning only that it was "resolving to 0px" — and the render produced a frame
with no text in it.

The contextual accessor already existed and was tested; nothing called it.
Every component reached for `font_size_px_or`, the context-free one.

The resolution pattern was already written and proven in `text.rs` for
`letter-spacing` and `line-height`: build a length context locally from the
frame dimensions. This extends it to `font-size` itself and rolls it out
across the components that render text, through one shared
`intrinsic::font_size_ctx` rather than fifteen copies — copy-paste of a
derived value is what produced several of the defects this chantier is
fixing.

`PaintCtx` is deliberately untouched: threading the context through it would
have rippled into all 57 painters.

Each component has a test that paints and asserts non-transparent pixels, not
merely that a number came back non-zero. Measurement and paint were checked
together, since the geometry validator reads the measured box: a component
that measured 0 while painting 32px would have moved the bug rather than
fixed it.

Known limit, unchanged: `em` and `%` on `font-size` depend on the parent's
resolved size, and the cascade still copies the parent's raw unresolved
`Length`. `rem`, `vw` and `vh` are correct now; `em` and `%` remain
approximate.
…ible

Three defects that all end the same way: a hole in the video and no diagnostic.

- Icon preloading could never work. The painter keyed its cache on the
  oversampled render size, the preloader on the target size — for a 40x40
  icon one wrote `80x80` and the other read `40x40`, so the keys could not
  collide at any size. The mechanism meant to stop N render threads hitting
  the network in parallel added a wasted download and rasterization, then
  let every thread fetch anyway. The preloader also rasterized without the
  oversample, so fixing the key alone would have turned a useless preload
  into a harmful one: the painter would finally find a bitmap, at half the
  resolution. Both sites now go through one `icon_cache_key`, which applies
  the oversample and builds the key — the drift is no longer expressible.
- Icons had no disk cache and failed silently at every level. There is now a
  cache on disk, following the pattern `google_fonts.rs` already uses in this
  crate, and an unresolvable icon fails the preload instead of leaving a gap.
- Video frame extraction failed silently too: a missing or failing ffmpeg
  produced an entirely empty video with no warning. PR #151 had already
  hardened the *audio* extraction of embedded videos in this same crate,
  with an availability probe and a warning emitted once; that discipline is
  now extended to the frame path, which had been left behind.

Warnings added here are deduplicated: `prepare_scene` runs per frame, so an
unguarded one would print over a thousand times on a 1200-frame render.
…lable

`ffmpeg_args` knew only software encoders. On machines that have
VideoToolbox or NVENC, the encode step was leaving an order of magnitude on
the table.

`--hardware-acceleration` now probes `ffmpeg -encoders` for what the machine
actually offers rather than inferring it from the compile target: a macOS
box without a usable VideoToolbox exists, and so does an ffmpeg built
without NVENC. When acceleration is asked for and unavailable, it says so
and continues in software — it neither aborts nor switches silently.

CRF is meaningless on most hardware encoders, which think in bitrate, so
`check_crf` now warns explicitly when `--crf` is passed alongside an
acceleration that ignores it, instead of letting the setting look honoured.

PR #143 had extracted `ffmpeg_args` into a pure function after an argument
ordering bug put an input after the output options. That property is
preserved and is why this was cheap: the probe (machine-dependent) and the
argument construction (pure) are separate, and the tests assert the presence
*and position* of the hardware arguments without needing a machine that has
one — CI does not.

`cmd_render` grew a parameter, so `cmd_batch`'s call site passes `false`:
`batch` has no flag of its own, and wiring one is a separate change.
… settles

A spring's duration used to be an emergent consequence of `damping`,
`stiffness` and `mass`. An author who wanted "this spring, finishing in
0.8s" had to guess at three coupled numbers, and nothing exposed the instant
a given spring actually comes to rest — the one figure you need to time the
rest of a scene against it.

`SpringConfig` now takes an optional `duration` and `rest_threshold`. When
`duration` is set, time is rescaled linearly so the settle point lands
exactly there. The spring's shape is preserved by construction — the solver
is called with a scaled `t`, not with altered physics — and a test asserts
it: sampled at matching fractions of each one's own settle time, the natural
and pinned curves agree to 1e-9, with identical oscillation counts and
overshoot amplitudes. Without that test nothing would distinguish a real
remap from a crude shortening that flattens the spring.

The settle search handles the regimes that break naive implementations: an
overdamped spring never reaches its target exactly, which is what
`rest_threshold` is for; a very lightly damped one can take arbitrarily
long, so the search is bounded and the bound is tested rather than left to
spin.

`rustmotion info` now reports each spring's rest time, so a scene can be
timed against it instead of by trial and error, and `validate` rejects a
non-positive `duration` or a negative `rest_threshold` alongside the checks
PR #158 already added.
@LeadcodeDev LeadcodeDev added the enhancement New feature or request label Aug 10, 2026
@LeadcodeDev LeadcodeDev self-assigned this Aug 10, 2026
@LeadcodeDev LeadcodeDev changed the title chantier: écarts Remotion — la vague à effort S (5 lots) chantier: Remotion gaps — the S-effort wave (5 workstreams) Aug 10, 2026
@LeadcodeDev
LeadcodeDev merged commit 327e571 into main Aug 10, 2026
3 checks passed
@LeadcodeDev
LeadcodeDev deleted the chantier/remotion-gaps-s branch August 10, 2026 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant