Files
ARCADIA-Studio/harness/experiments/diagram-resize-foundation-20260912/inverse-probe.rs

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.");
}