experiment: test distributed Allocation arrangement with actual projection

This commit is contained in:
2026-09-23 07:27:42 -07:00
parent 6d99aacab8
commit 3a7923b96d
2 changed files with 143 additions and 0 deletions
@@ -0,0 +1,140 @@
//! Unaccepted mixed-Arrange experiment. No production admission is changed.
use super::*;
struct Mixed {
f: Fixture,
an: Id,
xn: Id,
yn: Id,
internal: Id,
}
fn mixed(collapsed: bool, obstacle: bool) -> Mixed {
let mut f = fixture(Layer::Oa);
let x = f.p.add_element(Kind::Activity, Layer::Oa, "X", Some(f.w)).unwrap();
let y = f.p.add_element(Kind::Activity, Layer::Oa, "Y", Some(f.w)).unwrap();
let outside = f.p.add_element(Kind::Activity, Layer::Oa, "Outside", None).unwrap();
let xn = f.p.place(f.d, x, xy(140., 200.));
let yn = f.p.place(f.d, y, xy(940., 200.));
f.p.place(f.d, outside, xy(1800., 800.));
f.p.relate(RelationKind::Allocation, "A owns X", f.a, x);
f.p.relate(RelationKind::Allocation, "B owns Y", f.b, y);
let internal = f.p.relate(RelationKind::Interaction, "X to Y", x, y);
let external = f.p.relate(RelationKind::Interaction, "Y to Outside", y, outside);
let an = f.p.diagrams[&f.d].nodes.values().find(|n| n.element == f.a).unwrap().id;
let d = f.p.diagrams.get_mut(&f.d).unwrap();
d.nodes.get_mut(&f.n).unwrap().position = xy(100., 800.);
d.nodes.get_mut(&an).unwrap().collapsed = collapsed;
d.routes = vec![route(internal), route(external)];
if obstacle {
let id = f.p.add_element(Kind::Activity, Layer::Oa, "Stationary obstacle", None).unwrap();
f.p.place(f.d, id, xy(550., 230.));
}
validate_candidate(&f.p).unwrap();
let captured = scope(&f);
assert_eq!(captured.uniform, Uniform::MixedOrUnresolved);
assert_eq!(captured.placements.keys().copied().collect::<BTreeSet<_>>(), BTreeSet::from([f.n, xn, yn]));
// Explicit control: the real current API still refuses Arrange for this root.
assert!(matches!(plan_semantics(&f.p, &captured, Destination::LayoutOnly, &BTreeMap::new()), Err(Refusal::MixedLayoutRequired)));
Mixed { f, an, xn, yn, internal }
}
fn affected(m: &Mixed) -> BTreeSet<Id> {
let mut ids: BTreeSet<_> = scope(&m.f).placements.keys().copied().collect();
ids.insert(m.an);
ids.extend(projection::descendant_nodes(&m.f.p, m.f.d, m.an));
assert_eq!(ids, BTreeSet::from([m.an, m.f.n, m.xn, m.yn]));
ids
}
// Diagnostic expansion changes only a temporary Diagram. Include affected
// descendants and ancestors even when the normal projection hides them.
fn diagnostic(p: &Project, diagram: Id, ids: &BTreeSet<Id>) -> ProjectedView {
let mut d = p.diagrams[&diagram].clone();
for (id, node) in &mut d.nodes {
if ids.contains(id) || projection::descendant_nodes(p, diagram, *id).iter().any(|n| ids.contains(n)) {
node.collapsed = false;
}
}
projection::project_diagram(p, &d)
}
fn rigid(before: &ProjectedView, after: &ProjectedView, ids: &BTreeSet<Id>) {
for id in ids {
let old = before.nodes.iter().find(|n| n.id == *id).unwrap();
let new = after.nodes.iter().find(|n| n.id == *id).unwrap();
assert_eq!(new.position, xy(old.position.x + 400., old.position.y));
assert_eq!(new.size, old.size, "no reflow of an affected footprint");
}
}
// The only experimental bypass: use the pure general geometry command instead
// of plan_semantics(LayoutOnly), whose mixed-root pre-veto is asserted above.
// This is not a fabricated Scope or a call to singleton check_geometry with a
// falsely enlarged Scope. The combined scene uses the real overlap predicate.
fn candidate(m: &Mixed) -> (Command, Project) {
let ids = affected(m);
let command = oa_core::diagram_edit::translate_placements(&m.f.p, m.f.d, &ids, xy(400., 0.)).unwrap();
let actual = prepare_command(&m.f.p, &command).unwrap().project;
validate_candidate(&actual).unwrap();
// Independent literal expected Project: no planner or route helper oracle.
let mut expected = m.f.p.clone();
let d = expected.diagrams.get_mut(&m.f.d).unwrap();
for (id, position) in [(m.an, xy(500., 100.)), (m.f.n, xy(500., 800.)), (m.xn, xy(540., 200.)), (m.yn, xy(1340., 200.))] {
d.nodes.get_mut(&id).unwrap().position = position;
}
let internal = d.routes.iter_mut().find(|r| r.edge == m.internal).unwrap();
internal.waypoints = vec![xy(820., 400.), xy(820., 550.)];
assert_eq!(actual, expected, "complete records, including folds, routes, other view and relations");
(command, actual)
}
#[test]
fn mixed_arrange_experiment_distributed_root_and_performer_can_translate_rigidly() {
for collapsed in [false, true] {
let m = mixed(collapsed, false);
let accepted = m.f.p.clone();
let (command, actual) = candidate(&m);
let ids = affected(&m);
let before = projection::project_view(&accepted, m.f.d);
let after = projection::project_view(&actual, m.f.d);
let before_expanded = diagnostic(&accepted, m.f.d, &ids);
let after_expanded = diagnostic(&actual, m.f.d, &ids);
assert!(!overlaps(&before, &after), "visible scene must admit the combined move");
assert!(!overlaps(&before_expanded, &after_expanded), "expanded scene must admit the combined move");
rigid(&before_expanded, &after_expanded, &ids);
for node in after.nodes.iter().chain(&after_expanded.nodes) {
assert!((Rect { position: node.position, size: node.size }).valid());
}
// Explicit selected headers remain visible. Native clipping/readability
// and complete group gesture admission are intentionally not claimed.
for id in [m.an, m.f.n] { assert!(after.nodes.iter().any(|n| n.id == id)); }
assert_eq!(roundtrip(&accepted, command), actual);
assert_eq!(m.f.p, accepted);
}
}
#[test]
fn mixed_arrange_experiment_folded_child_collision_refuses_before_commit() {
let m = mixed(true, true);
let accepted = m.f.p.clone();
let (_, actual) = candidate(&m);
let ids = affected(&m);
let before = projection::project_view(&accepted, m.f.d);
let after = projection::project_view(&actual, m.f.d);
assert!(!before.nodes.iter().any(|n| n.id == m.xn));
assert!(!after.nodes.iter().any(|n| n.id == m.xn));
assert!(!overlaps(&before, &after), "must distinguish hidden collision from ordinary visible refusal");
let before_expanded = diagnostic(&accepted, m.f.d, &ids);
let after_expanded = diagnostic(&actual, m.f.d, &ids);
rigid(&before_expanded, &after_expanded, &ids);
assert!(overlaps(&before_expanded, &after_expanded), "combined diagnostic scene must refuse");
// No commit on refusal; candidate preparation never mutates accepted source.
let document = Document::from_project(accepted.clone(), ProjectLimits::default()).unwrap();
let source = document.bundle().clone();
assert_eq!(document.project(), &accepted);
assert_eq!(document.bundle(), &source);
assert_eq!(document.revision(), 0);
assert!(!document.can_undo());
assert_eq!(m.f.p, accepted);
}
@@ -2,6 +2,9 @@ use super::*;
use crate::document::Document;
use oa_core::source_project::{ProjectLimits, compile_bundle, format_bundle};
#[path = "group_arrange_experiment.rs"]
mod group_arrange_experiment;
struct Fixture {
p: Project,
d: Id,