mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
* Use GPT Image 2.5 Flare for image generation Add explicit model selection and cover generation/edit request provenance. Compare 24 comp samples with Image 2 and Sunburst, documenting efficiency gains and the photo-preservation tradeoff. Prepared with AI assistance under maintainer instructions. * Validate native image transparency for asset production Add repeatable cutout, glass, and reference-plate experiments with alpha measurements and compositing evidence. Record fidelity limits and the remaining asset-producer integration work. Prepared with AI assistance under the maintainer instructions. * Add native transparent PNG asset generation Wire background options through generation and edits, preserve alpha, and update asset production to use supported commands with visual cutout checks. Prepared with AI assistance under the maintainer's instructions. * Fix native alpha review findings Validate PNG paths for all background modes, score sparse and partial alpha over the sampled ground, and correct launcher and plate workflow guidance. Prepared with AI assistance under the maintainer's instructions. * Fix launcher paths in comp workflow commands Quote existing launcher filenames for the target shell while retaining multiword command prefixes. Cover relative and absolute paths and actual shell argument parsing. Prepared with AI assistance under the maintainer's instructions. * Remove evaluation artifacts and docs from image PR Keep the PR focused on runtime behavior, source skill instructions, and regression coverage. Preserve evaluation material locally. Prepared with AI assistance under the maintainer's instructions.
123 lines
5.1 KiB
Rust
123 lines
5.1 KiB
Rust
//! Parity checks for the deterministic (non-browser) comp-verb logic. The
|
|
//! expected numbers were produced by the original JS scripts (run from git
|
|
//! history) against the same `crates/comp/tests/fixtures` PNGs and confirmed
|
|
//! byte-identical to this port's output during the Node-free swap.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use impeccable_comp::png_io;
|
|
use impeccable_comp::raster::Image;
|
|
use impeccable_comp_verbs::comp_diff;
|
|
use impeccable_comp_verbs::comp_spec;
|
|
use serde_json::{json, Value};
|
|
|
|
fn fixtures() -> PathBuf {
|
|
// comp-verbs shares the comp crate's fixtures.
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../comp/tests/fixtures")
|
|
}
|
|
|
|
#[test]
|
|
fn transparent_plate_prompt_preserves_paint_and_reference_spacing() {
|
|
let dir = std::env::temp_dir().join(format!("impeccable-alpha-prompt-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
std::fs::write(dir.join("spec.json"), json!({"regions": [{"id": "boat", "kind": "plate", "note": "White sails and three hull holes"}]}).to_string()).unwrap();
|
|
let (mut io, output) = impeccable_common::Io::captured("", dir.clone(), Default::default());
|
|
let args = [
|
|
"--spec",
|
|
"spec.json",
|
|
"--plate-prompt",
|
|
"boat",
|
|
"--background",
|
|
"transparent",
|
|
]
|
|
.map(String::from);
|
|
assert_eq!(comp_spec::run(&args, &mut io), 0);
|
|
let prompt = String::from_utf8(output.stdout.borrow().clone()).unwrap();
|
|
std::fs::remove_dir_all(dir).unwrap();
|
|
assert!(prompt.contains("transparent alpha"), "{prompt}");
|
|
assert!(prompt.contains("white"));
|
|
assert!(prompt.contains("margins"));
|
|
assert!(!prompt.contains("edge to edge"));
|
|
}
|
|
|
|
fn load(name: &str) -> Image {
|
|
let buf = std::fs::read(fixtures().join(name)).unwrap();
|
|
png_io::decode_png(&buf).unwrap().image
|
|
}
|
|
|
|
#[test]
|
|
fn comp_diff_scores_match_js() {
|
|
// JS: comp-diff.mjs --comp comp.png --build build_flat.png (no spec).
|
|
let comp = load("comp.png");
|
|
let build = load("build_flat.png");
|
|
let res = comp_diff::compare(&comp, &build, None, "top", "", None);
|
|
let w = &res.whole;
|
|
assert_eq!(w.overall, 0.8374, "overall");
|
|
assert_eq!(w.structure, 0.9846, "structure");
|
|
assert_eq!(w.color, 0.8306, "color");
|
|
assert_eq!(w.detail, 0.5407, "detail");
|
|
assert_eq!(comp_diff::verdict_for(w, None), "match");
|
|
}
|
|
|
|
#[test]
|
|
fn grid_to_box_matches_js() {
|
|
// JS: gridToBox on a 10x10 grid.
|
|
assert_eq!(comp_spec::grid_to_box("E2:J4").unwrap(), (0.4, 0.2, 0.6, 0.3));
|
|
assert_eq!(comp_spec::grid_to_box("A0:J0").unwrap(), (0.0, 0.0, 1.0, 0.1));
|
|
assert_eq!(comp_spec::grid_to_box("a0:a0").unwrap(), (0.0, 0.0, 0.1, 0.1));
|
|
// reversed spans normalize the same way
|
|
assert_eq!(comp_spec::grid_to_box("J4:E2").unwrap(), (0.4, 0.2, 0.6, 0.3));
|
|
assert!(comp_spec::grid_to_box("Z9:A0").is_err());
|
|
assert!(comp_spec::grid_to_box("E2-J4").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn measure_regions_shape_matches_js() {
|
|
// A raster region gets a plate path + raster medium; a text region snaps
|
|
// to ink and is measured semantic. `allowUncovered` lets the busy comp pass.
|
|
let comp = load("comp.png");
|
|
let input: Value = json!({
|
|
"allowUncovered": true,
|
|
"regions": [
|
|
{ "id": "art", "kind": "plate", "grid": "E2:J4", "note": "an exploded illustration drawing" },
|
|
{ "id": "body", "kind": "text", "grid": "A5:D8", "note": "a paragraph of body text content" }
|
|
]
|
|
});
|
|
let spec = comp_spec::measure_regions(&comp, &input, "comp.png").unwrap();
|
|
let regions = spec.get("regions").and_then(Value::as_array).unwrap();
|
|
assert_eq!(regions.len(), 2);
|
|
let art = ®ions[0];
|
|
assert_eq!(art.get("medium").and_then(Value::as_str), Some("raster"));
|
|
assert_eq!(art.get("plate").and_then(Value::as_str), Some("assets/plates/art.png"));
|
|
let body = ®ions[1];
|
|
assert_eq!(body.get("medium").and_then(Value::as_str), Some("semantic"));
|
|
assert!(body.get("plate").unwrap().is_null());
|
|
// spec-level fields
|
|
assert_eq!(spec.get("tool").and_then(Value::as_str), Some("comp-spec"));
|
|
assert_eq!(spec.pointer("/compSize/width").and_then(Value::as_i64), Some(comp.width as i64));
|
|
}
|
|
|
|
#[test]
|
|
fn measure_regions_refuses_painted_note_under_code_kind() {
|
|
// JS: a text/control/chrome region whose note names painted material is
|
|
// refused at the spec unless codeDrawn is set.
|
|
let comp = load("comp.png");
|
|
let input: Value = json!({
|
|
"allowUncovered": true,
|
|
"regions": [ { "id": "x", "kind": "chrome", "grid": "A0:B1", "note": "an exploded diagram illustration" } ]
|
|
});
|
|
let err = comp_spec::measure_regions(&comp, &input, "comp.png").unwrap_err();
|
|
assert!(err.contains("describes painted material"), "got: {err}");
|
|
}
|
|
|
|
#[test]
|
|
fn measure_regions_refuses_oversized_code_region() {
|
|
let comp = load("comp.png");
|
|
let input: Value = json!({
|
|
"allowUncovered": true,
|
|
"regions": [ { "id": "col", "kind": "chrome", "grid": "A0:J9", "note": "a big column of things" } ]
|
|
});
|
|
let err = comp_spec::measure_regions(&comp, &input, "comp.png").unwrap_err();
|
|
assert!(err.contains("covers 100% of the comp") || err.contains("% of the comp"), "got: {err}");
|
|
}
|