Files
ARCADIA-Studio/harness/experiments/resize-port-policy-20260912/falsifiers.patch
T

411 lines
15 KiB
Diff

diff --git a/crates/oa-core/tests/experimental_port_policy_falsifiers.rs b/crates/oa-core/tests/experimental_port_policy_falsifiers.rs
new file mode 100644
index 0000000..ef6d197
--- /dev/null
+++ b/crates/oa-core/tests/experimental_port_policy_falsifiers.rs
@@ -0,0 +1,404 @@
+//! Prospective acceptance falsifiers, intentionally expected to fail at 7e25b3d.
+//! Do not change the policy or relax these criteria before preserving first-run
+//! receipts. Source-derived predictions are diagnostic text, not test outcomes.
+use oa_core::{projection::*, routing::*, *};
+use std::{fs::OpenOptions, io::Write, path::PathBuf};
+
+const POLICIES: [ExperimentalPortPolicy; 3] = [
+ ExperimentalPortPolicy::Current,
+ ExperimentalPortPolicy::SameSidesAdaptSpacing,
+ ExperimentalPortPolicy::CapacityAware,
+];
+const SIDES: [PortAnchorSide; 4] = [
+ PortAnchorSide::Left,
+ PortAnchorSide::Right,
+ PortAnchorSide::Top,
+ PortAnchorSide::Bottom,
+];
+fn id(n: u128) -> Id {
+ Id::from_u128(0x956a2026_0912_4000_8000_000000000000 + n)
+}
+fn pos(x: f32, y: f32) -> Position {
+ Position { x, y }
+}
+fn size(width: f32, height: f32) -> Size {
+ Size { width, height }
+}
+fn element(p: &mut Project, n: u128, kind: Kind, name: &str) -> Id {
+ let mut e = p.make_element(kind, Layer::Sa, name, None).unwrap();
+ e.id = id(n);
+ p.elements.insert(e.id, e);
+ id(n)
+}
+fn standard(p: &mut Project, n: u128, owner: Id, name: &str) -> Id {
+ let port = make_port(
+ p,
+ id(n),
+ owner,
+ name,
+ PortContract::Component {
+ kind: ComponentPortKind::Standard,
+ orientation: ComponentPortOrientation::Unset,
+ },
+ )
+ .unwrap();
+ p.elements.insert(port.id, port);
+ id(n)
+}
+fn place(p: &mut Project, d: Id, n: u128, e: Id, at: Position, dimensions: Size) -> Id {
+ let generated = p.place(d, e, at);
+ let nodes = &mut p.diagrams.get_mut(&d).unwrap().nodes;
+ let mut node = nodes.remove(&generated).unwrap();
+ node.id = id(n);
+ node.size = dimensions;
+ nodes.insert(node.id, node);
+ id(n)
+}
+struct Fixture {
+ p: Project,
+ d: Id,
+ owner: Id,
+ ports: Vec<Id>,
+}
+fn right_peer(count: usize, height: f32, obstacle: bool) -> Fixture {
+ let mut p = Project::empty();
+ p.id = id(1);
+ let a = element(&mut p, 10, Kind::System, "Owner");
+ let b = element(&mut p, 11, Kind::SystemActor, "Far right peer");
+ let pb = standard(&mut p, 20, b, "Peer interface");
+ let mut ports = Vec::new();
+ for i in 0..count {
+ let pa = standard(&mut p, 100 + i as u128, a, &format!("Interface {i}"));
+ let generated = p.relate(
+ RelationKind::BoundaryExchange,
+ &format!("Exchange {i}"),
+ pa,
+ pb,
+ );
+ let mut relation = p.relations.remove(&generated).unwrap();
+ relation.id = id(1000 + i as u128);
+ p.relations.insert(relation.id, relation);
+ ports.push(pa);
+ }
+ let generated = p.ensure_diagram(Layer::Sa);
+ let mut diagram = p.diagrams.remove(&generated).unwrap();
+ diagram.id = id(30);
+ let d = diagram.id;
+ p.diagrams.insert(d, diagram);
+ let owner = place(&mut p, d, 40, a, pos(0., 0.), size(224., height));
+ place(&mut p, d, 41, b, pos(1908., 336.), size(184., 132.));
+ if obstacle {
+ let c = element(&mut p, 12, Kind::SystemActor, "Unrelated top obstacle");
+ place(&mut p, d, 42, c, pos(0., -150.), size(224., 132.));
+ }
+ validate_candidate(&p).unwrap();
+ assert_eq!(language::parse(&language::format(&p)).unwrap(), p);
+ Fixture { p, d, owner, ports }
+}
+fn project(f: &Fixture, policy: ExperimentalPortPolicy) -> ProjectedView {
+ let view = project_diagram_with_experimental_port_policy(&f.p, &f.p.diagrams[&f.d], policy);
+ assert!(view.nodes.iter().all(|node| node.visual_parent.is_none()));
+ assert_eq!(view.edges.len(), f.ports.len());
+ view
+}
+fn owner<'a>(f: &Fixture, view: &'a ProjectedView) -> &'a ProjectedNode {
+ view.nodes.iter().find(|n| n.id == f.owner).unwrap()
+}
+fn port(view: &ProjectedView, id: Id) -> &ProjectedPort {
+ view.ports.iter().find(|p| p.element == id).unwrap()
+}
+fn side_counts(f: &Fixture, view: &ProjectedView) -> [usize; 4] {
+ SIDES.map(|side| {
+ view.ports
+ .iter()
+ .filter(|p| p.owner == f.owner && p.side == side)
+ .count()
+ })
+}
+fn gap(f: &Fixture, view: &ProjectedView) -> f32 {
+ let mut minimum = f32::INFINITY;
+ for side in SIDES {
+ let mut offsets: Vec<_> = view
+ .ports
+ .iter()
+ .filter(|p| p.owner == f.owner && p.side == side)
+ .map(|p| match side {
+ PortAnchorSide::Left | PortAnchorSide::Right => p.position.y,
+ _ => p.position.x,
+ })
+ .collect();
+ offsets.sort_by(f32::total_cmp);
+ for pair in offsets.windows(2) {
+ minimum = minimum.min(pair[1] - pair[0]);
+ }
+ }
+ minimum
+}
+// Independent segment/slab test, not the router's private Rect::crosses helper.
+// All fixture nodes are unrelated flat obstacles, so ancestor/header exceptions
+// do not apply. A node's own outward stub is allowed through its own clearance.
+fn stub_hits(port: &ProjectedPort, obstacle: &ProjectedNode) -> bool {
+ if port.owner == obstacle.id {
+ return false;
+ }
+ let normal = port.side.normal();
+ let mut lo = 0.0_f32;
+ let mut hi = 1.0_f32;
+ for (start, delta, min, max) in [
+ (
+ port.position.x,
+ normal.x * PORT_STUB,
+ obstacle.position.x - CLEARANCE,
+ obstacle.position.x + obstacle.size.width + CLEARANCE,
+ ),
+ (
+ port.position.y,
+ normal.y * PORT_STUB,
+ obstacle.position.y - CLEARANCE,
+ obstacle.position.y + obstacle.size.height + CLEARANCE,
+ ),
+ ] {
+ if delta == 0. {
+ if start <= min || start >= max {
+ return false;
+ }
+ } else {
+ let a = (min - start) / delta;
+ let b = (max - start) / delta;
+ lo = lo.max(a.min(b));
+ hi = hi.min(a.max(b));
+ }
+ }
+ lo < hi
+}
+fn blocked_stubs(view: &ProjectedView) -> Vec<(Id, Id)> {
+ view.ports
+ .iter()
+ .flat_map(|p| {
+ view.nodes
+ .iter()
+ .filter(move |n| stub_hits(p, n))
+ .map(move |n| (p.element, n.id))
+ })
+ .collect()
+}
+fn measure(f: &Fixture, view: &ProjectedView, label: &str) -> RoutedView {
+ let routed = route_diagram(&f.p, &f.p.diagrams[&f.d], view);
+ let mut length = 0.0_f32;
+ let mut bends = 0;
+ for edge in &view.edges {
+ let route = &routed.edges[&edge.id];
+ assert_eq!(
+ route.points.first(),
+ Some(&port(view, edge.source_port.unwrap()).position)
+ );
+ assert_eq!(
+ route.points.last(),
+ Some(&port(view, edge.target_port.unwrap()).position)
+ );
+ for pair in route.points.windows(2) {
+ assert!(pair[0].x == pair[1].x || pair[0].y == pair[1].y);
+ length += (pair[0].x - pair[1].x).abs() + (pair[0].y - pair[1].y).abs();
+ }
+ for triple in route.points.windows(3) {
+ let a = pos(triple[1].x - triple[0].x, triple[1].y - triple[0].y);
+ let b = pos(triple[2].x - triple[1].x, triple[2].y - triple[1].y);
+ if a.x * b.y != a.y * b.x {
+ bends += 1;
+ }
+ }
+ println!(
+ "{label} edge={} points={:?} warnings={:?}",
+ edge.id, route.points, route.warnings
+ );
+ }
+ let warnings: usize = routed.edges.values().map(|e| e.warnings.len()).sum();
+ println!(
+ "{label} bounds={:?}/{:?} LRTB={:?} gap={} warnings={warnings} length={length} bends={bends} blocked_stubs={:?}",
+ owner(f, view).position,
+ owner(f, view).size,
+ side_counts(f, view),
+ gap(f, view),
+ blocked_stubs(view)
+ );
+ for id in &f.ports {
+ println!(
+ "{label} port={id} side={:?} position={:?}",
+ port(view, *id).side,
+ port(view, *id).position
+ );
+ }
+ // A caller may retain exact model, projection and route geometry. Never
+ // overwrite a previous run. Ordinary export still chooses Current.
+ if let Some(directory) = std::env::var_os("OA_PORT_POLICY_RECEIPTS") {
+ let directory = PathBuf::from(directory);
+ std::fs::create_dir_all(&directory).unwrap();
+ for (suffix, content) in [
+ ("arcadia", language::format(&f.p)),
+ (
+ "projection.json",
+ serde_json::to_string_pretty(view).unwrap(),
+ ),
+ ("routing.txt", format!("{routed:#?}")),
+ ] {
+ let mut file = OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(directory.join(format!("{label}.{suffix}")))
+ .unwrap();
+ file.write_all(content.as_bytes()).unwrap();
+ }
+ }
+ routed
+}
+
+#[test]
+fn a_point_one_height_increase_does_not_needlessly_flip_twelve_of_twenty_ports() {
+ let mut f = right_peer(20, 259.9, false);
+ let mut violations = Vec::new();
+ for policy in POLICIES {
+ let mut views = Vec::new();
+ for height in [259.9, 260., 260.1] {
+ f.p.diagrams
+ .get_mut(&f.d)
+ .unwrap()
+ .nodes
+ .get_mut(&f.owner)
+ .unwrap()
+ .size
+ .height = height;
+ let before = f.p.clone();
+ let view = project(&f, policy);
+ measure(&f, &view, &format!("threshold-{policy:?}-{height}"));
+ assert_eq!(f.p, before, "Projection/routing must not author geometry");
+ if policy != ExperimentalPortPolicy::Current {
+ assert_eq!(owner(&f, &view).size, size(224., height));
+ }
+ views.push(view);
+ }
+ let flips: Vec<_> = views
+ .windows(2)
+ .map(|pair| {
+ f.ports
+ .iter()
+ .filter(|id| port(&pair[0], **id).side != port(&pair[1], **id).side)
+ .count()
+ })
+ .collect();
+ println!(
+ "threshold {policy:?}: adjacent side changes={flips:?}; source prediction capacity=[12,0], controls=[0,0]"
+ );
+ if policy == ExperimentalPortPolicy::CapacityAware {
+ // The prior assignment is still feasible at the larger rectangle.
+ // Nominal independent capacities: floor((edge - 36) / 28) + 1.
+ let new = owner(&f, &views[1]);
+ let capacities = [
+ new.size.height,
+ new.size.height,
+ new.size.width,
+ new.size.width,
+ ]
+ .map(|length| ((length - 36.) / 28.).floor() as usize + 1);
+ assert!(
+ side_counts(&f, &views[0])
+ .into_iter()
+ .zip(capacities)
+ .all(|(n, c)| n <= c)
+ );
+ assert!(gap(&f, &views[0]) >= 27.99);
+ // A single relocation can use the new preferred-side slot. This
+ // fixture-specific bound does not prohibit justified side changes.
+ if flips.iter().any(|n| *n > 1) {
+ violations.push(format!(
+ "CapacityAware changes {flips:?} sides while zero/one move suffices"
+ ));
+ }
+ } else if flips != [0, 0] {
+ violations.push(format!(
+ "{policy:?} control unexpectedly changes {flips:?} sides"
+ ));
+ }
+ }
+ assert!(
+ violations.is_empty(),
+ "Prospective resize-stability criterion failed: {violations:?}"
+ );
+}
+
+#[test]
+fn capacity_spillover_does_not_add_a_blocked_mandatory_stub_above_the_owner() {
+ let f = right_peer(8, 160., true);
+ let original = f.p.clone();
+ let mut violations = Vec::new();
+ for policy in POLICIES {
+ let view = project(&f, policy);
+ let routed = measure(&f, &view, &format!("obstacle-{policy:?}"));
+ let actual = owner(&f, &view);
+ assert_eq!(actual.position, pos(0., 0.));
+ assert_eq!(
+ actual.size,
+ size(
+ 224.,
+ if policy == ExperimentalPortPolicy::Current {
+ 232.
+ } else {
+ 160.
+ }
+ )
+ );
+ let obstacle = view.nodes.iter().find(|n| n.id == id(42)).unwrap();
+ assert_eq!(
+ (obstacle.position, obstacle.size),
+ (pos(0., -150.), size(224., 132.))
+ );
+ let blocked = blocked_stubs(&view);
+ let no_clear: Vec<_> = routed
+ .edges
+ .values()
+ .filter(|e| e.warnings.contains(&RouteWarning::NoClearPath))
+ .map(|e| e.id)
+ .collect();
+ println!(
+ "obstacle {policy:?}: independently blocked={blocked:?}, NoClearPath={no_clear:?}; source prediction capacity Top=(112,0), stub=(112,-18), obstacle inflation y=[-162,-6]"
+ );
+ if policy == ExperimentalPortPolicy::CapacityAware {
+ // Record whether each physically blocked source also reports the
+ // predicted route warning; do not use warning text as geometry proof.
+ for (port_id, obstacle_id) in &blocked {
+ let edges: Vec<_> = view
+ .edges
+ .iter()
+ .filter(|e| e.source_port == Some(*port_id) || e.target_port == Some(*port_id))
+ .map(|e| e.id)
+ .collect();
+ println!(
+ "blocked port={port_id} obstacle={obstacle_id} affected_edges={edges:?} all_NoClearPath={}",
+ edges.iter().all(|e| no_clear.contains(e))
+ );
+ }
+ if !blocked.is_empty() || routed.edges.values().any(|e| !e.warnings.is_empty()) {
+ violations.push(format!(
+ "CapacityAware introduces blocked stubs {blocked:?} or routed warnings"
+ ));
+ }
+ } else {
+ if !f
+ .ports
+ .iter()
+ .all(|p| port(&view, *p).side == PortAnchorSide::Right)
+ {
+ violations.push(format!("{policy:?} control no longer keeps Right exits"));
+ }
+ if !blocked.is_empty() || routed.edges.values().any(|e| !e.warnings.is_empty()) {
+ violations.push(format!(
+ "{policy:?} control has blocked stubs {blocked:?} or routed warnings"
+ ));
+ }
+ }
+ assert_eq!(f.p, original);
+ }
+ assert!(
+ violations.is_empty(),
+ "Prospective exit-clearance criterion failed: {violations:?}"
+ );
+}