the golden sweep gains two hand-framed spots and stops faking green
- Two new golden-capture spots framed by a human eye rather than a lane list: the Goldshire inn common room (the interior everyone actually stands in) and a Felwood vista. The sweep also captures the world-map field now. - An honest-tooling fix: a scenario that failed to produce its image used to vanish from the diff and the sweep still read green. A missing shot is now its own loud failure — a clean sweep means every frame was actually compared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,12 +140,22 @@ fn main() -> Result<()> {
|
||||
}
|
||||
"diff-dir" => {
|
||||
let [da, db] = two(rest, "diff-dir")?;
|
||||
let worst = diff_dir(
|
||||
let DirDiff { worst, unpaired } = diff_dir(
|
||||
Path::new(da),
|
||||
Path::new(db),
|
||||
opts.out.as_deref(),
|
||||
opts.amplify,
|
||||
)?;
|
||||
// Under `--fail` this is a gate, and a shot that never landed must sink it — see the
|
||||
// note in `diff_dir` (decision 0743).
|
||||
if opts.fail.is_some() && !unpaired.is_empty() {
|
||||
bail!(
|
||||
"{} unpaired image(s) — a shot is missing from one side, so this comparison is \
|
||||
incomplete: {}",
|
||||
unpaired.len(),
|
||||
unpaired.join(", ")
|
||||
);
|
||||
}
|
||||
if let Some((name, m)) = worst {
|
||||
if over_fail(&m, opts.fail) {
|
||||
bail!(
|
||||
@@ -679,13 +689,16 @@ fn diff_one(a: &Path, b: &Path, out: Option<&Path>, amplify: u32) -> Result<Metr
|
||||
}
|
||||
|
||||
/// Compare every `*.png` present in both dirs (by file name). Returns the worst (highest-MAE) result.
|
||||
fn diff_dir(
|
||||
da: &Path,
|
||||
db: &Path,
|
||||
out: Option<&Path>,
|
||||
amplify: u32,
|
||||
) -> Result<Option<(String, Metrics)>> {
|
||||
let names: BTreeSet<String> = pngs(da)?.intersection(&pngs(db)?).cloned().collect();
|
||||
/// What one `diff-dir` run found: the worst pair by MAE, and every image present on only ONE side.
|
||||
struct DirDiff {
|
||||
worst: Option<(String, Metrics)>,
|
||||
/// Files with no counterpart, so not diffed at all — see the note in [`diff_dir`].
|
||||
unpaired: Vec<String>,
|
||||
}
|
||||
|
||||
fn diff_dir(da: &Path, db: &Path, out: Option<&Path>, amplify: u32) -> Result<DirDiff> {
|
||||
let (a, b) = (pngs(da)?, pngs(db)?);
|
||||
let names: BTreeSet<String> = a.intersection(&b).cloned().collect();
|
||||
if names.is_empty() {
|
||||
bail!(
|
||||
"no common *.png files between {} and {}",
|
||||
@@ -693,6 +706,19 @@ fn diff_dir(
|
||||
db.display()
|
||||
);
|
||||
}
|
||||
// UNPAIRED shots are ANNOUNCED, never skipped in silence. Pairing on the intersection alone is
|
||||
// how a sweep reports all-green with a shot missing: on 2026-07-28 one `water-night` capture
|
||||
// exited 0 without writing its PNG, and `selfcheck` diffed the other eight and passed clean. A
|
||||
// gate that quietly narrows its own scope is worse than no gate. Reported always; fatal to the
|
||||
// CALLER when `--fail` is in force, i.e. whenever this is being used as a gate (decision 0743).
|
||||
let unpaired: Vec<String> = a
|
||||
.difference(&b)
|
||||
.map(|n| format!("{}/{n}", da.display()))
|
||||
.chain(b.difference(&a).map(|n| format!("{}/{n}", db.display())))
|
||||
.collect();
|
||||
for u in &unpaired {
|
||||
eprintln!("UNPAIRED (no counterpart — NOT diffed): {u}");
|
||||
}
|
||||
if let Some(out) = out {
|
||||
std::fs::create_dir_all(out).ok();
|
||||
}
|
||||
@@ -708,7 +734,7 @@ fn diff_dir(
|
||||
if let Some((name, m)) = &worst {
|
||||
println!("worst: {name} (MAE {:.3})", m.mae);
|
||||
}
|
||||
Ok(worst)
|
||||
Ok(DirDiff { worst, unpaired })
|
||||
}
|
||||
|
||||
fn pngs(dir: &Path) -> Result<BTreeSet<String>> {
|
||||
|
||||
@@ -376,7 +376,8 @@ impl Plugin for CapturePlugin {
|
||||
.init_resource::<FxViewState>();
|
||||
Scenario {
|
||||
name: "fxview",
|
||||
eye: GROUND_EYE, // overridden per frame by the orbit in `pin_scene`
|
||||
map: scenarios::MAP_AZEROTH, // the fixture spawns over the Northshire slope
|
||||
eye: GROUND_EYE, // overridden per frame by the orbit in `pin_scene`
|
||||
look: FXVIEW_POS,
|
||||
minute: 720,
|
||||
ui: None,
|
||||
@@ -418,6 +419,7 @@ impl Plugin for CapturePlugin {
|
||||
.init_resource::<FxViewState>();
|
||||
Scenario {
|
||||
name: "waterfx",
|
||||
map: scenarios::MAP_AZEROTH, // the synthetic lattice sits over the Northshire slope
|
||||
eye,
|
||||
look: center,
|
||||
minute: 720,
|
||||
@@ -459,6 +461,12 @@ impl Plugin for CapturePlugin {
|
||||
let d = 500.0_f32;
|
||||
Scenario {
|
||||
name: "vista",
|
||||
// The arbitrary-viewpoint instrument goes anywhere, so its map is a knob: a
|
||||
// horizon report from Kalimdor is `WOW_MAP=1` (a `Map.dbc` id).
|
||||
map: std::env::var("WOW_MAP")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(scenarios::MAP_AZEROTH),
|
||||
eye,
|
||||
look: [
|
||||
eye[0] + d * pitch.cos() * face.cos(),
|
||||
@@ -487,6 +495,14 @@ impl Plugin for CapturePlugin {
|
||||
);
|
||||
std::process::exit(2);
|
||||
};
|
||||
// Seed the continent the scenario names, BEFORE `world_map::load_world_map` reads it at
|
||||
// `Startup` — that is the single place `CurrentMap` is set for a server-less run, and the
|
||||
// terrain/WDL streamers and per-map lighting all key off it. Raw WoW coords repeat on every
|
||||
// continent, so a scenario that could not say which map it meant would stream the wrong
|
||||
// world's tiles and photograph a void (Felwood's tile `33_24` exists in Azeroth, empty).
|
||||
// Written back for `vista` too, which is where the value came from — a harmless no-op that
|
||||
// keeps one path for "which map is this run on" (decision 0743).
|
||||
std::env::set_var("WOW_MAP", scenario.map.to_string());
|
||||
let out = std::env::var("WOW_CAPTURE_OUT")
|
||||
.unwrap_or_else(|_| format!("target/visual/{}.png", scenario.name));
|
||||
if let Some(parent) = Path::new(&out).parent() {
|
||||
@@ -850,8 +866,23 @@ fn drive_capture(
|
||||
}
|
||||
Phase::Done(n) => {
|
||||
if n + 1 >= EXIT_GRACE_FRAMES {
|
||||
info!("capture: saved {}, exiting", ctx.out);
|
||||
exit.write(AppExit::Success);
|
||||
// Exit on what is ON DISK, not on having reached this phase. The save is async and
|
||||
// the `Saving` phase gives up after `SAVE_TIMEOUT_FRAMES` so a missed `Capturing`
|
||||
// marker can't hang the harness — but "gave up" used to exit Success anyway, so a
|
||||
// capture that wrote nothing reported success and the sweep carried on around the
|
||||
// hole (2026-07-28: one `water-night` run left no PNG, and `selfcheck` passed on
|
||||
// the remaining eight). A missing file is a FAILED capture and says so, so
|
||||
// `scripts/visual.sh`'s `set -e` stops the sweep at it (decision 0743).
|
||||
if Path::new(&ctx.out).is_file() {
|
||||
info!("capture: saved {}, exiting", ctx.out);
|
||||
exit.write(AppExit::Success);
|
||||
} else {
|
||||
error!(
|
||||
"capture: FAILED — no file at {} after the save window; exiting nonzero",
|
||||
ctx.out
|
||||
);
|
||||
exit.write(AppExit::error());
|
||||
}
|
||||
Phase::Done(n)
|
||||
} else {
|
||||
Phase::Done(n + 1)
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct Scenario {
|
||||
pub(super) name: &'static str,
|
||||
/// `Map.dbc` id the eye/look coords belong to. Raw WoW coords repeat on every continent — the
|
||||
/// Felwood spot's tile (`33_24`) exists in Azeroth too, empty — so a scenario that could not
|
||||
/// name its map silently photographed the wrong world. The harness seeds
|
||||
/// [`crate::world_map::CurrentMap`] from this before streaming starts (decision 0743).
|
||||
pub(super) map: u32,
|
||||
/// Camera eye, raw WoW coords `(x, y, z)`.
|
||||
pub(super) eye: [f32; 3],
|
||||
/// Camera look-at target, raw WoW coords.
|
||||
@@ -110,11 +115,16 @@ pub(super) const SKY_LOOK: [f32; 3] = [-8740.0, 80.0, 168.0]; // up + out: horiz
|
||||
// Abbey, one of the few buildings immune to it. Baselines must cover ordinary buildings too.
|
||||
pub(super) const HOUSE_EYE: [f32; 3] = [-9439.1, 71.2, 68.0];
|
||||
|
||||
/// THE golden baseline: three spots FRAMED BY THE DIRECTOR in the live client ([`/shot`], the
|
||||
/// `Map.dbc` ids the golden spots stand on.
|
||||
pub(super) const MAP_AZEROTH: u32 = 0;
|
||||
pub(super) const MAP_KALIMDOR: u32 = 1;
|
||||
|
||||
/// THE golden baseline: five spots FRAMED BY THE DIRECTOR in the live client ([`/shot`], the
|
||||
/// numbers below verbatim from their `~/.benilla/shots.txt`) — the Northshire overlook toward the
|
||||
/// Abbey, a Stormwind canal view, and Elwynn water — each at noon and at night.
|
||||
/// Abbey, a Stormwind canal view, Elwynn water, the Lion's Pride Inn common room, and a Felwood
|
||||
/// hollow — each at noon and at night, except the inn (which the clock barely reaches; see below).
|
||||
///
|
||||
/// Six shots of maximal mutual difference. The old set was thirty, and the director judged the
|
||||
/// Nine shots of maximal mutual difference. The old set was thirty, and the director judged the
|
||||
/// design bad: low-value repetition (four compass looks at one farmhouse, five frames from one
|
||||
/// Northshire camera, sixteen UI windows over the same backdrop), each one a real window popping
|
||||
/// open on their screen while they worked. The law is **a few spots chosen with the director, times
|
||||
@@ -122,9 +132,17 @@ pub(super) const HOUSE_EYE: [f32; 3] = [-9439.1, 71.2, 68.0];
|
||||
/// blessed sweep. (Decision 0632; ported from the sibling `substrate` fork's `notes/0025`, where the
|
||||
/// redesign was made and where it caught a real determinism bug — see `liquid::animate_liquid` — on
|
||||
/// its first run.)
|
||||
///
|
||||
/// The first six were all EXTERIOR, all Azeroth, all Elwynn/Stormwind. The two spots added in
|
||||
/// decision 0743 are the two things that set of six could not photograph at all: the inside of a
|
||||
/// building (portal culling, the INT vertex bake, MOCV self-illum, MOLT point pools, props) and a
|
||||
/// second continent (a different tileset, fog palette, liquid type and horizon). Both were framed
|
||||
/// by the director after the agent's own framings of the same two subjects were judged bad — which
|
||||
/// is 0632's rule working exactly as intended.
|
||||
pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
Scenario {
|
||||
name: "overlook-noon",
|
||||
map: MAP_AZEROTH,
|
||||
eye: OVERLOOK_EYE,
|
||||
look: OVERLOOK_LOOK,
|
||||
minute: 720,
|
||||
@@ -132,6 +150,7 @@ pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "overlook-night",
|
||||
map: MAP_AZEROTH,
|
||||
eye: OVERLOOK_EYE,
|
||||
look: OVERLOOK_LOOK,
|
||||
minute: 0,
|
||||
@@ -139,6 +158,7 @@ pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "canal-noon",
|
||||
map: MAP_AZEROTH,
|
||||
eye: CANAL_EYE,
|
||||
look: CANAL_LOOK,
|
||||
minute: 720,
|
||||
@@ -146,6 +166,7 @@ pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "canal-night",
|
||||
map: MAP_AZEROTH,
|
||||
eye: CANAL_EYE,
|
||||
look: CANAL_LOOK,
|
||||
minute: 0,
|
||||
@@ -153,6 +174,7 @@ pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "water-noon",
|
||||
map: MAP_AZEROTH,
|
||||
eye: WATER_EYE,
|
||||
look: WATER_LOOK,
|
||||
minute: 720,
|
||||
@@ -160,11 +182,41 @@ pub(super) const SCENARIOS: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "water-night",
|
||||
map: MAP_AZEROTH,
|
||||
eye: WATER_EYE,
|
||||
look: WATER_LOOK,
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "inn-noon",
|
||||
map: MAP_AZEROTH,
|
||||
eye: INN_EYE,
|
||||
look: INN_LOOK,
|
||||
minute: 720,
|
||||
ui: None,
|
||||
},
|
||||
// No `inn-night`: MEASURED at MAE 0.198 against `inn-noon`, 0.82 % of pixels — the room is lit
|
||||
// by its hearth and candles, and the clock barely reaches it (the outdoor spots' own noon/night
|
||||
// pairs differ by MAE 38-83 across ~100 % of the frame). A second window for 0.8 % of one shot
|
||||
// is the low-value repetition 0632 exists to prevent, and noon is the richer of the two anyway:
|
||||
// it has the daylight window AND the fire. Re-add it the day the interior light path earns it.
|
||||
Scenario {
|
||||
name: "felwood-noon",
|
||||
map: MAP_KALIMDOR,
|
||||
eye: FELWOOD_EYE,
|
||||
look: FELWOOD_LOOK,
|
||||
minute: 720,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "felwood-night",
|
||||
map: MAP_KALIMDOR,
|
||||
eye: FELWOOD_EYE,
|
||||
look: FELWOOD_LOOK,
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
];
|
||||
|
||||
/// Director shot 1 — the Northshire overlook, north-east of the Abbey looking at it: terrain,
|
||||
@@ -178,17 +230,44 @@ pub(super) const CANAL_EYE: [f32; 3] = [-8871.7, 724.6, 110.7];
|
||||
pub(super) const CANAL_LOOK: [f32; 3] = [-8836.7, 760.3, 112.3];
|
||||
|
||||
/// Director shot 3 — Elwynn river, south-east of Northshire: open water dominant, shoreline blend,
|
||||
/// murloc camp, fog. Chosen by the director over the Goldshire inn interior, which stays in
|
||||
/// [`ON_DEMAND`] as the interior-path fixture.
|
||||
/// murloc camp, fog.
|
||||
pub(super) const WATER_EYE: [f32; 3] = [-9527.0, -310.6, 70.8];
|
||||
pub(super) const WATER_LOOK: [f32; 3] = [-9499.4, -351.3, 61.4];
|
||||
|
||||
/// Director shot 4 (decision 0743) — INSIDE the Lion's Pride Inn at Goldshire, standing in the
|
||||
/// common room: the hearth (the MOCV-alpha self-illum bake), a daylight window, the lit chandelier,
|
||||
/// the stair run, floor boards and ceiling beams, and a room full of props. The sweep's only
|
||||
/// interior, and the only shot that exercises portal culling, the INT bake, the MOLT point pools
|
||||
/// and WMO props at all — the surface the ledger's dungeon reports live on, and the one B101's
|
||||
/// prop-cull regression (which hid the Blackrock lava falls) crossed unseen.
|
||||
///
|
||||
/// The agent's own interior framing (`inn-interior`, a tight crop of the kitchen hearth) was judged
|
||||
/// badly framed and stays in [`ON_DEMAND`]; this is the director's, and it frames the ROOM.
|
||||
///
|
||||
/// NB the camera must stand over a FLOOR FACE: over a floorless pocket the portal cull's down-ray
|
||||
/// reads "outside" and faithfully culls the containing group, which vanishes the room.
|
||||
pub(super) const INN_EYE: [f32; 3] = [-9471.4, 39.4, 59.9];
|
||||
pub(super) const INN_LOOK: [f32; 3] = [-9458.8, -7.5, 48.2];
|
||||
|
||||
/// Director shot 5 (decision 0743) — a Felwood hollow on **Kalimdor** (`MAP_KALIMDOR`): the
|
||||
/// corrupted forest floor's root mat, a stand of emissive `felwoodmushroom` doodads, a pool of
|
||||
/// green sludge (a liquid type no other golden shot contains), the vast trunks behind, and the
|
||||
/// zone's sick-green fog and light palette.
|
||||
///
|
||||
/// The sweep's only shot outside Azeroth and outside the Elwynn/Stormwind palette — until this one
|
||||
/// landed, a fog, tileset, WDL-horizon or per-map lighting regression anywhere else in the world
|
||||
/// had nowhere to show up. It is also what forced [`Scenario::map`]: this spot's ADT tile
|
||||
/// (`33_24`) exists in Azeroth too, empty, so on the old table it would have photographed a void.
|
||||
pub(super) const FELWOOD_EYE: [f32; 3] = [4060.9, -944.3, 256.8];
|
||||
pub(super) const FELWOOD_LOOK: [f32; 3] = [4014.0, -954.4, 242.9];
|
||||
|
||||
/// Every remaining named viewpoint — the UI look-pass fixtures, the sun/moon/sky regression
|
||||
/// fixtures, the house-compass and street scenes. Capturable by name (`WOW_CAPTURE=<name>`) for
|
||||
/// debugging and look passes, but NOT part of the blessed baseline sweep.
|
||||
pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
Scenario {
|
||||
name: "house-north",
|
||||
map: MAP_AZEROTH,
|
||||
eye: HOUSE_EYE,
|
||||
look: [-9389.1, 71.2, 58.0],
|
||||
minute: 720,
|
||||
@@ -196,6 +275,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "house-south",
|
||||
map: MAP_AZEROTH,
|
||||
eye: HOUSE_EYE,
|
||||
look: [-9489.1, 71.2, 58.0],
|
||||
minute: 720,
|
||||
@@ -203,6 +283,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "house-west",
|
||||
map: MAP_AZEROTH,
|
||||
eye: HOUSE_EYE,
|
||||
look: [-9439.1, 121.2, 58.0],
|
||||
minute: 720,
|
||||
@@ -210,6 +291,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "house-east",
|
||||
map: MAP_AZEROTH,
|
||||
eye: HOUSE_EYE,
|
||||
look: [-9439.1, 21.2, 58.0],
|
||||
minute: 720,
|
||||
@@ -225,6 +307,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// client does the same — the audit's "faithful-cull residue"), which vanishes the room.
|
||||
Scenario {
|
||||
name: "inn-interior",
|
||||
map: MAP_AZEROTH,
|
||||
eye: [-9463.3, 4.4, 58.8],
|
||||
look: [-9462.1, -5.6, 58.5],
|
||||
minute: 720,
|
||||
@@ -235,6 +318,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// stays as the warm-light/fog fixture.
|
||||
Scenario {
|
||||
name: "northshire-dusk",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 1170, // 19:30 — warm dusk light + fog
|
||||
@@ -242,6 +326,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "northshire-sky-noon",
|
||||
map: MAP_AZEROTH,
|
||||
eye: SKY_EYE,
|
||||
look: SKY_LOOK,
|
||||
minute: 720, // day sky-dome gradient + fog horizon
|
||||
@@ -249,6 +334,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "northshire-sky-dusk",
|
||||
map: MAP_AZEROTH,
|
||||
eye: SKY_EYE,
|
||||
look: SKY_LOOK,
|
||||
minute: 1170, // dusk dome warp + low sun + stars emerging
|
||||
@@ -262,6 +348,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// was cut along a giant faceted circle).
|
||||
Scenario {
|
||||
name: "northshire-sun-flare",
|
||||
map: MAP_AZEROTH,
|
||||
eye: SKY_EYE,
|
||||
look: [-8797.0, 23.0, 264.0], // eye + 300·(elev 30°, az 45°) — the sun's spot at 17:30
|
||||
minute: 1050,
|
||||
@@ -276,6 +363,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// unit tests (`flare_ray_*`) and the sun fixtures.
|
||||
Scenario {
|
||||
name: "northshire-moonrise",
|
||||
map: MAP_AZEROTH,
|
||||
eye: SKY_EYE,
|
||||
look: [-8775.0, 45.0, 190.0], // eye + 300·(elev 15°, az 45°) — the moon's spot at 22:44
|
||||
minute: 1364,
|
||||
@@ -287,6 +375,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// the moonrise fixture above proves its absence early, this one its presence at depth of night.
|
||||
Scenario {
|
||||
name: "northshire-moon-halo",
|
||||
map: MAP_AZEROTH,
|
||||
eye: SKY_EYE,
|
||||
look: [-8858.0, -38.0, 358.0], // eye + 300·(elev 55°, az 45°) — the moon's spot at 00:00
|
||||
minute: 0,
|
||||
@@ -298,6 +387,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// load is resident here; Northshire scenes never exercise that scale.
|
||||
Scenario {
|
||||
name: "stormwind",
|
||||
map: MAP_AZEROTH,
|
||||
eye: [-8833.38, 628.63, 96.0],
|
||||
look: [-8809.1, 672.3, 94.0],
|
||||
minute: 720,
|
||||
@@ -309,6 +399,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// director look pass, so "looks like shit" gets caught in-loop, not on the director's screen.
|
||||
Scenario {
|
||||
name: "ui-merchant",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -316,6 +407,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-gossip",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -323,6 +415,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-bank",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -330,6 +423,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-quest",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -337,6 +431,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-questgreeting",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -344,6 +439,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-questlog",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -351,6 +447,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-loot",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -358,6 +455,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
},
|
||||
Scenario {
|
||||
name: "ui-bag",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -367,6 +465,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-tooltip`.
|
||||
Scenario {
|
||||
name: "ui-tooltip",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -376,6 +475,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// seeded hostile wolf. Run with `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-tooltip-world`.
|
||||
Scenario {
|
||||
name: "ui-tooltip-world",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -385,6 +485,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-char`.
|
||||
Scenario {
|
||||
name: "ui-char",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -395,6 +496,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-unitframes`.
|
||||
Scenario {
|
||||
name: "ui-unitframes",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -407,6 +509,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-actionbar`.
|
||||
Scenario {
|
||||
name: "ui-actionbar",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -418,6 +521,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE=vplates` (main.rs sizes this window 1024×768 — the 1:1 gx window).
|
||||
Scenario {
|
||||
name: "vplates",
|
||||
map: MAP_AZEROTH,
|
||||
eye: [-8956.5, -137.5, 85.6],
|
||||
look: [-8949.95, -132.49, 84.8],
|
||||
minute: 720,
|
||||
@@ -428,6 +532,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-worldmap`.
|
||||
Scenario {
|
||||
name: "ui-worldmap",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -436,6 +541,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// The spellbook over a seeded mage book. Run with `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-spellbook`.
|
||||
Scenario {
|
||||
name: "ui-spellbook",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -445,6 +551,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-chatedit`.
|
||||
Scenario {
|
||||
name: "ui-chatedit",
|
||||
map: MAP_AZEROTH,
|
||||
eye: GROUND_EYE,
|
||||
look: GROUND_LOOK,
|
||||
minute: 720,
|
||||
@@ -455,6 +562,7 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// out in the water. Run with `WOW_CAPTURE=name-water`.
|
||||
Scenario {
|
||||
name: "name-water",
|
||||
map: MAP_AZEROTH,
|
||||
eye: WATER_EYE,
|
||||
look: WATER_LOOK,
|
||||
minute: 720,
|
||||
|
||||
@@ -212,12 +212,12 @@ use world_map::WorldMapPlugin;
|
||||
/// logs in — so the player sits in the middle of the block instead of Stormwind's edge.
|
||||
const SPAWN_XY: (f32, f32) = (-8949.95, -132.49);
|
||||
|
||||
fn main() {
|
||||
fn main() -> AppExit {
|
||||
// `WOW_CAPTURE=list` just prints the harness scenario names (the source of truth `scripts/visual.sh`
|
||||
// reads) and exits before any window/asset setup.
|
||||
if std::env::var("WOW_CAPTURE").as_deref() == Ok("list") {
|
||||
capture::print_scenario_names();
|
||||
return;
|
||||
return AppExit::Success;
|
||||
}
|
||||
|
||||
let mut app = App::new();
|
||||
@@ -779,5 +779,8 @@ fn main() {
|
||||
app.add_plugins(perf::FpsJournalPlugin);
|
||||
}
|
||||
|
||||
app.run();
|
||||
// Return the app's own exit status instead of dropping it: a failed capture writes
|
||||
// `AppExit::error()` (see `capture::drive_capture`), and discarding it made the process exit 0
|
||||
// with no PNG on disk — which is how a sweep carried on around a missing shot (decision 0743).
|
||||
app.run()
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ fn load_world_map(mut commands: Commands, world_assets: Option<Res<WorldAssets>>
|
||||
// `player::wire_in` overwrites this from the server's world-verify the moment it says
|
||||
// where we are. Without it every headless capture is stuck in Eastern Kingdoms, and a
|
||||
// horizon report from anywhere else cannot be reproduced.
|
||||
//
|
||||
// A named capture scenario carries its own map (`capture::scenarios::Scenario::map`)
|
||||
// and writes it here before this runs, so the golden sweep's Kalimdor shots need no env
|
||||
// at all — see `capture::CapturePlugin::build` (decision 0743).
|
||||
let map = std::env::var("WOW_MAP")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
|
||||
Reference in New Issue
Block a user