Skip to content

feat(illustration): derive impact geometry from the factual layout in the prompt - #25

Merged
upgradedev merged 2 commits into
mainfrom
feat/illustration-geometry-fidelity
Jul 30, 2026
Merged

feat(illustration): derive impact geometry from the factual layout in the prompt#25
upgradedev merged 2 commits into
mainfrom
feat/illustration-geometry-fidelity

Conversation

@upgradedev

Copy link
Copy Markdown
Owner

Problem

A judge who reads a case and then watches the AI illustration clip will notice the illustration depicts a different accident from the one the data describes. Confirmed on a real live render (case live-forensic-retry-0bb32eeb, provider genblaze, degraded: false): the SceneGraph described a rear-end collision (red car approaching from behind, striking the stopped blue car's rear) but the render showed the two cars side by side, as if waiting next to each other at a light.

Root cause

_forensic_scene_description() in src/claimscene/report.py described each vehicle independently (colour, kind, approach, maneuver, speed, damage clock positions) and never stated how the vehicles are positioned relative to each other. A text-to-image/video model has no layout instruction and invents one.

Fix

layout.py's LayoutEngine already computes the real geometry deterministically: a metric world frame with TimedPose(x, y, heading_deg) per vehicle, time-scheduled so impact participants are in contact at impact_time_s. This is the same factual layer the schematic renderer draws from. This PR derives an explicit spatial-relationship sentence from those poses and adds it to both illustration prompts, so the prompt becomes a faithful rendering of the factual layer instead of an independent description the model has to invent a layout for.

For each impact participant after the first, relative to the first-listed one:

  • Position — the bearing to the other vehicle in the first vehicle's own frame (0/east convention, matches layout.py), bucketed into 8 compass-style directions ("directly ahead of" / "directly behind" / "to the left/right of" / the four intermediate combinations).
  • Orientation — the heading difference between the two vehicles, bucketed into "both facing the same way" / "facing each other head-on" / "meeting at right angles" / "meeting at an oblique angle", plus the standard idioms "nose to tail" / "nose to nose" for the two configurations they unambiguously fit.
  • Contact — the distance between each vehicle's own stated impact point (clock_point_world), which the LayoutEngine guarantees is near zero for real impact pairs (see test_layout.test_impact_points_touch_at_contact_frame), so this reliably reads "in contact at the point of impact"; computed rather than assumed, with an "almost touching" / omitted fallback for anything that doesn't run through the current engine.

Deterministic: pure arithmetic over the Timeline's already-rounded pose floats, no randomness. Built only from the constrained vocabulary plus the computed geometry, not from re-reading the enum text. Single-vehicle scenes, parked-only scenes, and scenes with no impacts all degrade gracefully to no relationship clause (proven by dedicated tests), never a crash.

Also added an explicit "no on-image text" instruction to both prompts: the model was inventing its own on-image captions that compete with the deterministic disclosure burned into the pixels after generation (watermark.py, PR #24). The old Overlay text: '{DISCLOSURE}' request in illustration_prompt was removed rather than kept, since it now directly contradicts the new no-text instruction and a live render already proved the model is free to ignore such a request anyway — the pixel burn-in is the real mechanism now.

Not touched

SceneGraph schema, extra="forbid", layout.py's geometry math, the schematic renderer's output, provenance.py, tests/security/, and watermark.py (including the DISCLOSURE/WATERMARK constants and the burn-in logic) are all untouched.

Tests

New tests in tests/unit/test_report.py: the derived spatial phrase for rear-end / two right-angle fixtures / oblique / a synthetic head-on fixture, each asserting the exact expected relational sentence built from the real LayoutEngine output; determinism (independently-rebuilt Timelines yield identical wording); the no-text instruction present in both prompts; the overlay-text line's removal; graceful handling of single-vehicle, parked-only, and no-impact scenes; a scene/timeline mismatch; a vehicle struck twice (dedup); and direct coverage of the two small pure helpers' remaining branches. report.py coverage: 98%; pipeline.py: 100%.

Existing tests updated (signature only, in tests/unit/test_report.py -- illustration_prompt/illustration_still_prompt now take timeline as well as scene): test_prompts_stay_in_the_moderation_safe_forensic_register, test_still_prompt_describes_vehicles_and_damage. One genuine wording update: test_illustration_prompt_deterministic_and_self_disclosing no longer asserts DISCLOSURE in a, since the overlay line that put it there is gone. tests/integration/test_pipeline.py needed zero changes -- it only inspects the sealed manifest's prompt substrings, all of which are preserved.

Full suite: 425 passed, 95.44% total coverage (gate is 90%). ruff clean.

Not merging

Per instructions, this PR is left open for manual review of the derived wording and a live-render check before merge.

Efthimios Fousekis added 2 commits July 30, 2026 12:16
… the prompt

The AI illustration could depict a different accident from the one the
data describes: the forensic-reconstruction prompt described each vehicle
independently and never stated how impact participants are positioned
relative to each other, so a rear-end collision could render as two cars
waiting side by side. Derive the real relationship (position, orientation,
contact) from the Timeline's computed poses at the impact frame, the same
factual layer the schematic is drawn from, and state it in plain language
in both illustration prompts. Also instruct the model to render no
on-image text, and drop the now-contradictory overlay-text request; the
disclosure is guaranteed by the pixel burn-in (watermark.py) instead.
Drop the over-broad bare "facing" absence check (could collide with
unrelated future prose) and assert the full expected relational sentence
in the duplicate-impact-entry test instead of an incidental substring
count.
@upgradedev
upgradedev merged commit 4c7f8d0 into main Jul 30, 2026
11 checks passed
@upgradedev
upgradedev deleted the feat/illustration-geometry-fidelity branch July 30, 2026 09: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