Skip to content

feat(illustration): suppress the runaway camera, pin a reproducible seed - #27

Merged
upgradedev merged 1 commit into
mainfrom
feat/illustration-camera-control
Aug 2, 2026
Merged

feat(illustration): suppress the runaway camera, pin a reproducible seed#27
upgradedev merged 1 commit into
mainfrom
feat/illustration-camera-control

Conversation

@upgradedev

Copy link
Copy Markdown
Owner

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-i2v exposes no motion or camera parameter (verified against the SDK's own param_allowlist, not assumed), so this uses the levers that actually exist:

  • negative_prompt names 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.
  • seed derived 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_ratio deliberately 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.

…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).
@upgradedev
upgradedev merged commit 5c093f8 into main Aug 2, 2026
11 checks passed
@upgradedev
upgradedev deleted the feat/illustration-camera-control branch August 2, 2026 04:59
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>
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