433 lines
23 KiB
Rust
433 lines
23 KiB
Rust
//! Isolated actual-core inverse-resize experiment; no production integration.
|
|
//! Author did not compile/run this. Root owns the matching-rlib reader lease.
|
|
//! Default runs bounded planner cases; --oracle adds exhaustive finite grid checks.
|
|
use oa_core::{projection::*, validation, view_options::ViewIndex, *};
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::time::Instant;
|
|
|
|
fn pos(x: f32, y: f32) -> Position { Position { x, y } }
|
|
fn size(width: f32, height: f32) -> Size { Size { width, height } }
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
struct Rect { l: f32, t: f32, r: f32, b: f32 }
|
|
impl Rect {
|
|
fn of(n: &ProjectedNode) -> Self {
|
|
Self { l: n.position.x, t: n.position.y,
|
|
r: n.position.x + n.size.width, b: n.position.y + n.size.height }
|
|
}
|
|
fn width(self) -> f32 { self.r - self.l }
|
|
fn height(self) -> f32 { self.b - self.t }
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum Handle { N, Ne, E, Se, S, Sw, W, Nw }
|
|
const HANDLES: [Handle; 8] = [Handle::N, Handle::Ne, Handle::E, Handle::Se,
|
|
Handle::S, Handle::Sw, Handle::W, Handle::Nw];
|
|
impl Handle {
|
|
fn west(self) -> bool { matches!(self, Self::W | Self::Nw | Self::Sw) }
|
|
fn east(self) -> bool { matches!(self, Self::E | Self::Ne | Self::Se) }
|
|
fn north(self) -> bool { matches!(self, Self::N | Self::Nw | Self::Ne) }
|
|
fn south(self) -> bool { matches!(self, Self::S | Self::Sw | Self::Se) }
|
|
fn horizontal(self) -> bool { self.west() || self.east() }
|
|
fn vertical(self) -> bool { self.north() || self.south() }
|
|
fn requested(self, base: Rect, dx: f32, dy: f32) -> Rect {
|
|
Rect { l: base.l + if self.west() { dx } else { 0. },
|
|
r: base.r + if self.east() { dx } else { 0. },
|
|
t: base.t + if self.north() { dy } else { 0. },
|
|
b: base.b + if self.south() { dy } else { 0. } }
|
|
}
|
|
fn growing_delta(self, amount: f32) -> (f32, f32) {
|
|
(if self.west() { -amount } else if self.east() { amount } else { 0. },
|
|
if self.north() { -amount } else if self.south() { amount } else { 0. })
|
|
}
|
|
fn fixed_equal(self, a: Rect, b: Rect) -> bool {
|
|
(self.west() || a.l == b.l) && (self.east() || a.r == b.r)
|
|
&& (self.north() || a.t == b.t) && (self.south() || a.b == b.b)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum Status { Exact, StaticMinimum, NoChange, Unresolved, Ineligible }
|
|
#[derive(Debug)]
|
|
struct ResultPlan {
|
|
status: Status, reason: &'static str, requested: Rect, target: Rect,
|
|
node: Option<NodePlacement>, projections: usize, corrections: usize,
|
|
}
|
|
|
|
struct Planner<'a> {
|
|
p: &'a Project, d: Id, n: Id, original: NodePlacement,
|
|
base: Rect, view: ProjectedView, index: ViewIndex,
|
|
// Conservative detection: every visible port makes the experiment dynamic.
|
|
// Production should use shared constraint facts instead of duplicating policy.
|
|
dynamic: bool, state_view: bool,
|
|
}
|
|
impl<'a> Planner<'a> {
|
|
fn new(p: &'a Project, d: Id, n: Id) -> Self {
|
|
let index = ViewIndex::new(p);
|
|
let view = project_diagram_with_index(p, &p.diagrams[&d], &index);
|
|
let base = Rect::of(view.nodes.iter().find(|v| v.id == n).unwrap());
|
|
Self { p, d, n, original: p.diagrams[&d].nodes[&n].clone(), base,
|
|
dynamic: !view.ports.is_empty(), state_view: p.diagrams[&d].projection.state_machine().is_some(),
|
|
view, index }
|
|
}
|
|
fn legal(n: &NodePlacement) -> bool {
|
|
[n.position.x, n.position.y, n.size.width, n.size.height].into_iter()
|
|
.all(|v| v.is_finite() && v.abs() <= MAX_COORDINATE)
|
|
&& n.size.width > 0. && n.size.height > 0.
|
|
}
|
|
fn project(&self, n: &NodePlacement, count: &mut usize) -> Option<ProjectedView> {
|
|
if !Self::legal(n) { return None; }
|
|
let mut diagram = self.p.diagrams[&self.d].clone();
|
|
diagram.nodes.insert(self.n, n.clone());
|
|
*count += 1;
|
|
let view = project_diagram_with_index(self.p, &diagram, &self.index);
|
|
view.nodes.iter().find(|v| v.id == self.n)?;
|
|
Some(view)
|
|
}
|
|
fn rect(&self, view: &ProjectedView) -> Rect {
|
|
Rect::of(view.nodes.iter().find(|v| v.id == self.n).unwrap())
|
|
}
|
|
|
|
fn target(&self, h: Handle, requested: Rect) -> (Rect, &'static str) {
|
|
let mut target = requested;
|
|
let mut reason = "exact requested edges";
|
|
let kind = self.p.elements[&self.original.element].kind();
|
|
let hard_width = if self.state_view {
|
|
if kind.is_stable_state() { 184. } else { 224. }
|
|
} else { 0. };
|
|
let hard_height = if self.state_view && !self.original.collapsed {
|
|
state_header_height(self.p, self.original.element) + 20.
|
|
} else { 0. };
|
|
let min_width = self.base.width().min(184.).max(hard_width);
|
|
let min_height = self.base.height().min(94.).max(hard_height);
|
|
if h.west() && target.l > self.base.r - min_width {
|
|
target.l = self.base.r - min_width; reason = "preferred/state width minimum";
|
|
}
|
|
if h.east() && target.r < self.base.l + min_width {
|
|
target.r = self.base.l + min_width; reason = "preferred/state width minimum";
|
|
}
|
|
if h.north() && target.t > self.base.b - min_height {
|
|
target.t = self.base.b - min_height; reason = "preferred/state height minimum";
|
|
}
|
|
if h.south() && target.b < self.base.t + min_height {
|
|
target.b = self.base.t + min_height; reason = "preferred/state height minimum";
|
|
}
|
|
// No geometry-dependent ports: direct child frames are fixed constraints.
|
|
// Boundary Entry/Exit symbols follow their State/Machine and are excluded.
|
|
// These constants intentionally mirror today's projector only in this probe.
|
|
if !self.dynamic {
|
|
let header = if self.state_view { state_header_height(self.p, self.original.element) } else { 64. };
|
|
for child in self.view.nodes.iter().filter(|v| v.visual_parent == Some(self.n)) {
|
|
if self.state_view && matches!(self.p.elements[&child.element].kind(), Kind::EntryPoint | Kind::ExitPoint) {
|
|
continue;
|
|
}
|
|
let c = Rect::of(child);
|
|
if h.west() && target.l > c.l - 24. { target.l = c.l - 24.; reason = "fixed content envelope"; }
|
|
if h.east() && target.r < c.r + 24. { target.r = c.r + 24.; reason = "fixed content envelope"; }
|
|
if h.north() && target.t > c.t - header { target.t = c.t - header; reason = "fixed content/header envelope"; }
|
|
if h.south() && target.b < c.b + 24. { target.b = c.b + 24.; reason = "fixed content/footer envelope"; }
|
|
}
|
|
}
|
|
(target, reason)
|
|
}
|
|
|
|
fn plan(&self, h: Handle, dx: f32, dy: f32) -> ResultPlan {
|
|
let requested = h.requested(self.base, dx, dy);
|
|
let base_result = |status, reason, target, node| ResultPlan {
|
|
status, reason, requested, target, node, projections: 0, corrections: 0 };
|
|
let kind = self.p.elements[&self.original.element].kind();
|
|
if (self.state_view && state_symbol_size(kind).is_some()) || (self.original.collapsed && h.vertical()) {
|
|
return base_result(Status::Ineligible, "fixed state symbol or folded vertical handle", self.base, None);
|
|
}
|
|
if !dx.is_finite() || !dy.is_finite() {
|
|
return base_result(Status::Ineligible, "non-finite pointer", self.base, None);
|
|
}
|
|
if requested == self.base {
|
|
return base_result(Status::NoChange, "exact original-record zero-motion fallback", self.base, Some(self.original.clone()));
|
|
}
|
|
let (target, reason) = self.target(h, requested);
|
|
if target == self.base {
|
|
return base_result(Status::StaticMinimum, reason, target, Some(self.original.clone()));
|
|
}
|
|
let status = if target == requested { Status::Exact } else { Status::StaticMinimum };
|
|
let x = axis_choices(self.original.position.x, self.original.size.width,
|
|
self.base.l, self.base.r, target.l, target.r, h.horizontal());
|
|
let y = if self.original.collapsed { vec![(self.original.position.y, self.original.size.height)] }
|
|
else { axis_choices(self.original.position.y, self.original.size.height,
|
|
self.base.t, self.base.b, target.t, target.b, h.vertical()) };
|
|
let mut projections = 0;
|
|
let mut corrections = 0;
|
|
let mut seeds = Vec::new();
|
|
let mut seen = BTreeSet::new();
|
|
// Prefer original inactive fields. Normalize only if a prior candidate fails.
|
|
for &(x, width) in &x {
|
|
for &(y, height) in &y {
|
|
let mut node = self.original.clone();
|
|
node.position = pos(x, y); node.size = size(width, height);
|
|
if !seen.insert(bits(&node)) { continue; }
|
|
if let Some(view) = self.project(&node, &mut projections) {
|
|
if self.rect(&view) == target {
|
|
return ResultPlan { status, reason, requested, target, node: Some(node), projections, corrections };
|
|
}
|
|
seeds.push((node, self.rect(&view)));
|
|
}
|
|
}
|
|
}
|
|
// Bounded inverse residual correction is experimental, not a complete solver.
|
|
// Never label its exhaustion or repeated candidates as a content minimum.
|
|
for (mut node, mut observed) in seeds {
|
|
for _ in 0..6 {
|
|
node.position.x += target.l - observed.l;
|
|
node.size.width += target.width() - observed.width();
|
|
if !self.original.collapsed {
|
|
node.position.y += target.t - observed.t;
|
|
node.size.height += target.height() - observed.height();
|
|
}
|
|
if !seen.insert(bits(&node)) { break; }
|
|
corrections += 1;
|
|
let Some(view) = self.project(&node, &mut projections) else { break; };
|
|
observed = self.rect(&view);
|
|
if observed == target {
|
|
return ResultPlan { status, reason, requested, target, node: Some(node), projections, corrections };
|
|
}
|
|
}
|
|
}
|
|
ResultPlan { status: Status::Unresolved,
|
|
reason: "target not realized by finite candidates/corrections; infeasibility not proven",
|
|
requested, target, node: None, projections, corrections }
|
|
}
|
|
}
|
|
|
|
fn bits(n: &NodePlacement) -> (u32, u32, u32, u32) {
|
|
(n.position.x.to_bits(), n.position.y.to_bits(), n.size.width.to_bits(), n.size.height.to_bits())
|
|
}
|
|
fn axis_choices(start: f32, length: f32, b0: f32, b1: f32, t0: f32, t1: f32, active: bool) -> Vec<(f32, f32)> {
|
|
let end = start + length;
|
|
let pairs = if active {
|
|
vec![(start + t0 - b0, end + t1 - b1),
|
|
(if t0 == b0 { start } else { t0 }, if t1 == b1 { end } else { t1 }),
|
|
(t0, t1), (start, t1), (t0, end), (start, end)]
|
|
} else { vec![(start, end), (t0, t1), (start, t1), (t0, end)] };
|
|
let mut result = Vec::new();
|
|
for (a, b) in pairs {
|
|
if a.is_finite() && b.is_finite() && b > a && !result.contains(&(a, b-a)) {
|
|
result.push((a, b-a));
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
struct Case { name: &'static str, p: Project, d: Id, n: Id }
|
|
fn valid(label: &str, p: &Project) {
|
|
let issues = validation::validate(p);
|
|
let errors: Vec<_> = issues.iter().filter(|i| i.severity == validation::Severity::Error).collect();
|
|
assert!(errors.is_empty(), "{label}: {errors:?}");
|
|
}
|
|
fn place(p: &mut Project, d: Id, e: Id, x: f32, y: f32, w: f32, h: f32) -> Id {
|
|
let id = p.place(d, e, pos(x,y));
|
|
p.diagrams.get_mut(&d).unwrap().nodes.get_mut(&id).unwrap().size = size(w,h);
|
|
id
|
|
}
|
|
fn leaf(name: &'static str, w: f32, h: f32) -> Case {
|
|
let mut p = Project::empty();
|
|
let e = p.add_element(Kind::Entity, Layer::Oa, "Frame", None).unwrap();
|
|
let d = p.ensure_diagram(Layer::Oa);
|
|
let n = place(&mut p, d, e, 37.25, 80.5, w, h);
|
|
Case { name, p, d, n }
|
|
}
|
|
fn container(roomy: bool, folded: bool, child_folded: bool) -> Case {
|
|
let mut p = Project::empty();
|
|
let e = p.add_element(Kind::Entity, Layer::Oa, "Performer", None).unwrap();
|
|
let w = p.add_element(Kind::Activity, Layer::Oa, "Whole work", None).unwrap();
|
|
let c = p.add_element(Kind::Activity, Layer::Oa, "Nested work", Some(w)).unwrap();
|
|
p.relate(RelationKind::Allocation, "Performs", e, c);
|
|
let d = p.ensure_diagram(Layer::Oa);
|
|
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Allocation;
|
|
let n = if roomy { place(&mut p,d,e,50.,20.,550.,550.) } else { place(&mut p,d,e,200.,200.,224.,480.) };
|
|
let nw = place(&mut p,d,w,124.,164.,300.,240.);
|
|
place(&mut p,d,c,170.,240.,184.,94.);
|
|
p.diagrams.get_mut(&d).unwrap().nodes.get_mut(&n).unwrap().collapsed = folded;
|
|
p.diagrams.get_mut(&d).unwrap().nodes.get_mut(&nw).unwrap().collapsed = child_folded;
|
|
Case { name: if folded { "folded container" } else if child_folded { "folded child container" }
|
|
else if roomy { "roomy allocated container" } else { "derived top-left container" }, p,d,n }
|
|
}
|
|
fn state_case(select: Kind) -> Case {
|
|
let mut p = Project::empty();
|
|
let owner = p.add_element(Kind::Entity,Layer::Oa,"Crew",None).unwrap();
|
|
let machine = p.add_element(Kind::StateMachine,Layer::Oa,"Machine",Some(owner)).unwrap();
|
|
let region = p.add_element(Kind::Region,Layer::Oa,"Region",Some(machine)).unwrap();
|
|
let state = p.add_element(Kind::State,Layer::Oa,"Operating",Some(region)).unwrap();
|
|
let inner = p.add_element(Kind::Region,Layer::Oa,"Inner region",Some(state)).unwrap();
|
|
let child = p.add_element(Kind::State,Layer::Oa,"Working",Some(inner)).unwrap();
|
|
let entry = p.add_element(Kind::EntryPoint,Layer::Oa,"Entry",Some(inner)).unwrap();
|
|
let exit = p.add_element(Kind::ExitPoint,Layer::Oa,"Exit",Some(inner)).unwrap();
|
|
if let Some(SharedBody::State { entry, .. }) = p.elements.get_mut(&state).unwrap().shared_mut() {
|
|
*entry = "Keep entry annotation visible".into();
|
|
}
|
|
let internal = p.relate(RelationKind::StateTransition,"Internal",state,state);
|
|
if let RelationBody::StateTransition { transition_kind, .. } = &mut p.relations.get_mut(&internal).unwrap().body {
|
|
*transition_kind = TransitionKind::Internal;
|
|
}
|
|
let incoming = p.relate(RelationKind::StateTransition,"Enter work",entry,child);
|
|
if let RelationBody::StateTransition { transition_kind, .. } = &mut p.relations.get_mut(&incoming).unwrap().body {
|
|
*transition_kind = TransitionKind::Local;
|
|
}
|
|
p.relate(RelationKind::StateTransition,"Leave work",child,exit);
|
|
let d = Id::new_v4();
|
|
p.diagrams.insert(d, Diagram { id:d, alias:"state_view".into(),name:"State view".into(),description:String::new(),
|
|
layer:Layer::Oa, projection:ProjectionKind::StateMachine { machine },view_options:Default::default(),
|
|
nodes:BTreeMap::new(),routes:vec![] });
|
|
let nm = place(&mut p,d,machine,0.,0.,1100.,900.);
|
|
place(&mut p,d,region,24.,64.,1000.,800.);
|
|
let ns = place(&mut p,d,state,180.,180.,440.,500.);
|
|
let nr = place(&mut p,d,inner,204.,360.,380.,300.);
|
|
place(&mut p,d,child,310.,460.,224.,94.);
|
|
let ne = place(&mut p,d,entry,-200.,390.,224.,94.);
|
|
place(&mut p,d,exit,2000.,550.,224.,94.);
|
|
let n = match select { Kind::StateMachine => nm, Kind::Region => nr, Kind::EntryPoint => ne, _ => ns };
|
|
Case { name: match select { Kind::StateMachine => "state machine",Kind::Region=>"state region",Kind::EntryPoint=>"fixed symbol",_=>"state actions and content" },p,d,n }
|
|
}
|
|
fn standard(p: &mut Project, owner: Id, name: &str) -> Id {
|
|
let id = Id::new_v4();
|
|
let port = make_port(p,id,owner,name,PortContract::Component {
|
|
kind:ComponentPortKind::Standard,orientation:ComponentPortOrientation::Unset }).unwrap();
|
|
p.elements.insert(id,port); id
|
|
}
|
|
fn ports(manual: bool) -> Case {
|
|
let mut p = Project::empty();
|
|
let a = p.add_element(Kind::System,Layer::Sa,"Owner",None).unwrap();
|
|
let b = p.add_element(Kind::SystemActor,Layer::Sa,"Above",None).unwrap();
|
|
let c = p.add_element(Kind::SystemActor,Layer::Sa,"Below",None).unwrap();
|
|
let pb = standard(&mut p,b,"Above interface");
|
|
let pc = standard(&mut p,c,"Below interface");
|
|
let mut list = Vec::new();
|
|
for i in 0..8 {
|
|
let pa = standard(&mut p,a,&format!("Owner interface {i}"));
|
|
p.relate(RelationKind::BoundaryExchange,&format!("Above exchange {i}"),pa,pb);
|
|
p.relate(RelationKind::BoundaryExchange,&format!("Below exchange {i}"),pa,pc);
|
|
list.push(pa);
|
|
}
|
|
let d = p.ensure_diagram(Layer::Sa);
|
|
let n = place(&mut p,d,a,0.,0.,224.,160.);
|
|
place(&mut p,d,b,38.,-996.,184.,132.);
|
|
place(&mut p,d,c,38.,1004.,184.,132.);
|
|
if manual {
|
|
p.diagrams.get_mut(&d).unwrap().nodes.get_mut(&n).unwrap().port_anchors = list.into_iter().enumerate()
|
|
.map(|(i,port)| PortAnchor { port,side:PortAnchorSide::Right,offset:(i+1) as f32/9. }).collect();
|
|
}
|
|
Case { name:if manual {"manual ports"} else {"automatic ports"},p,d,n }
|
|
}
|
|
|
|
fn verify(case: &Case, planner: &Planner<'_>, handle: Handle, result: &ResultPlan) {
|
|
if result.status == Status::Exact {
|
|
assert_eq!(result.requested, result.target, "An Exact result must realize the requested edges");
|
|
}
|
|
if let Some(node) = &result.node {
|
|
assert!(handle.fixed_equal(planner.base, result.target), "fixed edges changed: {result:?}");
|
|
assert_eq!(node.id,planner.original.id);
|
|
assert_eq!(node.element,planner.original.element);
|
|
assert_eq!(node.port_anchors,planner.original.port_anchors);
|
|
if planner.original.collapsed {
|
|
assert_eq!(node.size.height,planner.original.size.height);
|
|
assert_eq!(node.position.y,planner.original.position.y);
|
|
}
|
|
let prepared = prepare_command(&case.p,&Command::EditNode { diagram:case.d,original:case.n,node:node.clone() }).unwrap();
|
|
let mut restored = prepared.project.clone();
|
|
restored.diagrams.get_mut(&case.d).unwrap().nodes.insert(case.n,planner.original.clone());
|
|
assert_eq!(restored,case.p,"Only selected placement may change");
|
|
let committed = project_diagram(&prepared.project,&prepared.project.diagrams[&case.d]);
|
|
let mut calls = 0;
|
|
let preview = planner.project(node,&mut calls).unwrap();
|
|
assert_eq!(preview,committed,"accepted command and preview differ");
|
|
assert_eq!(planner.rect(&committed),result.target);
|
|
if matches!(result.status,Status::NoChange) || result.target == planner.base {
|
|
assert_eq!(*node,planner.original,"No visible change must preserve exact original record");
|
|
}
|
|
} else { assert!(matches!(result.status,Status::Unresolved|Status::Ineligible)); }
|
|
}
|
|
|
|
fn exercise(case: &Case) -> (usize,usize,usize,usize) {
|
|
valid(case.name,&case.p);
|
|
let planner = Planner::new(&case.p,case.d,case.n);
|
|
println!("CASE {} base={:?} authored={:?}/{:?} dynamic={}",case.name,planner.base,planner.original.position,planner.original.size,planner.dynamic);
|
|
let mut counts = (0,0,0,0);
|
|
for h in HANDLES {
|
|
// Grow, shrink, clamp/cross, reverse after shrink, then identical repeat.
|
|
for amount in [0.,30.,-10.,-1000.,60.,-10.] {
|
|
let (dx,dy) = h.growing_delta(amount);
|
|
let result = planner.plan(h,dx,dy);
|
|
let repeated = planner.plan(h,dx,dy);
|
|
assert_eq!(result.status,repeated.status);
|
|
assert_eq!(result.target,repeated.target);
|
|
assert_eq!(result.node,repeated.node,"Planner must be independent of previous pointer history");
|
|
verify(case,&planner,h,&result);
|
|
match result.status { Status::Exact|Status::NoChange=>counts.0+=1,
|
|
Status::StaticMinimum=>counts.1+=1,Status::Unresolved=>counts.2+=1,Status::Ineligible=>counts.3+=1 }
|
|
if !planner.dynamic { assert_ne!(result.status,Status::Unresolved,"static fixture not solved: {} {h:?} {amount} {result:?}",case.name); }
|
|
println!(" {h:?} grow={amount} {:?} requested={:?} offered={:?} calls={} corrections={} reason={}",
|
|
result.status,result.requested,result.target,result.projections,result.corrections,result.reason);
|
|
}
|
|
}
|
|
println!("COUNTS {} exact/noop={} static_min={} unresolved={} ineligible={}",case.name,counts.0,counts.1,counts.2,counts.3);
|
|
counts
|
|
}
|
|
|
|
// Exhaustively enumerate this declared finite domain, not all valid f32 records.
|
|
// Port fixture has no visual children, so the target origin must be authored.
|
|
// Width/height grid deliberately includes latent sizes below visible dimensions.
|
|
fn oracle(planner: &Planner<'_>, target: Rect) -> (Option<NodePlacement>,usize) {
|
|
let mut witness = None;
|
|
let mut calls = 0;
|
|
let max_w = target.width().floor() as i32;
|
|
let max_h = target.height().floor() as i32;
|
|
for w in (84..=max_w).step_by(2) {
|
|
for h in (132..=max_h).step_by(2) {
|
|
let mut node = planner.original.clone();
|
|
node.position = pos(target.l,target.t); node.size = size(w as f32,h as f32);
|
|
let Some(view) = planner.project(&node,&mut calls) else { continue; };
|
|
if planner.rect(&view) == target && witness.is_none() { witness = Some(node); }
|
|
}
|
|
}
|
|
(witness,calls)
|
|
}
|
|
fn compare_oracle(manual: bool) {
|
|
let case = ports(manual);
|
|
valid(case.name,&case.p);
|
|
let planner = Planner::new(&case.p,case.d,case.n);
|
|
let mut misses = 0;
|
|
for (h,dx,dy) in [(Handle::E,-10.,0.),(Handle::E,8.,0.),(Handle::W,10.,0.),
|
|
(Handle::N,0.,-10.),(Handle::S,0.,-10.),(Handle::Se,10.,10.),(Handle::Nw,-10.,-10.)] {
|
|
let result = planner.plan(h,dx,dy);
|
|
let begin = Instant::now();
|
|
let (witness,calls) = oracle(&planner,result.target);
|
|
let missed = result.node.is_none() && witness.is_some();
|
|
misses += usize::from(missed);
|
|
println!("ORACLE manual={manual} {h:?} delta=({dx},{dy}) target={:?} planner={:?} witness={:?} calls={calls} ms={} missed={missed}",
|
|
result.target,result.status,witness.as_ref().map(|n|(n.position,n.size)),begin.elapsed().as_millis());
|
|
// Failures must be visible: a bounded candidate miss requires revision.
|
|
assert!(!missed,"Finite oracle found a feasible candidate missed by planner");
|
|
if result.node.is_some() { assert!(witness.is_some(),"Accepted target missing from this fixture's declared oracle grid"); }
|
|
}
|
|
println!("ORACLE SUMMARY manual={manual}: missed={misses}; this does not prove completeness outside the finite grid");
|
|
}
|
|
|
|
fn main() {
|
|
let start = Instant::now();
|
|
let cases = vec![leaf("off-grid leaf",284.5,194.25),leaf("small valid leaf",100.,50.),
|
|
container(false,false,false),container(true,false,false),container(false,true,false),container(true,false,true),
|
|
state_case(Kind::State),state_case(Kind::Region),state_case(Kind::StateMachine),state_case(Kind::EntryPoint),
|
|
ports(false),ports(true)];
|
|
let mut totals = (0,0,0,0);
|
|
for case in &cases {
|
|
let c = exercise(case); totals.0+=c.0; totals.1+=c.1; totals.2+=c.2; totals.3+=c.3;
|
|
}
|
|
// Regression: original inactive height realizes an E shrink; full normalization does not.
|
|
let case = ports(false); let planner = Planner::new(&case.p,case.d,case.n);
|
|
let result = planner.plan(Handle::E,-10.,0.);
|
|
assert_eq!(result.status,Status::Exact);
|
|
assert_eq!(result.node.unwrap().size,size(214.,160.));
|
|
if std::env::args().any(|v|v=="--oracle") { compare_oracle(false); compare_oracle(true); }
|
|
println!("PASS bounded experiment: exact/noop={} static_min={} unresolved={} ineligible={} ms={}",totals.0,totals.1,totals.2,totals.3,start.elapsed().as_millis());
|
|
println!("Unresolved outcomes are explicit solver limitations, not proven minima. No native/source/API usability or general completeness claim.");
|
|
}
|