Skip to content

fix(validate,info): line bounding boxes, rendered duration, and four doc claims - #391

Merged
LeadcodeDev merged 1 commit into
mainfrom
fix/duration-and-docs
Sep 27, 2026
Merged

LeadcodeDev merged 1 commit into
mainfrom
fix/duration-and-docs

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Closes #369. Closes #372 (partly — see below). Closes #370. Refs #388.

#369 — a line's bounding box ignored its endpoints

line, arrow and connector paint at their own x1/y1/x2/y2 inside a canvas already translated to the node's layout origin, but the validator reused bbox_of(layout): right size, wrong origin. So an overflow was reported in the wrong place, and a real one with a negative x1 passed silently.

The canvas-translation behaviour was confirmed empirically before trusting the issue's formula — a line at x=300,y=200,x1=100,y1=100 puts ink at (400,300), i.e. x + x1, so the endpoint offset genuinely adds to the layout origin rather than replacing it.

Red proof:

bbox must be anchored at x1/y1 (960, 100), not at the node's own x/y (0, 0):
  left:  (0.0, 0.0, 1.0, 1180.0)
  right: (958.0, 98.0, 4.0, 1184.0)

a line whose x1 pokes past x=0 must be reported even though its own box (x=0) does not: []

Against the release binary, the issue's JSON now reports [958, 98] -> [962, 1282] instead of [0, 0] -> [1, 1180].

#372 — info re-derived the duration instead of asking the encoder

validate already used build_frame_tasks(...).len() as ground truth. info was the last place summing scene.duration, so it ignored v1 transition overlap and v2 at placement entirely. It now calls the same function render and validate do.

repro before after
two 1.0 s scenes, 0.6 s transition (v1) 60 frames 42 frames / 1.4 s
at: 0 and at: 1.0 over 2.0 s scenes (v2) 120 frames 90 frames / 3.0 s

Part 2 of #372 is not fixed here, deliberately. A scene duration landing on a half frame adds one — two 1.05 s scenes render 64 frames, not 63 — because build_frame_tasks rounds each scene independently and the error accumulates. That is in encode/video/tasks.rs, outside this workstream's ownership. info now faithfully reports what render will produce, so the two no longer disagree with each other; both still disagree with what rounding the cumulative timeline once would give. I am keeping #372 open for that half rather than closing it — see the note below.

#370 — four documentation claims, each verified against the binary

The brief was to establish which side was wrong for each, and to report rather than document a bug as intended behaviour. All four turned out to be the doc drifting from working behaviour — no engine bug hiding behind any of them.

Claim Verdict Evidence
div defaults to border-radius: 12 doc wrong corner pixel reads the background colour — sharp. Every decoration painter does .unwrap_or([0.0; 4])
scale_x/scale_y are animatable doc wrong unknown animation property 'scale_x': expected one of … scale, scale.x, scale.y; scale.x validates
layout.padding takes an object doc wrong invalid type: map, expected f32. SceneLayout.padding is Option<f32>; CssStyle.padding is Option<Edges> — the doc conflated two different fields
commands take a positional path doc wrong error: unexpected argument found; --help shows --file <FILE> required, no positional

The scale_x asymmetry is real but pre-existing: translate_x/translate_y are accepted in underscore form and only scale differs. Left alone — nothing is broken, the naming is just inconsistent, and schema/video.rs was out of scope.

Verification

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace (1594) all clean. Comment count still 0.

Written comment-free, per the codebase-wide rule from #345.

… info matches the encoder's frame count; four stale SKILL.md claims

#369: the geometry validator's viewport-overflow check reused the
component's layout box for line/arrow/connector, whose painters draw
straight from x1/y1/x2/y2 (or from/to) inside a canvas already
translated to that box's origin. The reported bbox dropped the
endpoint offset entirely, so a line's bbox was anchored at the node's
own (usually 0,0) x/y with a size of just |x2-x1| by |y2-y1| — wrong
on both axes, and silent on a real overflow whenever an endpoint went
negative. component_bbox now derives the box from the endpoints
themselves (plus half the stroke width, and the arrow-head padding
already used for their intrinsic size), anchored at the layout
origin the canvas is actually translated to.

#372: `info` summed raw scene.duration and independently rounded each
scene to frames, so it ignored legacy-v1 transition overlap and v2
`at` placement entirely, and could diverge from the video `render`
actually produces. `validate` already treats build_frame_tasks(...).len()
as ground truth (see validate.rs's announced_duration) — `info` now
calls the same function instead of re-deriving the number, so it can no
longer drift from what render/validate report.

Note for whoever owns encode/video/tasks.rs: build_frame_tasks itself
still rounds each scene's duration to frames independently (round(31.5)
per scene rather than round(cumulative) once), so two 1.05s scenes at
30fps render 64 frames instead of 63 — this is now what `info` reports
too (matching the encoder), but the encoder's own rounding is still the
root cause the issue's part 2 asks to fix, and tasks.rs is outside this
branch's owned files.

#370: verified each of the four claims against the built binary rather
than trusting the issue. All four were doc bugs, not engine bugs:
- div's default border-radius renders sharp (0), not the documented
  12.0 — paint_pass's decoration painter unwraps to [0.0; 4].
- `scale_x`/`scale_y` are rejected by the keyframe validator; the
  schema's KNOWN_MOTION_PROPERTIES only accepts `scale.x`/`scale.y`
  (translate_x/translate_y are fine as-is, only scale differs).
- `layout.padding` (SceneLayout) is `Option<f32>` and rejects the
  `{top,right,bottom,left}` object form that `style.padding` (CssStyle,
  a different type) accepts; the doc's nearby "f32 or obj" table was
  for style.padding but read as if it also covered layout.padding.
- `render`/`info` only accept `-f/--file`; the CLI struct in cli/mod.rs
  (not owned here) has no positional path argument, so the doc's
  `rustmotion render scenario.json ...` examples now all use `-f`.

Tests: geometry::tests::line_bbox_honours_x1_y1_not_just_the_node_position,
geometry::tests::line_with_negative_x1_that_pokes_off_the_left_edge_is_caught,
info::duration_tests::a_v1_transition_shortens_the_rendered_total_the_way_the_encoder_sees_it,
info::duration_tests::v2_at_placement_reports_the_overlapped_total_not_the_sum_of_durations.
@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 27, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 27, 2026
@LeadcodeDev
LeadcodeDev merged commit a31ef2c into main Sep 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

1 participant