feat(illustration): suppress the runaway camera, pin a reproducible seed - #27
Merged
Merged
Conversation
…e seed A live render on 2026-07-30 obeyed the seeded geometry but read the prompt's 'gentle camera parallax and a slow push' as licence for a fly-through, pulling so far out that the vehicles were specks on an empty road for roughly half the clip. The positive prompt already asked for restraint, so asking more politely was not the fix. pixverse-v6-i2v exposes no motion or camera parameter -- verified against the SDK's own param_allowlist (image, seed, aspect_ratio, duration, video, negative_prompt, video_url, resolution, quality, image_url, prompt, cfg_scale) rather than assumed -- so the levers that do exist are used instead: - negative_prompt names the measured failure (zoom out, wide shot, distant view, empty road) plus the no-invented-text rule the burned-in disclosure depends on. Diffusion models suppress what a negative prompt names far more reliably than they honour a positive instruction not to do it. - seed is derived from the seed raster's own bytes, which are a pure function of the factual Timeline, so the same sealed case asks for the same clip every time: reproducible from the record, not from a stored random number. aspect_ratio is deliberately not set: the provider already returns 4:3 output matching the 960x720 seed, so pinning it would add risk without benefit. The recording test double now also captures params (additive; no existing assertion changed).
9 tasks
upgradedev
added a commit
that referenced
this pull request
Aug 2, 2026
…ion (#28) * feat(illustration): compute the camera deterministically, raise clip resolution Two live renders proved pixverse-v6-i2v cannot be steered on camera motion or resolution by prompting alone (#26, #27): it exposes no motion/camera param at all (verified against the SDK's param_allowlist), and asking for "gentle parallax" with a negative_prompt naming "zoom out" still drifted into an unusable extreme close-up. So we stop asking the model to move the camera and compute the move ourselves from the case's own factual Timeline. - claimscene/camera.py (new): compute_camera_path derives a slow push toward the reconstructed contact point from Timeline geometry alone, sized and positioned so every impact participant's full footprint stays inside the frame for the whole clip; applied via an ffmpeg zoompan filter built from the case's own numbers, resolution-agnostic (targets ffmpeg's iw/ih, probed from the actual clip via ffprobe). Runs on the raw model output, before the disclosure caption is burned in, so the caption is never cropped or distorted; fails safe to the unmoved clip on any error, never failing the render. - report.py: the clip prompt now asks for a locked-off, static shot (no camera movement of any kind) instead of "gentle parallax"; the negative prompt now suppresses camera motion in both directions, not just the pull-out direction measured in #27. - pipeline.py: quality raised from 360p to 720p (the SDK performs no value validation for this field; 720p is the standard next tier in the SDK's own portable resolution vocabulary), aspect_ratio pinned to 4:3 to match the seed raster so the camera math's coordinate mapping holds. - provenance.py / readiness.py: seal + verify the camera move's own provenance (source, note, applied/error) alongside the existing watermark-burn honesty checks; success is never required, only that the outcome is honestly recorded. schematic.py's pixel-projection formula is extracted into a public world_to_pixel() (pure refactor, golden SVG unchanged) so camera.py reuses the exact same mapping the seed image was drawn with, instead of duplicating it. * fix(illustration): probe the real frame rate and skip a no-op push _probe_video_params now prefers ffprobe's avg_frame_rate (frames over duration -- the actual playback rate) over r_frame_rate (the lowest rate every timestamp divides into exactly, which only equals the real rate for constant-frame-rate content). A variable-frame-rate or odd-timestamp clip from a live provider could otherwise hand zoompan's fps= a stream timebase like 90000/1 and silently blow up the encode. Falls back to r_frame_rate when avg_frame_rate is ffprobe's own "unknown" sentinel. apply_camera_push now treats an end_zoom clamped all the way to 1.0 (containment needed the whole frame -- only reachable for a pathologically large or spread-out footprint) as a fail-safe no-op instead of re-encoding a filter that would visually do nothing and sealing a truthful-sounding but empty applied=True. Also corrects a comment in report.py that described the camera push's containment padding as "letterboxed framing" -- it is a crop-and-scale in the seed image's own coordinate space, not letterboxing. Adds a real-ffmpeg test proving the push is visibly meaningful by the end of the clip (not just "bytes changed", which a re-encode alone would already satisfy), calibrated against that same clip's own first-frame re-encode noise floor. --------- Co-authored-by: Efthimios Fousekis <tf@upgrade.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The schematic seeding (#26) fixed the geometry, but a live render showed the camera pulling out until the vehicles were specks on an empty road for about half the clip.
Why not just reword the prompt
The positive prompt already asked for restraint. It was ignored.
pixverse-v6-i2vexposes no motion or camera parameter (verified against the SDK's ownparam_allowlist, not assumed), so this uses the levers that actually exist:negative_promptnames the measured failure mode (zoom out, wide shot, distant view, empty road) plus the no-invented-text rule the burned-in disclosure depends on. Negative prompts suppress named behaviour far more reliably than positive instructions forbid it.seedderived from the seed raster's bytes, which are a pure function of the factual Timeline. The same sealed case now asks for the same clip every time: reproducible from the record rather than a stored random number.aspect_ratiodeliberately left unset: output is already 4:3 matching the seed, so pinning it adds risk without benefit.Verification
Deterministic tests only prove what we send. Whether the camera actually settles can only be judged from pixels, so I will merge, redeploy and run a real live render before calling this done.