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

376 lines
16 KiB
Diff

diff --git a/crates/oa-core/src/projection.rs b/crates/oa-core/src/projection.rs
index 2ee820e3c88eda1245ea17fe36965d5e5643cf5e..b6f71ab24e23a7918260e239f102ac17552d5828 100644
--- a/crates/oa-core/src/projection.rs
+++ b/crates/oa-core/src/projection.rs
@@ -12,6 +12,7 @@ pub enum ExperimentalPortPolicy {
Current,
SameSidesAdaptSpacing,
CapacityAware,
+ CapacityAwareStableRanking,
}
#[path = "projection/state.rs"]
mod state;
diff --git a/crates/oa-core/src/projection/port_capacity_experiment.rs b/crates/oa-core/src/projection/port_capacity_experiment.rs
index 25bdab04371cafee66c6bb51bb47c11c873e976c..29723b86dcb91985b7374f9553e1aaf9b20b0299 100644
--- a/crates/oa-core/src/projection/port_capacity_experiment.rs
+++ b/crates/oa-core/src/projection/port_capacity_experiment.rs
@@ -31,7 +31,22 @@ fn counts(assigned: &[Option<PortAnchorSide>]) -> [usize; 4] {
result
}
-fn ranked(demand: &Demand, counts: [usize; 4]) -> Vec<PortAnchorSide> {
+// Explicit reproducible identity/side ranking for the fourth experimental mode.
+// UUID bits are interpreted as u128, with low64 XOR high64.rotate_left(17).
+// Each side uses (L/R/T/B index + 1) * the golden-ratio constant as a salt.
+// The SplitMix64 finalizer below uses wrapping arithmetic. Largest score wins;
+// equal scores use L/R/T/B order. No occupancy, overflow ordinal or runtime hash.
+fn identity_score(port: Id, side: PortAnchorSide) -> u64 {
+ let bits = port.as_u128();
+ let mut value = bits as u64
+ ^ ((bits >> 64) as u64).rotate_left(17)
+ ^ (index(side) as u64 + 1).wrapping_mul(0x9e37_79b9_7f4a_7c15);
+ value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
+ value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
+ value ^ (value >> 31)
+}
+
+fn ranked(demand: &Demand, counts: [usize; 4], identity: Option<Id>) -> Vec<PortAnchorSide> {
let mut result = Vec::new();
for side in [Some(demand.preferred), demand.semantic]
.into_iter()
@@ -42,7 +57,12 @@ fn ranked(demand: &Demand, counts: [usize; 4]) -> Vec<PortAnchorSide> {
}
}
let mut remaining = SIDES;
- remaining.sort_by_key(|side| (counts[index(*side)], index(*side)));
+ if let Some(port) = identity {
+ remaining
+ .sort_by_key(|side| (std::cmp::Reverse(identity_score(port, *side)), index(*side)));
+ } else {
+ remaining.sort_by_key(|side| (counts[index(*side)], index(*side)));
+ }
for side in remaining {
if demand.clear[index(side)] && !result.contains(&side) {
result.push(side);
@@ -59,9 +79,10 @@ fn try_assign(
capacities: [usize; 4],
assigned: &mut [Option<PortAnchorSide>],
visited: &mut [bool; 4],
+ identities: Option<&[Id]>,
) -> bool {
let counts = counts(assigned);
- let sides = ranked(&demands[port], counts);
+ let sides = ranked(&demands[port], counts, identities.map(|ids| ids[port]));
for side in &sides {
let slot = index(*side);
if !visited[slot] && counts[slot] < capacities[slot] {
@@ -81,7 +102,7 @@ fn try_assign(
.filter_map(|(port, current)| (*current == Some(side)).then_some(port))
.collect();
for other in occupants {
- if try_assign(other, demands, capacities, assigned, visited) {
+ if try_assign(other, demands, capacities, assigned, visited, identities) {
assigned[port] = Some(side);
return true;
}
@@ -91,11 +112,27 @@ fn try_assign(
}
fn assign_with_capacities(demands: &[Demand], capacities: [usize; 4]) -> Vec<PortAnchorSide> {
+ assign_with_identities(demands, capacities, None)
+}
+
+fn assign_with_identities(
+ demands: &[Demand],
+ capacities: [usize; 4],
+ identities: Option<&[Id]>,
+) -> Vec<PortAnchorSide> {
+ assert!(identities.is_none_or(|ids| ids.len() == demands.len()));
let mut assigned = vec![None; demands.len()];
// Demands are in the core's stable semantic port-ID order. Manual anchors
// are deliberately absent: editing one must not resize owners or flip peers.
for port in 0..demands.len() {
- try_assign(port, demands, capacities, &mut assigned, &mut [false; 4]);
+ try_assign(
+ port,
+ demands,
+ capacities,
+ &mut assigned,
+ &mut [false; 4],
+ identities,
+ );
}
for (port, demand) in demands.iter().enumerate() {
if assigned[port].is_some() {
@@ -104,7 +141,7 @@ fn assign_with_capacities(demands: &[Demand], capacities: [usize; 4]) -> Vec<Por
let counts = counts(&assigned);
// Prefer a crowded clear exit to an available blocked one. The existing
// position allocator adapts spacing; no port or owner geometry is invented.
- let side = ranked(demand, counts)
+ let side = ranked(demand, counts, identities.map(|ids| ids[port]))
.into_iter()
.next()
.unwrap_or_else(|| {
@@ -130,6 +167,23 @@ pub(super) fn assign(node: &ProjectedNode, demands: &[Demand]) -> Vec<PortAnchor
)
}
+pub(super) fn assign_stable(
+ node: &ProjectedNode,
+ demands: &[Demand],
+ identities: &[Id],
+) -> Vec<PortAnchorSide> {
+ assign_with_identities(
+ demands,
+ [
+ capacity(node.size.height),
+ capacity(node.size.height),
+ capacity(node.size.width),
+ capacity(node.size.width),
+ ],
+ Some(identities),
+ )
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/oa-core/src/projection/port_layout.rs b/crates/oa-core/src/projection/port_layout.rs
index ba0d86897ed432efc466ed7af43dd52d010d08a0..555fa56b3829f357d41f3a94bbac520b690be9e7 100644
--- a/crates/oa-core/src/projection/port_layout.rs
+++ b/crates/oa-core/src/projection/port_layout.rs
@@ -232,17 +232,23 @@ pub(super) fn layout(
automatic_side: side,
manual: anchor.map(|a| a.offset),
peer,
- capacity: (policy == ExperimentalPortPolicy::CapacityAware).then(|| {
- capacity_experiment::Demand {
- preferred: side,
- semantic: port_anchor_side(port),
- clear: SIDES.map(clear),
- }
+ capacity: matches!(
+ policy,
+ ExperimentalPortPolicy::CapacityAware
+ | ExperimentalPortPolicy::CapacityAwareStableRanking
+ )
+ .then(|| capacity_experiment::Demand {
+ preferred: side,
+ semantic: port_anchor_side(port),
+ clear: SIDES.map(clear),
}),
});
}
}
- if policy == ExperimentalPortPolicy::CapacityAware {
+ if matches!(
+ policy,
+ ExperimentalPortPolicy::CapacityAware | ExperimentalPortPolicy::CapacityAwareStableRanking
+ ) {
for node in nodes.iter() {
let Some(choices) = choices.get_mut(&node.id) else {
continue;
@@ -251,7 +257,12 @@ pub(super) fn layout(
.iter()
.map(|choice| choice.capacity.clone().unwrap())
.collect();
- let sides = capacity_experiment::assign(node, &demands);
+ let sides = if policy == ExperimentalPortPolicy::CapacityAwareStableRanking {
+ let identities: Vec<_> = choices.iter().map(|choice| choice.port).collect();
+ capacity_experiment::assign_stable(node, &demands, &identities)
+ } else {
+ capacity_experiment::assign(node, &demands)
+ };
for (choice, side) in choices.iter_mut().zip(sides) {
choice.automatic_side = side;
if choice.manual.is_none() {
diff --git a/crates/oa-core/tests/experimental_port_policy_falsifiers.rs b/crates/oa-core/tests/experimental_port_policy_falsifiers.rs
index ef6d197b0f397ce4c976859adabd62c46cbcb0da..0a31254a47fc151a723e21a372f1023d09ef83c0 100644
--- a/crates/oa-core/tests/experimental_port_policy_falsifiers.rs
+++ b/crates/oa-core/tests/experimental_port_policy_falsifiers.rs
@@ -402,3 +402,184 @@ fn capacity_spillover_does_not_add_a_blocked_mandatory_stub_above_the_owner() {
"Prospective exit-clearance criterion failed: {violations:?}"
);
}
+
+fn fixed_port_identity_set(mut f: Fixture, set: usize) -> Fixture {
+ if set == 0 {
+ return f;
+ }
+ // Two declared, reproducible UUID sets; not random draws or selected after
+ // observing layout results. Only port identities change, not topology.
+ let replacements: Vec<_> = (1..=f.ports.len())
+ .map(|n| {
+ let low = match set {
+ 1 => (n as u64).wrapping_mul(0xd134_2543_de82_ef95),
+ 2 => (n as u64)
+ .wrapping_mul(0x9e37_79b9_7f4a_7c15)
+ .rotate_left(23),
+ _ => unreachable!(),
+ } & 0x0000_ffff_ffff_ffff;
+ let prefix = if set == 1 {
+ 0x12345678_9abc_4000_8000_000000000000
+ } else {
+ 0xfedcba98_7654_4000_8000_000000000000
+ };
+ Id::from_u128(prefix | u128::from(low))
+ })
+ .collect();
+ assert_eq!(
+ replacements
+ .iter()
+ .collect::<std::collections::BTreeSet<_>>()
+ .len(),
+ f.ports.len()
+ );
+ let commands: Vec<_> = f
+ .ports
+ .iter()
+ .zip(&replacements)
+ .map(|(old, new)| Command::Reidentify(*old, *new))
+ .collect();
+ f.p = execute_batch(&f.p, &commands).unwrap();
+ f.ports = replacements;
+ validate_candidate(&f.p).unwrap();
+ assert_eq!(language::parse(&language::format(&f.p)).unwrap(), f.p);
+ f
+}
+
+#[test]
+fn stable_fallback_ranking_bounds_threshold_churn_across_three_identity_sets_and_reverse() {
+ let stable = ExperimentalPortPolicy::CapacityAwareStableRanking;
+ let mut violations = Vec::new();
+ for set in 0..3 {
+ for policy in [ExperimentalPortPolicy::CapacityAware, stable] {
+ let mut f = fixed_port_identity_set(right_peer(20, 259.9, false), set);
+ let mut views = Vec::new();
+ for (frame, height) in [259.9, 260., 260.1, 260., 259.9].into_iter().enumerate() {
+ f.p.diagrams
+ .get_mut(&f.d)
+ .unwrap()
+ .nodes
+ .get_mut(&f.owner)
+ .unwrap()
+ .size
+ .height = height;
+ let original = f.p.clone();
+ let view = project(&f, policy);
+ let label = format!("stable-threshold-set{set}-{policy:?}-frame{frame}-{height}");
+ measure(&f, &view, &label);
+ assert_eq!(f.p, original);
+ assert_eq!(owner(&f, &view).size, size(224., height));
+ if policy == stable && gap(&f, &view) < 27.99 {
+ violations.push(format!(
+ "{label}: avoidable sub-nominal gap {}",
+ gap(&f, &view)
+ ));
+ }
+ 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!(
+ "stable threshold set={set} policy={policy:?} changes={flips:?}; prediction original=[12,0,0,12], stable<=1 each (unverified)"
+ );
+ assert_eq!(
+ views[0], views[4],
+ "Same rectangle must reproject identically after reversal"
+ );
+ assert_eq!(views[1], views[3]);
+ if policy == stable && flips.iter().any(|n| *n > 1) {
+ violations.push(format!("set={set} stable side changes {flips:?}; same bounded criterion across every UUID set"));
+ }
+ }
+ }
+ assert!(
+ violations.is_empty(),
+ "Prospective stable-ranking threshold criterion failed: {violations:?}"
+ );
+}
+
+#[test]
+fn stable_fallback_ranking_still_needs_real_exit_clearance_including_near_corner() {
+ let stable = ExperimentalPortPolicy::CapacityAwareStableRanking;
+ let mut violations = Vec::new();
+ for corner in [false, true] {
+ let mut f = right_peer(8, 160., true);
+ if corner {
+ let obstacle =
+ f.p.diagrams
+ .get_mut(&f.d)
+ .unwrap()
+ .nodes
+ .get_mut(&id(42))
+ .unwrap();
+ obstacle.position = pos(230., -120.);
+ obstacle.size = size(184., 132.);
+ }
+ validate_candidate(&f.p).unwrap();
+ assert_eq!(language::parse(&language::format(&f.p)).unwrap(), f.p);
+ let original = f.p.clone();
+ for policy in POLICIES.into_iter().chain([stable]) {
+ let view = project(&f, policy);
+ let label = format!("stable-obstacle-corner{corner}-{policy:?}");
+ let routed = measure(&f, &view, &label);
+ let blocked = blocked_stubs(&view);
+ if policy != ExperimentalPortPolicy::Current {
+ assert_eq!(owner(&f, &view).size, size(224., 160.));
+ }
+ if corner {
+ let obstacle = view.nodes.iter().find(|n| n.id == id(42)).unwrap();
+ assert_eq!(
+ (obstacle.position, obstacle.size),
+ (pos(230., -120.), size(184., 132.))
+ );
+ let midpoint = ProjectedPort {
+ element: f.ports[0],
+ owner: f.owner,
+ side: PortAnchorSide::Right,
+ position: pos(224., owner(&f, &view).size.height / 2.),
+ };
+ assert!(view.nodes.iter().all(|node| !stub_hits(&midpoint, node)));
+ // Independent geometric witness only: five legal same-side
+ // coordinates exist. No fake ports are passed to the projector
+ // or router. The actual mode must produce its own clear layout.
+ let coordinates = [24.01_f32, 53.5075, 83.005, 112.5025, 142.];
+ assert!(coordinates.windows(2).all(|p| p[1] - p[0] >= 28.));
+ for y in coordinates {
+ let witness = ProjectedPort {
+ position: pos(224., y),
+ ..midpoint.clone()
+ };
+ assert!(view.nodes.iter().all(|node| !stub_hits(&witness, node)));
+ }
+ println!(
+ "{label}: actual Right midpoint {:?} clear; independent five-position witness={coordinates:?}; actual blocked={blocked:?}",
+ midpoint.position
+ );
+ }
+ let warned: Vec<_> = routed
+ .edges
+ .values()
+ .filter(|e| !e.warnings.is_empty())
+ .map(|e| e.id)
+ .collect();
+ println!(
+ "{label}: candidate attribution only; midpoint/placement unchanged, warnings={warned:?}"
+ );
+ if policy == stable && (!blocked.is_empty() || !warned.is_empty()) {
+ violations.push(format!("{label}: blocked={blocked:?}, warned={warned:?}"));
+ }
+ assert_eq!(f.p, original);
+ }
+ }
+ assert!(
+ violations.is_empty(),
+ "Prospective stable-ranking real-exit criterion failed: {violations:?}"
+ );
+}