Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66260c0c6b | ||
|
|
613e3ca322 | ||
|
|
57b3fe4bff | ||
|
|
81faeff58a | ||
|
|
70bdcdf8fe | ||
|
|
66278ed33b | ||
|
|
a2b8204f8b | ||
|
|
2af40c7e27 |
+167
-11
@@ -53,6 +53,8 @@ pub struct RoutedEdge {
|
||||
pub struct EdgeLabelGeometry {
|
||||
/// Empty for edge kinds without a permanently displayed label.
|
||||
pub text: String,
|
||||
/// Dedicated State-canvas two-block presentation; raw summary stays in text.
|
||||
pub state_layout: Option<StateLabelLayout>,
|
||||
/// Effective arc-length fraction, including an automatic collision-avoidance
|
||||
/// choice. Use this when the first label drag becomes authored metadata.
|
||||
pub fraction: f32,
|
||||
@@ -61,6 +63,108 @@ pub struct EdgeLabelGeometry {
|
||||
pub bounds: RoutingBounds,
|
||||
}
|
||||
|
||||
/// Derived hard-line layout for the dedicated State projection, never persisted.
|
||||
/// Excerpts are chosen on raw text before display-only separator/tab expansion.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StateLabelLayout {
|
||||
pub title_lines: Vec<String>,
|
||||
pub summary_lines: Vec<String>,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl StateLabelLayout {
|
||||
pub const FONT_SIZE: f32 = 10.0;
|
||||
pub const LINE_HEIGHT: f32 = 12.0;
|
||||
pub const HORIZONTAL_PADDING: f32 = 5.0;
|
||||
pub const MIN_HEIGHT: f32 = 24.0;
|
||||
|
||||
/// `summary` is the existing raw, globally shortened routing summary.
|
||||
/// Measurement receives each final display line at FONT_SIZE, without breaks.
|
||||
/// Invalid measurements fall back to the deterministic approximate width.
|
||||
pub fn new(title: &str, summary: &str, measure: &dyn Fn(&str) -> f32) -> Self {
|
||||
let budget = (7 * summary.chars().count() + 6) / 6;
|
||||
let title = if title.chars().count() > budget {
|
||||
title
|
||||
.chars()
|
||||
.take(budget.saturating_sub(1))
|
||||
.chain(std::iter::once('…'))
|
||||
.collect()
|
||||
} else {
|
||||
title.to_owned()
|
||||
};
|
||||
let title_lines = state_display_lines(&title);
|
||||
let summary_lines = state_display_lines(summary);
|
||||
let width = title_lines
|
||||
.iter()
|
||||
.chain(&summary_lines)
|
||||
.map(|line| {
|
||||
let width = measure(line);
|
||||
if width.is_finite() && width >= 0.0 {
|
||||
width
|
||||
} else {
|
||||
approximate_state_line_width(line)
|
||||
}
|
||||
})
|
||||
.fold(0.0_f32, f32::max);
|
||||
let size = Size {
|
||||
width: width + 2.0 * Self::HORIZONTAL_PADDING,
|
||||
height: (Self::LINE_HEIGHT * (title_lines.len() + summary_lines.len()) as f32)
|
||||
.max(Self::MIN_HEIGHT),
|
||||
};
|
||||
Self {
|
||||
title_lines,
|
||||
summary_lines,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Local origin relative to EdgeLabelGeometry.bounds.position.
|
||||
pub fn title_line_position(&self, index: usize) -> Position {
|
||||
Position {
|
||||
x: Self::HORIZONTAL_PADDING,
|
||||
y: index as f32 * Self::LINE_HEIGHT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Local origin relative to EdgeLabelGeometry.bounds.position.
|
||||
pub fn summary_line_position(&self, index: usize) -> Position {
|
||||
Position {
|
||||
x: Self::HORIZONTAL_PADDING,
|
||||
y: (self.title_lines.len() + index) as f32 * Self::LINE_HEIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_display_lines(raw: &str) -> Vec<String> {
|
||||
if raw.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut lines = vec![String::new()];
|
||||
let mut chars = raw.chars().peekable();
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\r' => {
|
||||
if chars.peek() == Some(&'\n') {
|
||||
chars.next();
|
||||
}
|
||||
lines.push(String::new());
|
||||
}
|
||||
'\n' | '\u{85}' | '\u{2028}' | '\u{2029}' => lines.push(String::new()),
|
||||
'\t' => lines.last_mut().unwrap().push_str(" "),
|
||||
_ => lines.last_mut().unwrap().push(ch),
|
||||
}
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
fn approximate_state_line_width(line: &str) -> f32 {
|
||||
line.chars().count() as f32 * 7.0
|
||||
}
|
||||
|
||||
/// Derived labels and navigation markers for the two named association ends.
|
||||
/// Canvas and SVG consume these same coordinates; no extra diagram state exists.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -161,6 +265,7 @@ pub fn association_end_geometry(
|
||||
source_end,
|
||||
label: EdgeLabelGeometry {
|
||||
text,
|
||||
state_layout: None,
|
||||
fraction: if source_end { 0.0 } else { 1.0 },
|
||||
anchor: tip,
|
||||
center,
|
||||
@@ -436,7 +541,24 @@ fn ancestors(id: Id, nodes: &BTreeMap<Id, &ProjectedNode>, out: &mut BTreeSet<Id
|
||||
/// Search caps are presentation limits, reported on the affected edge, never
|
||||
/// semantic errors or a reason to discard an authored point.
|
||||
pub fn route_diagram(project: &Project, diagram: &Diagram, view: &ProjectedView) -> RoutedView {
|
||||
route_diagram_impl(project, diagram, view, None)
|
||||
route_diagram_with_state_label_measurement(
|
||||
project,
|
||||
diagram,
|
||||
view,
|
||||
&approximate_state_line_width,
|
||||
)
|
||||
}
|
||||
|
||||
/// Route with frontend line measurements for dedicated State labels before collision placement.
|
||||
/// The legacy entrypoint uses a deterministic 7-units-per-scalar approximation,
|
||||
/// not a promise of shaped glyph containment. All other label kinds are unchanged.
|
||||
pub fn route_diagram_with_state_label_measurement(
|
||||
project: &Project,
|
||||
diagram: &Diagram,
|
||||
view: &ProjectedView,
|
||||
measure: &dyn Fn(&str) -> f32,
|
||||
) -> RoutedView {
|
||||
route_diagram_impl(project, diagram, view, None, measure)
|
||||
}
|
||||
|
||||
/// Transient route/label editing with unchanged projected nodes and ports.
|
||||
@@ -451,7 +573,26 @@ pub fn route_edge_preview(
|
||||
baseline: &RoutedView,
|
||||
edge: Id,
|
||||
) -> RoutedView {
|
||||
route_diagram_impl(project, diagram, view, Some((baseline, edge)))
|
||||
route_edge_preview_with_state_label_measurement(
|
||||
project,
|
||||
diagram,
|
||||
view,
|
||||
baseline,
|
||||
edge,
|
||||
&approximate_state_line_width,
|
||||
)
|
||||
}
|
||||
|
||||
/// Preview counterpart using the same State text measurer as the settled scene.
|
||||
pub fn route_edge_preview_with_state_label_measurement(
|
||||
project: &Project,
|
||||
diagram: &Diagram,
|
||||
view: &ProjectedView,
|
||||
baseline: &RoutedView,
|
||||
edge: Id,
|
||||
measure: &dyn Fn(&str) -> f32,
|
||||
) -> RoutedView {
|
||||
route_diagram_impl(project, diagram, view, Some((baseline, edge)), measure)
|
||||
}
|
||||
|
||||
fn route_diagram_impl(
|
||||
@@ -459,6 +600,7 @@ fn route_diagram_impl(
|
||||
diagram: &Diagram,
|
||||
view: &ProjectedView,
|
||||
preview: Option<(&RoutedView, Id)>,
|
||||
measure: &dyn Fn(&str) -> f32,
|
||||
) -> RoutedView {
|
||||
let nodes: BTreeMap<_, _> = view.nodes.iter().map(|n| (n.id, n)).collect();
|
||||
let rectangles: Vec<_> = nodes.values().map(|n| Rect::node(n)).collect();
|
||||
@@ -639,13 +781,14 @@ fn route_diagram_impl(
|
||||
.flatten();
|
||||
routed.label = make_label(
|
||||
project,
|
||||
diagram.view_options.exchange_labels,
|
||||
diagram,
|
||||
edge.id,
|
||||
&routed.points,
|
||||
authored_label,
|
||||
&obstacles,
|
||||
&previous_labels,
|
||||
&previous,
|
||||
measure,
|
||||
);
|
||||
}
|
||||
if !routed.label.text.is_empty() {
|
||||
@@ -1345,6 +1488,7 @@ fn label_text(project: &Project, edge: Id) -> String {
|
||||
fn empty_label() -> EdgeLabelGeometry {
|
||||
EdgeLabelGeometry {
|
||||
text: String::new(),
|
||||
state_layout: None,
|
||||
fraction: 0.5,
|
||||
anchor: pos(0.0, 0.0),
|
||||
center: pos(0.0, 0.0),
|
||||
@@ -1362,14 +1506,16 @@ fn empty_label() -> EdgeLabelGeometry {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn make_label(
|
||||
project: &Project,
|
||||
labels: crate::view_options::ExchangeLabels,
|
||||
diagram: &Diagram,
|
||||
edge: Id,
|
||||
points: &[Position],
|
||||
authored: Option<&crate::EdgeLabel>,
|
||||
obstacles: &[Rect],
|
||||
previous_labels: &[RoutingBounds],
|
||||
strokes: &[Segment],
|
||||
measure: &dyn Fn(&str) -> f32,
|
||||
) -> EdgeLabelGeometry {
|
||||
let labels = diagram.view_options.exchange_labels;
|
||||
let text = if labels != crate::view_options::ExchangeLabels::Name
|
||||
&& project
|
||||
.relations
|
||||
@@ -1385,14 +1531,23 @@ fn make_label(
|
||||
} else {
|
||||
label_text(project, edge)
|
||||
};
|
||||
let size = Size {
|
||||
width: if text.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
text.chars().count() as f32 * 7.0 + 16.0
|
||||
let state_layout = (diagram.projection.state_machine().is_some()
|
||||
&& project
|
||||
.relations
|
||||
.get(&edge)
|
||||
.is_some_and(|r| r.kind() == RelationKind::StateTransition))
|
||||
.then(|| StateLabelLayout::new(project.named(edge), &text, measure));
|
||||
let size = state_layout.as_ref().map_or_else(
|
||||
|| Size {
|
||||
width: if text.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
text.chars().count() as f32 * 7.0 + 16.0
|
||||
},
|
||||
height: if text.is_empty() { 0.0 } else { 24.0 },
|
||||
},
|
||||
height: if text.is_empty() { 0.0 } else { 24.0 },
|
||||
};
|
||||
StateLabelLayout::size,
|
||||
);
|
||||
let mut offset = authored.map_or(pos(0.0, -18.0), |l| l.offset);
|
||||
let geometry = |fraction, offset: Position| {
|
||||
let anchor = point_along_polyline(points, fraction).unwrap_or(pos(0.0, 0.0));
|
||||
@@ -1474,6 +1629,7 @@ fn make_label(
|
||||
let (anchor, center, bounds) = geometry(fraction, offset);
|
||||
EdgeLabelGeometry {
|
||||
text,
|
||||
state_layout,
|
||||
fraction,
|
||||
anchor,
|
||||
center,
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
use oa_core::{projection::project_diagram, routing::*, *};
|
||||
|
||||
fn fixture() -> (Project, Id, Id) {
|
||||
let mut project = language::parse(include_str!(
|
||||
"../../../harness/experiments/edge-attachments-20260922/native-fixture.arcadia"
|
||||
))
|
||||
.unwrap();
|
||||
let edge = Id::parse_str("ea202922-0033-4000-8000-000000000041").unwrap();
|
||||
let diagram = Id::parse_str("ea202922-0033-4000-8000-000000000200").unwrap();
|
||||
let relation = project.relations.get_mut(&edge).unwrap();
|
||||
relation.name = "Ready\nfor review".into();
|
||||
if let RelationBody::StateTransition { guard, .. } = &mut relation.body {
|
||||
*guard = "request_ready".into();
|
||||
}
|
||||
(project, diagram, edge)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_two_block_layout_separates_lines_and_keeps_authored_geometry_and_project() {
|
||||
let (project, diagram, edge) = fixture();
|
||||
let before = project.clone();
|
||||
let diagram = &project.diagrams[&diagram];
|
||||
let view = project_diagram(&project, diagram);
|
||||
let routed = route_diagram(&project, diagram, &view);
|
||||
let edge_geometry = &routed.edges[&edge];
|
||||
let label = &edge_geometry.label;
|
||||
let layout = label.state_layout.as_ref().unwrap();
|
||||
assert_eq!(layout.title_lines, ["Ready", "for review"]);
|
||||
assert_eq!(
|
||||
layout.summary_lines,
|
||||
["External · Completion [request_ready]"]
|
||||
);
|
||||
assert_eq!(label.text, "External · Completion [request_ready]");
|
||||
assert_eq!(layout.title_line_position(1), Position { x: 5., y: 12. });
|
||||
assert_eq!(layout.summary_line_position(0), Position { x: 5., y: 24. });
|
||||
assert_eq!(
|
||||
label.bounds.size,
|
||||
Size {
|
||||
width: 269.,
|
||||
height: 36.
|
||||
}
|
||||
);
|
||||
let authored = diagram
|
||||
.routes
|
||||
.iter()
|
||||
.find(|r| r.edge == edge)
|
||||
.unwrap()
|
||||
.label
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert_eq!(label.fraction, authored.fraction);
|
||||
let anchor = point_along_polyline(&edge_geometry.points, authored.fraction).unwrap();
|
||||
assert_eq!(label.anchor, anchor);
|
||||
assert_eq!(
|
||||
label.center,
|
||||
Position {
|
||||
x: anchor.x + authored.offset.x,
|
||||
y: anchor.y + authored.offset.y
|
||||
}
|
||||
);
|
||||
assert_eq!(project, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_display_breaks_tabs_and_blank_lines_are_single_sourced() {
|
||||
let summary = "A sufficiently long unchanged summary";
|
||||
for title in [
|
||||
"Ready\nfor review",
|
||||
"Ready\r\nfor review",
|
||||
"Ready\rfor review",
|
||||
"Ready\u{85}for review",
|
||||
"Ready\u{2028}for review",
|
||||
"Ready\u{2029}for review",
|
||||
] {
|
||||
let layout = StateLabelLayout::new(title, summary, &|s| s.chars().count() as f32);
|
||||
assert_eq!(layout.title_lines, ["Ready", "for review"]);
|
||||
assert_eq!(layout.summary_line_position(0).y, 24.);
|
||||
}
|
||||
let layout = StateLabelLayout::new("Ready\n\nfor\treview\n", summary, &|s| {
|
||||
s.chars().count() as f32
|
||||
});
|
||||
assert_eq!(layout.title_lines, ["Ready", "", "for review", ""]);
|
||||
assert_eq!(layout.summary_line_position(0).y, 48.);
|
||||
assert_eq!(layout.size().height, 60.);
|
||||
let empty = StateLabelLayout::new("", "", &|_| 0.);
|
||||
assert!(empty.title_lines.is_empty() && empty.summary_lines.is_empty());
|
||||
assert_eq!(empty.size().height, 24.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_summary_raw_prefix_and_title_budget_survive_display_normalization() {
|
||||
let (mut project, diagram, edge) = fixture();
|
||||
let relation = project.relations.get_mut(&edge).unwrap();
|
||||
relation.name = "T".repeat(80);
|
||||
if let RelationBody::StateTransition { guard, .. } = &mut relation.body {
|
||||
*guard = format!("first\r\n{}", "guard".repeat(20));
|
||||
}
|
||||
let full = state_transition_summary(&project, &project.relations[&edge]);
|
||||
let raw: String = full.chars().take(47).chain(std::iter::once('…')).collect();
|
||||
let d = &project.diagrams[&diagram];
|
||||
let routed = route_diagram(&project, d, &project_diagram(&project, d));
|
||||
let label = &routed.edges[&edge].label;
|
||||
assert_eq!(label.text, raw);
|
||||
assert!(label.text.contains("\r\n"));
|
||||
let layout = label.state_layout.as_ref().unwrap();
|
||||
assert_eq!(layout.title_lines, [format!("{}…", "T".repeat(56))]);
|
||||
assert_eq!(
|
||||
layout.summary_lines,
|
||||
raw.replace("\r\n", "\n")
|
||||
.split('\n')
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(label.text.chars().count(), 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supplied_measurement_controls_settled_and_preview_state_bounds_only() {
|
||||
let (project, diagram, edge) = fixture();
|
||||
let d = &project.diagrams[&diagram];
|
||||
let view = project_diagram(&project, d);
|
||||
let baseline = route_diagram(&project, d, &view);
|
||||
let wide = |_: &str| 400.;
|
||||
let measured = route_diagram_with_state_label_measurement(&project, d, &view, &wide);
|
||||
let label = &measured.edges[&edge].label;
|
||||
assert_eq!(
|
||||
label.bounds.size,
|
||||
Size {
|
||||
width: 410.,
|
||||
height: 36.
|
||||
}
|
||||
);
|
||||
assert_eq!(label.center, baseline.edges[&edge].label.center);
|
||||
assert_eq!(measured.edges[&edge].points, baseline.edges[&edge].points);
|
||||
let preview =
|
||||
route_edge_preview_with_state_label_measurement(&project, d, &view, &measured, edge, &wide);
|
||||
assert_eq!(preview.edges[&edge].label, *label);
|
||||
let mut ordinary = d.clone();
|
||||
ordinary.projection = ProjectionKind::Allocation;
|
||||
let ordinary_measured =
|
||||
route_diagram_with_state_label_measurement(&project, &ordinary, &view, &wide);
|
||||
let ordinary_default = route_diagram(&project, &ordinary, &view);
|
||||
assert!(ordinary_measured.edges[&edge].label.state_layout.is_none());
|
||||
assert_eq!(ordinary_measured, ordinary_default);
|
||||
}
|
||||
@@ -210,6 +210,7 @@ impl SemanticPlan {
|
||||
pub fn project(&self) -> &Project {
|
||||
&self.candidate
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn index(&self) -> &ViewIndex {
|
||||
&self.index
|
||||
}
|
||||
@@ -484,57 +485,6 @@ pub struct PresentationContext {
|
||||
pub target: Option<TargetPresentation>,
|
||||
}
|
||||
|
||||
impl PresentationContext {
|
||||
fn header_visible(self, header: Rect, outgoing: bool) -> bool {
|
||||
if !outgoing {
|
||||
return self.viewport.contains(header);
|
||||
}
|
||||
// Leaving a performer must not require fitting the entire work card
|
||||
// inside the camera. Retain a 24px grab region so the result stays
|
||||
// directly reachable; geometry/model admission is independent.
|
||||
let width = (header.position.x + header.size.width)
|
||||
.min(self.viewport.position.x + self.viewport.size.width)
|
||||
- header.position.x.max(self.viewport.position.x);
|
||||
let height = (header.position.y + header.size.height)
|
||||
.min(self.viewport.position.y + self.viewport.size.height)
|
||||
- header.position.y.max(self.viewport.position.y);
|
||||
width * self.zoom >= 24. && height * self.zoom >= 24.
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outgoing_header_retains_a_grab_region_without_requiring_full_camera_containment() {
|
||||
let context = PresentationContext {
|
||||
viewport: Rect {
|
||||
position: Position { x: 0., y: 0. },
|
||||
size: Size {
|
||||
width: 800.,
|
||||
height: 400.,
|
||||
},
|
||||
},
|
||||
zoom: 0.75,
|
||||
target: None,
|
||||
};
|
||||
let mut header = Rect {
|
||||
position: Position { x: -100., y: 30. },
|
||||
size: Size {
|
||||
width: 224.,
|
||||
height: 62.,
|
||||
},
|
||||
};
|
||||
assert!(context.header_visible(header, true));
|
||||
assert!(!context.header_visible(header, false));
|
||||
header.position.x = -192.; // exactly 32 world pixels = 24 logical pixels
|
||||
assert!(context.header_visible(header, true));
|
||||
header.position.x -= 1.;
|
||||
assert!(!context.header_visible(header, true));
|
||||
header.position.x = 0.;
|
||||
header.position.y = 368.;
|
||||
assert!(context.header_visible(header, true));
|
||||
header.position.y += 1.;
|
||||
assert!(!context.header_visible(header, true));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlacementPlan {
|
||||
pub diagram: Diagram,
|
||||
@@ -546,8 +496,8 @@ pub struct PlacementPlan {
|
||||
commands: Vec<Command>,
|
||||
}
|
||||
|
||||
/// `requested` preserves the frozen pointer offset unless the caller proposes
|
||||
/// bounded outward clearance for an outgoing drop. Snap only
|
||||
/// `requested` is the original selected placement position plus pointer motion
|
||||
/// in the frozen transform (therefore preserving the press offset). Snap only
|
||||
/// this root; apply the resulting identical delta to captured Q and its routes.
|
||||
/// No whole Project clone, validator or semantic ViewIndex rebuild occurs here.
|
||||
pub fn place(
|
||||
@@ -627,7 +577,7 @@ pub fn place(
|
||||
height: selected.size.height.min(62.),
|
||||
},
|
||||
};
|
||||
if !presentation.header_visible(header, plan.destination == Destination::OutsidePerformers) {
|
||||
if !presentation.viewport.contains(header) {
|
||||
return Err(Refusal::LayoutRequired(
|
||||
"Make room or adjust the view, then drag again.".into(),
|
||||
));
|
||||
|
||||
@@ -188,7 +188,7 @@ pub fn place(
|
||||
};
|
||||
geometry::check_rigid(&plan.before_expanded, &expanded, &plan.placements, delta)?;
|
||||
scenes?;
|
||||
geometry::check_headers(&view, &plan.selected, presentation, false)?;
|
||||
geometry::check_headers(&view, &plan.selected, presentation.viewport)?;
|
||||
Ok(PlacementPlan {
|
||||
diagram,
|
||||
view,
|
||||
|
||||
@@ -398,12 +398,7 @@ pub fn place(
|
||||
&expanded,
|
||||
)?;
|
||||
geometry::check_rigid(&scope.0.before_expanded, &expanded, scope.affected(), delta)?;
|
||||
geometry::check_headers(
|
||||
&view,
|
||||
scope.selected(),
|
||||
presentation,
|
||||
plan.destination == Destination::OutsidePerformers,
|
||||
)?;
|
||||
geometry::check_headers(&view, scope.selected(), presentation.viewport)?;
|
||||
Ok(PlacementPlan {
|
||||
diagram,
|
||||
view,
|
||||
|
||||
@@ -170,8 +170,7 @@ pub(super) fn check_rigid(
|
||||
pub(super) fn check_headers(
|
||||
view: &ProjectedView,
|
||||
selected: &BTreeMap<Id, Id>,
|
||||
presentation: PresentationContext,
|
||||
outgoing: bool,
|
||||
viewport: Rect,
|
||||
) -> Result<(), Refusal> {
|
||||
for (id, element) in selected {
|
||||
let node = view
|
||||
@@ -186,7 +185,7 @@ pub(super) fn check_headers(
|
||||
height: node.size.height.min(62.),
|
||||
},
|
||||
};
|
||||
if !presentation.header_visible(header, outgoing) {
|
||||
if !viewport.contains(header) {
|
||||
return Err(Refusal::LayoutRequired(
|
||||
"Make room or adjust the view, then drag again.".into(),
|
||||
));
|
||||
|
||||
@@ -5241,7 +5241,7 @@ impl App {
|
||||
if route.waypoints.len() >= 128 {
|
||||
return;
|
||||
}
|
||||
let geometry = oa_core::routing::route_diagram(self.project(), diagram, &view);
|
||||
let geometry = crate::state_label::route_diagram(self.project(), diagram, &view);
|
||||
let edge = &geometry.edges[&self.selected];
|
||||
// Mandatory port leads are inside the card's clearance. A pin
|
||||
// starts beyond those leads, including coincident short stubs.
|
||||
@@ -5321,7 +5321,7 @@ impl App {
|
||||
self.pan = iced::Vector::new(30., 30.);
|
||||
return;
|
||||
}
|
||||
let geometry = oa_core::routing::route_diagram(
|
||||
let geometry = crate::state_label::route_diagram(
|
||||
self.project(),
|
||||
&self.project().diagrams[&self.active_diagram.unwrap()],
|
||||
&view,
|
||||
|
||||
@@ -14,8 +14,6 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
mod clearance;
|
||||
|
||||
pub const FINISH_EDIT: &str =
|
||||
"Finish or cancel the current edit before selecting or moving elements.";
|
||||
|
||||
@@ -58,7 +56,6 @@ pub struct Target {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Frozen {
|
||||
pub targets: Vec<Target>,
|
||||
pub moving: Vec<Rectangle>,
|
||||
cards: Vec<Rectangle>,
|
||||
controls: Vec<Rectangle>,
|
||||
edges: Vec<(Point, Point)>,
|
||||
@@ -85,16 +82,14 @@ impl Frozen {
|
||||
) -> Self {
|
||||
let mut targets = Vec::new();
|
||||
let mut cards = Vec::new();
|
||||
let mut moving = Vec::new();
|
||||
for n in &view.nodes {
|
||||
if affected.contains_key(&n.id) {
|
||||
continue;
|
||||
}
|
||||
let region = Rectangle::new(
|
||||
Point::new(n.position.x, n.position.y),
|
||||
iced::Size::new(n.size.width, n.size.height),
|
||||
);
|
||||
if affected.contains_key(&n.id) {
|
||||
moving.push(region);
|
||||
continue;
|
||||
}
|
||||
let Some(e) = project.elements.get(&n.element) else {
|
||||
cards.push(region);
|
||||
continue;
|
||||
@@ -177,7 +172,6 @@ impl Frozen {
|
||||
}
|
||||
Self {
|
||||
targets,
|
||||
moving,
|
||||
cards,
|
||||
controls,
|
||||
edges,
|
||||
@@ -413,6 +407,7 @@ impl Candidate {
|
||||
Self::Performers(p) => &p.accepted,
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn index(&self) -> &view_options::ViewIndex {
|
||||
match self {
|
||||
Self::Singleton(p) => p.index(),
|
||||
@@ -530,8 +525,6 @@ pub(crate) struct Feedback {
|
||||
pub inspection_targets: Vec<usize>,
|
||||
pub text: String,
|
||||
pub refused: bool,
|
||||
/// Pointer-following outline only; it never authorizes a model change.
|
||||
pub ghost_delta: Option<Vector>,
|
||||
}
|
||||
impl Feedback {
|
||||
pub fn inspection_identity(&self) -> String {
|
||||
@@ -927,7 +920,7 @@ impl App {
|
||||
};
|
||||
let frozen = affected.map(|affected| {
|
||||
let d = &self.project().diagrams[&diagram];
|
||||
let routed = routing::route_diagram(self.project(), d, &view);
|
||||
let routed = crate::state_label::route_diagram(self.project(), d, &view);
|
||||
Arc::new(Frozen::capture(
|
||||
self.project(),
|
||||
d,
|
||||
@@ -1062,26 +1055,7 @@ impl App {
|
||||
viewport: stamp.viewport(), zoom: stamp.zoom,
|
||||
target: target.map(|i| plan::TargetPresentation { placement: active.frozen.as_ref().unwrap().targets[i].placement }),
|
||||
};
|
||||
let exact = candidate.place(requested, presentation);
|
||||
let placed = if destination == plan::Destination::OutsidePerformers
|
||||
&& matches!(&exact, Err(plan::Refusal::LayoutRequired(_)))
|
||||
{
|
||||
let affected = match &active.prepared {
|
||||
Prepared::Singleton(scope) => Some(&scope.placements),
|
||||
Prepared::Assignment(scope) => Some(scope.affected()),
|
||||
_ => None,
|
||||
};
|
||||
affected.and_then(|affected| {
|
||||
clearance::requests(
|
||||
(self.project(), self.document.view_index()),
|
||||
(candidate.project(), candidate.index()), stamp.context.diagram,
|
||||
affected, *anchor, requested,
|
||||
(stamp.world(press.start), stamp.world(pointer.unwrap())),
|
||||
).into_iter().find_map(|position| candidate.place(position, presentation).ok())
|
||||
}).map_or(exact, Ok)
|
||||
} else {
|
||||
exact
|
||||
}.map_err(|r| refusal(&r))?;
|
||||
let placed = candidate.place(requested, presentation).map_err(|r| refusal(&r))?;
|
||||
let label = candidate.label();
|
||||
if release {
|
||||
if let Some(command) = candidate.finish(self.project(), &placed).map_err(|r| refusal(&r))? {
|
||||
@@ -1092,7 +1066,7 @@ impl App {
|
||||
self.reconcile_multiselect();
|
||||
}
|
||||
} else {
|
||||
let routed = Arc::new(routing::route_diagram(candidate.project(), placed.diagram(), placed.view()));
|
||||
let routed = Arc::new(crate::state_label::route_diagram(candidate.project(), placed.diagram(), placed.view()));
|
||||
preview = Some(Arc::new(Preview { token, candidate, placement: placed.into_preview(), routed }));
|
||||
}
|
||||
Ok(label)
|
||||
@@ -1108,14 +1082,6 @@ impl App {
|
||||
} else {
|
||||
let inspection_targets = active.inspection_targets.clone();
|
||||
let frozen = active.frozen.clone();
|
||||
// A rejected intermediate position is not a return gesture. Keep the
|
||||
// last admitted scene and show current motion separately; release
|
||||
// always recomputes admission and never commits this retained view.
|
||||
if refused && in_bounds && preview.is_none() {
|
||||
preview = self.allocation_drag.preview.take();
|
||||
}
|
||||
let ghost_delta = (refused && in_bounds)
|
||||
.then(|| (pointer.unwrap() - press.start) * (1. / stamp.zoom));
|
||||
self.allocation_drag.active = Some(active);
|
||||
self.allocation_drag.preview = preview;
|
||||
self.allocation_drag.feedback = Some(Feedback {
|
||||
@@ -1126,7 +1092,6 @@ impl App {
|
||||
inspection_targets,
|
||||
text,
|
||||
refused,
|
||||
ghost_delta,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,595 +0,0 @@
|
||||
//! Bounded outgoing clearance proposals. The ordinary placement and semantic
|
||||
//! validators still decide whether any proposal may be previewed or committed.
|
||||
use iced::Point;
|
||||
use oa_core::{Id, NodePlacement, Position, Project, projection, view_options::ViewIndex};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
const GRID: f32 = 10.;
|
||||
const GAP: f32 = 10.;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Bounds {
|
||||
left: f32,
|
||||
top: f32,
|
||||
right: f32,
|
||||
bottom: f32,
|
||||
}
|
||||
|
||||
impl Bounds {
|
||||
fn node(node: &projection::ProjectedNode) -> Option<Self> {
|
||||
let result = Self {
|
||||
left: node.position.x,
|
||||
top: node.position.y,
|
||||
right: node.position.x + node.size.width,
|
||||
bottom: node.position.y + node.size.height,
|
||||
};
|
||||
([result.left, result.top, result.right, result.bottom]
|
||||
.iter()
|
||||
.all(|value| value.is_finite())
|
||||
&& result.right > result.left
|
||||
&& result.bottom > result.top)
|
||||
.then_some(result)
|
||||
}
|
||||
|
||||
fn union(self, other: Self) -> Self {
|
||||
Self {
|
||||
left: self.left.min(other.left),
|
||||
top: self.top.min(other.top),
|
||||
right: self.right.max(other.right),
|
||||
bottom: self.bottom.max(other.bottom),
|
||||
}
|
||||
}
|
||||
|
||||
fn overlaps(self, other: Self) -> bool {
|
||||
self.left < other.right
|
||||
&& other.left < self.right
|
||||
&& self.top < other.bottom
|
||||
&& other.top < self.bottom
|
||||
}
|
||||
}
|
||||
|
||||
/// Called only after an OutsidePerformers candidate fails ordinary placement.
|
||||
/// Coordinates use the frozen world transform. The entire affected footprint
|
||||
/// receives one anchor translation; this never moves or packs other cards.
|
||||
pub(super) fn requests(
|
||||
accepted: (&Project, &ViewIndex),
|
||||
candidate: (&Project, &ViewIndex),
|
||||
diagram: Id,
|
||||
affected: &BTreeMap<Id, NodePlacement>,
|
||||
anchor: Id,
|
||||
requested: Position,
|
||||
motion: (Point, Point),
|
||||
) -> Vec<Position> {
|
||||
let (accepted, accepted_index) = accepted;
|
||||
let (candidate, candidate_index) = candidate;
|
||||
let (start, pointer) = motion;
|
||||
if ![
|
||||
requested.x,
|
||||
requested.y,
|
||||
start.x,
|
||||
start.y,
|
||||
pointer.x,
|
||||
pointer.y,
|
||||
]
|
||||
.iter()
|
||||
.all(|value| value.is_finite())
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
let (Some(original), Some(base), Some(anchor)) = (
|
||||
accepted.diagrams.get(&diagram),
|
||||
candidate.diagrams.get(&diagram),
|
||||
affected.get(&anchor),
|
||||
) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let snapped = Position {
|
||||
x: (requested.x / GRID).round() * GRID,
|
||||
y: (requested.y / GRID).round() * GRID,
|
||||
};
|
||||
let delta = Position {
|
||||
x: snapped.x - anchor.position.x,
|
||||
y: snapped.y - anchor.position.y,
|
||||
};
|
||||
let before = projection::project_diagram_with_index(accepted, original, accepted_index);
|
||||
let before_nodes: BTreeMap<_, _> = before.nodes.iter().map(|node| (node.id, node)).collect();
|
||||
let mut sources = BTreeSet::new();
|
||||
for node in before
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|node| affected.contains_key(&node.id))
|
||||
{
|
||||
let mut parent = node.visual_parent;
|
||||
let mut seen = BTreeSet::new();
|
||||
while let Some(id) = parent {
|
||||
if !seen.insert(id) {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(node) = before_nodes.get(&id) else {
|
||||
break;
|
||||
};
|
||||
if !affected.contains_key(&id)
|
||||
&& accepted
|
||||
.elements
|
||||
.get(&node.element)
|
||||
.is_some_and(|element| element.kind().is_performer())
|
||||
{
|
||||
sources.insert(id);
|
||||
}
|
||||
parent = node.visual_parent;
|
||||
}
|
||||
}
|
||||
if sources.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
// Projection after responsibility removal supplies the former performer's
|
||||
// remaining enclosure, including other allocated work. Projecting the moved
|
||||
// block also includes the full visible descendant enclosure of every root.
|
||||
let mut moved = base.clone();
|
||||
for (id, captured) in affected {
|
||||
let Some(node) = moved.nodes.get_mut(id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
node.position = Position {
|
||||
x: captured.position.x + delta.x,
|
||||
y: captured.position.y + delta.y,
|
||||
};
|
||||
if ![
|
||||
node.position.x,
|
||||
node.position.y,
|
||||
node.size.width,
|
||||
node.size.height,
|
||||
]
|
||||
.iter()
|
||||
.all(|value| value.is_finite())
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
let after = projection::project_diagram_with_index(candidate, &moved, candidate_index);
|
||||
let mut footprint: Option<Bounds> = None;
|
||||
for node in after
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|node| affected.contains_key(&node.id))
|
||||
{
|
||||
let Some(bounds) = Bounds::node(node) else {
|
||||
return Vec::new();
|
||||
};
|
||||
footprint = Some(footprint.map_or(bounds, |previous| previous.union(bounds)));
|
||||
}
|
||||
let Some(footprint) = footprint else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut overlaps = Vec::new();
|
||||
for node in after.nodes.iter().filter(|node| sources.contains(&node.id)) {
|
||||
let (Some(old), Some(current)) = (Bounds::node(before_nodes[&node.id]), Bounds::node(node))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
if footprint.overlaps(current) {
|
||||
overlaps.push((old, current));
|
||||
}
|
||||
}
|
||||
if overlaps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Each entry is a half-plane actually crossed by the pointer. At a corner,
|
||||
// prefer the boundary reached first on the press-to-pointer segment. The
|
||||
// direction index is a stable tie-break, independent of placement UUIDs.
|
||||
let mut crossings = [None::<f32>; 4];
|
||||
for (old, _) in &overlaps {
|
||||
for (direction, initial, final_, boundary, outward) in [
|
||||
(
|
||||
0,
|
||||
start.x,
|
||||
pointer.x,
|
||||
old.left,
|
||||
pointer.x < old.left && start.x >= old.left,
|
||||
),
|
||||
(
|
||||
1,
|
||||
start.x,
|
||||
pointer.x,
|
||||
old.right,
|
||||
pointer.x >= old.right && start.x < old.right,
|
||||
),
|
||||
(
|
||||
2,
|
||||
start.y,
|
||||
pointer.y,
|
||||
old.top,
|
||||
pointer.y < old.top && start.y >= old.top,
|
||||
),
|
||||
(
|
||||
3,
|
||||
start.y,
|
||||
pointer.y,
|
||||
old.bottom,
|
||||
pointer.y >= old.bottom && start.y < old.bottom,
|
||||
),
|
||||
] {
|
||||
if outward {
|
||||
let fraction = (boundary - initial) / (final_ - initial);
|
||||
if fraction.is_finite() && (0. ..=1.).contains(&fraction) {
|
||||
crossings[direction] =
|
||||
Some(crossings[direction].map_or(fraction, |old| old.min(fraction)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut directions: Vec<_> = crossings
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(direction, fraction)| fraction.map(|fraction| (direction, fraction)))
|
||||
.collect();
|
||||
directions.sort_by(|a, b| a.1.total_cmp(&b.1).then(a.0.cmp(&b.0)));
|
||||
let mut result = Vec::new();
|
||||
for (direction, _) in directions {
|
||||
let mut corrected = snapped;
|
||||
match direction {
|
||||
0 => {
|
||||
let edge = overlaps
|
||||
.iter()
|
||||
.map(|(_, current)| current.left)
|
||||
.fold(f32::INFINITY, f32::min);
|
||||
corrected.x = ((snapped.x + edge - GAP - footprint.right) / GRID).floor() * GRID;
|
||||
}
|
||||
1 => {
|
||||
let edge = overlaps
|
||||
.iter()
|
||||
.map(|(_, current)| current.right)
|
||||
.fold(f32::NEG_INFINITY, f32::max);
|
||||
corrected.x = ((snapped.x + edge + GAP - footprint.left) / GRID).ceil() * GRID;
|
||||
}
|
||||
2 => {
|
||||
let edge = overlaps
|
||||
.iter()
|
||||
.map(|(_, current)| current.top)
|
||||
.fold(f32::INFINITY, f32::min);
|
||||
corrected.y = ((snapped.y + edge - GAP - footprint.bottom) / GRID).floor() * GRID;
|
||||
}
|
||||
3 => {
|
||||
let edge = overlaps
|
||||
.iter()
|
||||
.map(|(_, current)| current.bottom)
|
||||
.fold(f32::NEG_INFINITY, f32::max);
|
||||
corrected.y = ((snapped.y + edge + GAP - footprint.top) / GRID).ceil() * GRID;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if corrected.x.is_finite() && corrected.y.is_finite() && !result.contains(&corrected) {
|
||||
result.push(corrected);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::allocation_drag as plan;
|
||||
use oa_core::{Kind, Layer, ProjectionKind, RelationKind, Size};
|
||||
|
||||
struct Fixture {
|
||||
project: Project,
|
||||
diagram: Id,
|
||||
performer: Id,
|
||||
work: Id,
|
||||
node: Id,
|
||||
}
|
||||
|
||||
fn xy(x: f32, y: f32) -> Position {
|
||||
Position { x, y }
|
||||
}
|
||||
|
||||
fn fixture(layer: Layer) -> Fixture {
|
||||
let mut project = Project::empty();
|
||||
let (performer, work) = match layer {
|
||||
Layer::Oa => (Kind::Entity, Kind::Activity),
|
||||
Layer::Sa => (Kind::System, Kind::Function),
|
||||
};
|
||||
let performer = project
|
||||
.add_element(performer, layer, "Performer", None)
|
||||
.unwrap();
|
||||
let work = project.add_element(work, layer, "Work", None).unwrap();
|
||||
let diagram = project.ensure_diagram(layer);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation;
|
||||
let performer = project.place(diagram, performer, xy(100., 100.));
|
||||
project
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&performer)
|
||||
.unwrap()
|
||||
.size = Size {
|
||||
width: 400.,
|
||||
height: 250.,
|
||||
};
|
||||
let node = project.place(diagram, work, xy(160., 180.));
|
||||
let owner = project.diagrams[&diagram].nodes[&performer].element;
|
||||
project.relate(RelationKind::Allocation, "Responsibility", owner, work);
|
||||
Fixture {
|
||||
project,
|
||||
diagram,
|
||||
performer,
|
||||
work,
|
||||
node,
|
||||
}
|
||||
}
|
||||
|
||||
fn semantic(f: &Fixture) -> (plan::Scope, plan::SemanticPlan) {
|
||||
let scope = plan::capture(&f.project, f.diagram, f.work, f.node).unwrap();
|
||||
let semantic = plan::plan_semantics(
|
||||
&f.project,
|
||||
&scope,
|
||||
plan::Destination::OutsidePerformers,
|
||||
&BTreeMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
(scope, semantic)
|
||||
}
|
||||
|
||||
fn presentation() -> plan::PresentationContext {
|
||||
plan::PresentationContext {
|
||||
viewport: plan::Rect {
|
||||
position: xy(-2000., -2000.),
|
||||
size: Size {
|
||||
width: 5000.,
|
||||
height: 5000.,
|
||||
},
|
||||
},
|
||||
zoom: 1.,
|
||||
target: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_outgoing_edge_clears_the_source_and_passes_ordinary_placement() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let f = fixture(layer);
|
||||
let (scope, semantic) = semantic(&f);
|
||||
for (start, pointer, requested, expected) in [
|
||||
(
|
||||
Point::new(170., 210.),
|
||||
Point::new(99., 210.),
|
||||
xy(89., 180.),
|
||||
xy(-140., 180.),
|
||||
),
|
||||
(
|
||||
Point::new(340., 210.),
|
||||
Point::new(501., 210.),
|
||||
xy(321., 180.),
|
||||
xy(510., 180.),
|
||||
),
|
||||
(
|
||||
Point::new(200., 190.),
|
||||
Point::new(200., 99.),
|
||||
xy(160., 89.),
|
||||
xy(160., -10.),
|
||||
),
|
||||
(
|
||||
Point::new(200., 260.),
|
||||
Point::new(200., 351.),
|
||||
xy(160., 271.),
|
||||
xy(160., 360.),
|
||||
),
|
||||
] {
|
||||
assert!(matches!(
|
||||
plan::place(&semantic, requested, presentation()),
|
||||
Err(plan::Refusal::LayoutRequired(_))
|
||||
));
|
||||
let proposed = requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
requested,
|
||||
(start, pointer),
|
||||
);
|
||||
assert_eq!(proposed, vec![expected]);
|
||||
let placed = plan::place(&semantic, proposed[0], presentation()).unwrap();
|
||||
assert!(
|
||||
plan::finish(&semantic, &f.project, &placed)
|
||||
.unwrap()
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_shrink_uses_candidate_enclosure_not_frozen_target() {
|
||||
let mut f = fixture(Layer::Oa);
|
||||
f.project
|
||||
.diagrams
|
||||
.get_mut(&f.diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&f.performer)
|
||||
.unwrap()
|
||||
.size = Size {
|
||||
width: 224.,
|
||||
height: 94.,
|
||||
};
|
||||
let (scope, semantic) = semantic(&f);
|
||||
let proposed = requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
xy(229., 180.),
|
||||
(Point::new(340., 210.), Point::new(409., 210.)),
|
||||
);
|
||||
assert_eq!(proposed, vec![xy(340., 180.)]);
|
||||
plan::place(&semantic, proposed[0], presentation()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn corner_order_follows_boundary_crossing_and_is_deterministic() {
|
||||
let f = fixture(Layer::Sa);
|
||||
let (scope, semantic) = semantic(&f);
|
||||
// Right crossed at 160/161 of the segment; bottom at 90/91.
|
||||
let args = (
|
||||
xy(321., 271.),
|
||||
Point::new(340., 260.),
|
||||
Point::new(501., 351.),
|
||||
);
|
||||
let proposed = requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
args.0,
|
||||
(args.1, args.2),
|
||||
);
|
||||
assert_eq!(proposed, vec![xy(320., 360.), xy(510., 270.)]);
|
||||
assert_eq!(
|
||||
proposed,
|
||||
requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
args.0,
|
||||
(args.1, args.2)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_crossing_no_overlap_no_source_and_nonfinite_requests_are_empty() {
|
||||
let f = fixture(Layer::Oa);
|
||||
let (scope, semantic) = semantic(&f);
|
||||
for (requested, pointer) in [
|
||||
(xy(170., 180.), Point::new(210., 210.)),
|
||||
(xy(700., 180.), Point::new(740., 210.)),
|
||||
(xy(f32::NAN, 180.), Point::new(99., 210.)),
|
||||
(xy(89., 180.), Point::new(f32::INFINITY, 210.)),
|
||||
] {
|
||||
assert!(
|
||||
requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
requested,
|
||||
(Point::new(200., 210.), pointer)
|
||||
)
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
requests(
|
||||
(semantic.project(), semantic.index()),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
xy(89., 180.),
|
||||
(Point::new(200., 210.), Point::new(99., 210.))
|
||||
)
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descendant_footprint_controls_clearance_without_mutating_projects() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = fixture(layer);
|
||||
let kind = f.project.elements[&f.work].kind();
|
||||
let child = f
|
||||
.project
|
||||
.add_element(kind, layer, "Child", Some(f.work))
|
||||
.unwrap();
|
||||
let child_node = f.project.place(f.diagram, child, xy(240., 270.));
|
||||
// First-child allocation transfer is normally performed by commands;
|
||||
// this fixture expresses the resulting leaf allocation directly.
|
||||
f.project.relations.clear();
|
||||
let owner = f.project.diagrams[&f.diagram].nodes[&f.performer].element;
|
||||
f.project.relate(
|
||||
RelationKind::Allocation,
|
||||
"Leaf responsibility",
|
||||
owner,
|
||||
child,
|
||||
);
|
||||
let (scope, semantic) = semantic(&f);
|
||||
assert!(scope.placements.contains_key(&child_node));
|
||||
let before = f.project.clone();
|
||||
let candidate = semantic.project().clone();
|
||||
let proposed = requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(&candidate, semantic.index()),
|
||||
f.diagram,
|
||||
&scope.placements,
|
||||
f.node,
|
||||
xy(89., 180.),
|
||||
(Point::new(170., 210.), Point::new(99., 210.)),
|
||||
);
|
||||
assert_eq!(proposed, vec![xy(-240., 180.)]);
|
||||
let placed = plan::place(&semantic, proposed[0], presentation()).unwrap();
|
||||
let root = &placed.diagram.nodes[&f.node];
|
||||
let child = &placed.diagram.nodes[&child_node];
|
||||
assert_eq!(child.position.x - root.position.x, 80.);
|
||||
assert_eq!(child.position.y - root.position.y, 90.);
|
||||
assert_eq!(f.project, before);
|
||||
assert_eq!(semantic.project(), &candidate);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_selected_roots_keep_one_rigid_group_translation() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = fixture(layer);
|
||||
let kind = f.project.elements[&f.work].kind();
|
||||
let second = f
|
||||
.project
|
||||
.add_element(kind, layer, "Second work", None)
|
||||
.unwrap();
|
||||
let second_node = f.project.place(f.diagram, second, xy(240., 300.));
|
||||
let owner = f.project.diagrams[&f.diagram].nodes[&f.performer].element;
|
||||
f.project.relate(
|
||||
RelationKind::Allocation,
|
||||
"Second responsibility",
|
||||
owner,
|
||||
second,
|
||||
);
|
||||
let selected = BTreeMap::from([(f.node, f.work), (second_node, second)]);
|
||||
let scope =
|
||||
plan::assignment::capture(&f.project, f.diagram, &selected, f.node).unwrap();
|
||||
let semantic = plan::assignment::plan_semantics(
|
||||
&scope,
|
||||
plan::Destination::OutsidePerformers,
|
||||
&BTreeMap::new(),
|
||||
)
|
||||
.unwrap();
|
||||
let proposed = requests(
|
||||
(&f.project, &ViewIndex::new(&f.project)),
|
||||
(semantic.project(), semantic.index()),
|
||||
f.diagram,
|
||||
scope.affected(),
|
||||
f.node,
|
||||
xy(89., 180.),
|
||||
(Point::new(170., 210.), Point::new(99., 210.)),
|
||||
);
|
||||
assert_eq!(proposed, vec![xy(-220., 180.)]);
|
||||
let placed = plan::assignment::place(&semantic, proposed[0], presentation()).unwrap();
|
||||
let root = &placed.diagram.nodes[&f.node];
|
||||
let second = &placed.diagram.nodes[&second_node];
|
||||
assert_eq!(second.position.x - root.position.x, 80.);
|
||||
assert_eq!(second.position.y - root.position.y, 120.);
|
||||
assert!(
|
||||
plan::assignment::finish(&semantic, &f.project, &placed)
|
||||
.unwrap()
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,117 +30,6 @@ async fn mount() -> (Fixture, Mounted) {
|
||||
mounted.settle(&mut f.app).await;
|
||||
(f, mounted)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn actual_tree_outgoing_boundary_motion_preview_release_and_cancel() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = outgoing_fixture(layer);
|
||||
f.app.layer = layer;
|
||||
f.app.window_size = iced::Size::new(1440., 900.);
|
||||
let _ = f.app.update(Message::Select(f.work));
|
||||
let mut mounted = Mounted::new(&mut f.app).await;
|
||||
f.app.zoom = 0.75;
|
||||
f.app.pan = Vector::new(20., 15.);
|
||||
f.app.fit_to_view = false;
|
||||
mounted.settle(&mut f.app).await;
|
||||
let original = f.app.project().clone();
|
||||
let source = f.app.document.bundle().clone();
|
||||
let from = at(&mut mounted, &f, xy(660., 225.));
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[
|
||||
Input::Mouse(mouse::Event::CursorMoved { position: from }),
|
||||
Input::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
||||
],
|
||||
mouse::Cursor::Available(from),
|
||||
)
|
||||
.await;
|
||||
// Separate input/update/layout turns expose the held boundary transition
|
||||
// that a one-batch distant drop cannot exercise.
|
||||
for x in [620., 560., 510., 499., 490.] {
|
||||
let to = at(&mut mounted, &f, xy(x, 225.));
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[Input::Mouse(mouse::Event::CursorMoved { position: to })],
|
||||
mouse::Cursor::Available(to),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(f.app.project(), &original);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(
|
||||
!f.app.allocation_drag.feedback.as_ref().unwrap().refused,
|
||||
"{layer:?} x={x}: {}",
|
||||
f.app.status
|
||||
);
|
||||
assert!(f.app.allocation_drag.preview.is_some());
|
||||
let _ = mounted.pixels(&f.app);
|
||||
}
|
||||
let expected = f
|
||||
.app
|
||||
.allocation_drag
|
||||
.preview
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.placement
|
||||
.diagram
|
||||
.clone();
|
||||
let to = at(&mut mounted, &f, xy(490., 225.));
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[Input::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Left,
|
||||
))],
|
||||
mouse::Cursor::Available(to),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(&f.app.project().diagrams[&f.diagram], &expected);
|
||||
assert!(
|
||||
!f.app
|
||||
.project()
|
||||
.relations
|
||||
.values()
|
||||
.any(|r| r.kind() == RelationKind::Allocation && r.target() == f.work)
|
||||
);
|
||||
let _ = f.app.update(Message::Undo);
|
||||
mounted.settle(&mut f.app).await;
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
let from = at(&mut mounted, &f, xy(660., 225.));
|
||||
let to = at(&mut mounted, &f, xy(490., 225.));
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[
|
||||
Input::Mouse(mouse::Event::CursorMoved { position: from }),
|
||||
Input::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
||||
Input::Mouse(mouse::Event::CursorMoved { position: to }),
|
||||
],
|
||||
mouse::Cursor::Available(to),
|
||||
)
|
||||
.await;
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[key(keyboard::key::Named::Escape)],
|
||||
mouse::Cursor::Available(to),
|
||||
)
|
||||
.await;
|
||||
mounted
|
||||
.batch(
|
||||
&mut f.app,
|
||||
&[Input::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Left,
|
||||
))],
|
||||
mouse::Cursor::Available(to),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(f.app.allocation_drag.preview.is_none());
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
fn canvas_bounds(mounted: &mut Mounted, app: &App) -> Rectangle {
|
||||
mounted
|
||||
.geometry(app)
|
||||
@@ -592,7 +481,6 @@ async fn actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canva
|
||||
if pan.y == -100. {
|
||||
// A genuine clipping refusal in the same held gesture, then
|
||||
// recovery at the original exact point: no camera rebasing.
|
||||
let retained = f.app.allocation_drag.preview.clone().unwrap();
|
||||
let clipped = at(&mut mounted, &f, xy(160., 110.));
|
||||
mounted
|
||||
.batch(
|
||||
@@ -604,19 +492,7 @@ async fn actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canva
|
||||
)
|
||||
.await;
|
||||
assert!(f.app.allocation_drag.feedback.as_ref().unwrap().refused);
|
||||
assert!(Arc::ptr_eq(
|
||||
f.app.allocation_drag.preview.as_ref().unwrap(),
|
||||
&retained
|
||||
));
|
||||
assert!(
|
||||
f.app
|
||||
.allocation_drag
|
||||
.feedback
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.ghost_delta
|
||||
.is_some()
|
||||
);
|
||||
assert!(f.app.allocation_drag.preview.is_none());
|
||||
assert_eq!(canvas_bounds(&mut mounted, &f.app), canvas);
|
||||
assert_eq!(destination_bounds(&mut mounted, &f.app), slot);
|
||||
assert_eq!(f.app.project(), &before);
|
||||
|
||||
@@ -1239,7 +1239,6 @@ fn destination_identity_does_not_describe_blocked_or_outside_viewport_as_unalloc
|
||||
inspection_targets: vec![],
|
||||
text: "A card blocks this drop".into(),
|
||||
refused: true,
|
||||
ghost_delta: None,
|
||||
};
|
||||
assert_eq!(feedback.destination_identity(), "No eligible destination");
|
||||
feedback.text = "Release inside the captured canvas".into();
|
||||
@@ -1247,361 +1246,3 @@ fn destination_identity_does_not_describe_blocked_or_outside_viewport_as_unalloc
|
||||
feedback.refused = false;
|
||||
assert_eq!(feedback.destination_identity(), "Outside performers");
|
||||
}
|
||||
|
||||
fn outgoing_fixture(layer: Layer) -> Fixture {
|
||||
let mut f = fixture(layer);
|
||||
drag(&mut f, screen(xy(160., 190.)));
|
||||
let mut project = f.app.project().clone();
|
||||
let d = project.diagrams.get_mut(&f.diagram).unwrap();
|
||||
let performer = d.nodes.values_mut().find(|n| n.element == f.a).unwrap();
|
||||
performer.position = xy(500., 100.);
|
||||
performer.size = Size {
|
||||
width: 300.,
|
||||
height: 300.,
|
||||
};
|
||||
d.nodes.get_mut(&f.node).unwrap().position = xy(540., 190.);
|
||||
replace(&mut f, project);
|
||||
f
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outgoing_boundary_drop_clears_source_without_changing_other_placements() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = outgoing_fixture(layer);
|
||||
let before = f.app.project().clone();
|
||||
let source = f.app.document.bundle().clone();
|
||||
let from = screen(xy(660., 225.));
|
||||
let to = screen(xy(490., 225.));
|
||||
let mut events = batch(&f.app, from, to);
|
||||
let release = events.pop().unwrap();
|
||||
send(&mut f.app, events);
|
||||
let feedback = f.app.allocation_drag.feedback.as_ref().unwrap();
|
||||
assert!(!feedback.refused, "{}", feedback.text);
|
||||
let preview = f.app.allocation_drag.preview.as_ref().unwrap();
|
||||
let expected_diagram = preview.placement.diagram.clone();
|
||||
let work = &expected_diagram.nodes[&f.node];
|
||||
assert!(work.position.x + work.size.width <= 490.);
|
||||
assert_eq!(work.position.y, 190.);
|
||||
for (id, node) in &before.diagrams[&f.diagram].nodes {
|
||||
if *id != f.node {
|
||||
assert_eq!(&expected_diagram.nodes[id], node);
|
||||
}
|
||||
}
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
let _ = f.app.update(release);
|
||||
assert_eq!(&f.app.project().diagrams[&f.diagram], &expected_diagram);
|
||||
assert!(
|
||||
!f.app
|
||||
.project()
|
||||
.relations
|
||||
.values()
|
||||
.any(|r| r.kind() == RelationKind::Allocation && r.target() == f.work)
|
||||
);
|
||||
let after = f.app.project().clone();
|
||||
let saved = f.app.document.bundle().clone();
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(!f.app.document.can_undo());
|
||||
let _ = f.app.update(Message::Redo);
|
||||
assert_eq!(f.app.project(), &after);
|
||||
assert_eq!(f.app.document.bundle(), &saved);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outgoing_clearance_collision_refuses_release_without_source_or_history_mutation() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = outgoing_fixture(layer);
|
||||
let mut project = f.app.project().clone();
|
||||
let obstacle = project
|
||||
.add_element(
|
||||
project.elements[&f.work].kind(),
|
||||
layer,
|
||||
"Unrelated work",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let obstacle_node = project.place(f.diagram, obstacle, xy(240., 190.));
|
||||
replace(&mut f, project);
|
||||
// A refused release must preserve an existing redo branch, too.
|
||||
let _ = f.app.update(Message::Command(Command::Rename(
|
||||
f.work,
|
||||
"Temporary".into(),
|
||||
)));
|
||||
let _ = f.app.update(Message::Undo);
|
||||
let before = f.app.project().clone();
|
||||
let source = f.app.document.bundle().clone();
|
||||
let snapshot = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let from = screen(xy(660., 225.));
|
||||
let to = screen(xy(490., 225.));
|
||||
let mut events = batch(&f.app, from, to);
|
||||
let release = events.pop().unwrap();
|
||||
send(&mut f.app, events);
|
||||
|
||||
let active = f.app.allocation_drag.active.as_ref().unwrap();
|
||||
let p = &active.press;
|
||||
let frozen = active.frozen.as_ref().unwrap();
|
||||
let owner = frozen.targets.iter().find(|t| t.element == f.a).unwrap();
|
||||
assert_eq!(p.start, from);
|
||||
assert!(contains(owner.region, p.stamp.world(from)));
|
||||
assert!(p.stamp.world(to).x < owner.region.x);
|
||||
assert_eq!(
|
||||
frozen.resolve(p.stamp, Some(to), false),
|
||||
Hit::Destination(plan::Destination::OutsidePerformers, None),
|
||||
"The pointer must reach empty canvas, not be occluded by the obstacle"
|
||||
);
|
||||
let candidate = active
|
||||
.cache
|
||||
.iter()
|
||||
.find(|(destination, _)| *destination == plan::Destination::OutsidePerformers)
|
||||
.unwrap()
|
||||
.1
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let affected = match &active.prepared {
|
||||
Prepared::Singleton(scope) => &scope.placements,
|
||||
Prepared::Assignment(scope) => scope.affected(),
|
||||
_ => panic!("expected assignment gesture"),
|
||||
};
|
||||
let requested = xy(370., 190.);
|
||||
let presentation = plan::PresentationContext {
|
||||
viewport: p.stamp.viewport(),
|
||||
zoom: p.stamp.zoom,
|
||||
target: None,
|
||||
};
|
||||
assert!(matches!(
|
||||
candidate.place(requested, presentation),
|
||||
Err(plan::Refusal::LayoutRequired(_))
|
||||
));
|
||||
let proposed = clearance::requests(
|
||||
(f.app.project(), f.app.document.view_index()),
|
||||
(candidate.project(), candidate.index()),
|
||||
f.diagram,
|
||||
affected,
|
||||
f.node,
|
||||
requested,
|
||||
(p.stamp.world(from), p.stamp.world(to)),
|
||||
);
|
||||
assert_eq!(
|
||||
proposed.len(),
|
||||
1,
|
||||
"a genuine left crossing must propose clearance"
|
||||
);
|
||||
assert!(proposed[0].x < requested.x);
|
||||
let work = &before.diagrams[&f.diagram].nodes[&f.node];
|
||||
let obstacle = &before.diagrams[&f.diagram].nodes[&obstacle_node];
|
||||
assert!(obstacle.position.x + obstacle.size.width < 490.);
|
||||
assert!(proposed[0].x + work.size.width <= owner.region.x - 10.);
|
||||
assert!(proposed[0].x < obstacle.position.x + obstacle.size.width);
|
||||
assert!(obstacle.position.x < proposed[0].x + work.size.width);
|
||||
assert_eq!(proposed[0].y, obstacle.position.y);
|
||||
assert!(matches!(
|
||||
candidate.place(proposed[0], presentation),
|
||||
Err(plan::Refusal::LayoutRequired(message)) if message.contains("overlapping cards")
|
||||
));
|
||||
let feedback = f.app.allocation_drag.feedback.as_ref().unwrap();
|
||||
assert!(feedback.refused, "{}", feedback.text);
|
||||
assert!(feedback.ghost_delta.is_some());
|
||||
assert!(f.app.allocation_drag.preview.is_none());
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(Arc::ptr_eq(&snapshot, &f.app.document.snapshot()));
|
||||
let _ = f.app.update(release);
|
||||
assert!(
|
||||
f.app.status.contains("overlapping cards"),
|
||||
"{}",
|
||||
f.app.status
|
||||
);
|
||||
assert!(f.app.allocation_drag.active.is_none());
|
||||
assert!(f.app.allocation_drag.preview.is_none());
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert!(Arc::ptr_eq(&snapshot, &f.app.document.snapshot()));
|
||||
assert!(!f.app.document.can_undo());
|
||||
assert!(f.app.document.can_redo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outgoing_clearance_preserves_source_enclosure_and_allocated_sibling() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = outgoing_fixture(layer);
|
||||
let mut project = f.app.project().clone();
|
||||
let sibling = project
|
||||
.add_element(
|
||||
project.elements[&f.work].kind(),
|
||||
layer,
|
||||
"Remaining work",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let sibling_node = project.place(f.diagram, sibling, xy(540., 450.));
|
||||
let allocation = project.relate(
|
||||
RelationKind::Allocation,
|
||||
"Keep responsibility",
|
||||
f.a,
|
||||
sibling,
|
||||
);
|
||||
replace(&mut f, project);
|
||||
let before = f.app.project().clone();
|
||||
let source = f.app.document.bundle().clone();
|
||||
let view = projection::project_view(&before, f.diagram);
|
||||
let owner = view.nodes.iter().find(|n| n.element == f.a).unwrap();
|
||||
let bottom = owner.position.y + owner.size.height;
|
||||
let stored_owner = &before.diagrams[&f.diagram].nodes[&owner.id];
|
||||
assert!(
|
||||
bottom > stored_owner.position.y + stored_owner.size.height,
|
||||
"The retained sibling must enlarge the projected source enclosure"
|
||||
);
|
||||
let from = screen(xy(660., 225.));
|
||||
let to = screen(xy(660., bottom + 1.));
|
||||
let mut events = batch(&f.app, from, to);
|
||||
let release = events.pop().unwrap();
|
||||
send(&mut f.app, events);
|
||||
let active = f.app.allocation_drag.active.as_ref().unwrap();
|
||||
let p = &active.press;
|
||||
let frozen = active.frozen.as_ref().unwrap();
|
||||
let target = frozen.targets.iter().find(|t| t.element == f.a).unwrap();
|
||||
assert_eq!(p.start, from);
|
||||
assert!(contains(target.region, p.stamp.world(from)));
|
||||
assert!(p.stamp.world(to).y >= target.region.y + target.region.height);
|
||||
assert_eq!(
|
||||
frozen.resolve(p.stamp, Some(to), false),
|
||||
Hit::Destination(plan::Destination::OutsidePerformers, None)
|
||||
);
|
||||
let preview = f.app.allocation_drag.preview.as_ref().expect(&f.app.status);
|
||||
assert!(!f.app.allocation_drag.feedback.as_ref().unwrap().refused);
|
||||
let requested = xy(540., 190. + to.y - from.y);
|
||||
assert!(matches!(
|
||||
preview.candidate.place(
|
||||
requested,
|
||||
plan::PresentationContext {
|
||||
viewport: p.stamp.viewport(),
|
||||
zoom: p.stamp.zoom,
|
||||
target: None,
|
||||
}
|
||||
),
|
||||
Err(plan::Refusal::LayoutRequired(_))
|
||||
));
|
||||
let moved = &preview.placement.diagram.nodes[&f.node];
|
||||
assert_eq!(moved.position.x, 540.);
|
||||
assert!(moved.position.y >= bottom + 10.);
|
||||
assert!(moved.position.y > requested.y);
|
||||
assert_eq!(
|
||||
preview
|
||||
.placement
|
||||
.view
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|n| n.id == owner.id)
|
||||
.unwrap(),
|
||||
owner
|
||||
);
|
||||
let remaining = preview
|
||||
.placement
|
||||
.view
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|n| n.id == sibling_node)
|
||||
.unwrap();
|
||||
assert_eq!(remaining.visual_parent, Some(owner.id));
|
||||
assert_eq!(
|
||||
remaining.position,
|
||||
before.diagrams[&f.diagram].nodes[&sibling_node].position
|
||||
);
|
||||
assert_eq!(
|
||||
&preview.project().relations[&allocation],
|
||||
&before.relations[&allocation]
|
||||
);
|
||||
for (id, node) in &before.diagrams[&f.diagram].nodes {
|
||||
if *id != f.node {
|
||||
assert_eq!(&preview.placement.diagram.nodes[id], node);
|
||||
}
|
||||
}
|
||||
let mut expected = preview.project().clone();
|
||||
expected
|
||||
.diagrams
|
||||
.insert(f.diagram, preview.placement.diagram.clone());
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
let _ = f.app.update(release);
|
||||
assert_eq!(f.app.project(), &expected, "{}", f.app.status);
|
||||
assert_eq!(f.app.document.revision(), 1);
|
||||
assert_eq!(
|
||||
validation::effective_performer(f.app.project(), f.work),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
validation::effective_performer(f.app.project(), sibling),
|
||||
Some(f.a)
|
||||
);
|
||||
assert_eq!(
|
||||
&f.app.project().relations[&allocation],
|
||||
&before.relations[&allocation]
|
||||
);
|
||||
let after = f.app.document.bundle().clone();
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(!f.app.document.can_undo());
|
||||
let _ = f.app.update(Message::Redo);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), &after);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refused_motion_retains_last_scene_but_release_never_commits_it() {
|
||||
let mut f = outgoing_fixture(Layer::Sa);
|
||||
let mut project = f.app.project().clone();
|
||||
let obstacle = project
|
||||
.add_element(Kind::Function, Layer::Sa, "Unrelated work", None)
|
||||
.unwrap();
|
||||
project.place(f.diagram, obstacle, xy(1250., 120.));
|
||||
replace(&mut f, project);
|
||||
let original = f.app.project().clone();
|
||||
let source = f.app.document.bundle().clone();
|
||||
let p = admit_press(&mut f);
|
||||
let clear = screen(xy(450., 225.));
|
||||
let _ = f.app.update(Message::WorkDrag(Event::Preview(
|
||||
p.stamp,
|
||||
p.token,
|
||||
Some(clear),
|
||||
)));
|
||||
let last = f.app.allocation_drag.preview.clone().expect(&f.app.status);
|
||||
// Another work card blocks this location: the prior legal outgoing scene is display
|
||||
// state only, and must not become a fallback command on release.
|
||||
let blocked = screen(xy(1260., 130.));
|
||||
let _ = f.app.update(Message::WorkDrag(Event::Preview(
|
||||
p.stamp,
|
||||
p.token,
|
||||
Some(blocked),
|
||||
)));
|
||||
assert!(f.app.allocation_drag.feedback.as_ref().unwrap().refused);
|
||||
assert!(
|
||||
f.app
|
||||
.allocation_drag
|
||||
.feedback
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.ghost_delta
|
||||
.is_some()
|
||||
);
|
||||
assert!(Arc::ptr_eq(
|
||||
f.app.allocation_drag.preview.as_ref().unwrap(),
|
||||
&last
|
||||
));
|
||||
let _ = f.app.update(Message::WorkDrag(Event::Release(
|
||||
p.stamp,
|
||||
p.token,
|
||||
Some(blocked),
|
||||
)));
|
||||
assert_eq!(f.app.project(), &original);
|
||||
assert_eq!(f.app.document.bundle(), &source);
|
||||
assert!(!f.app.document.can_undo());
|
||||
assert!(f.app.allocation_drag.preview.is_none());
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl App {
|
||||
};
|
||||
let diagram = &self.project().diagrams[&context.diagram];
|
||||
let view = projection::project_diagram(self.project(), diagram);
|
||||
let bounds = routing::route_diagram(self.project(), diagram, &view).bounds;
|
||||
let bounds = crate::state_label::route_diagram(self.project(), diagram, &view).bounds;
|
||||
let viewport = self.behavior.diagram.viewport.unwrap_or(iced::Size::new(
|
||||
(self.window_size.width - 610.).max(280.),
|
||||
(self.window_size.height - 320.).max(240.),
|
||||
|
||||
@@ -744,7 +744,7 @@ pub(crate) fn diagram_usages(
|
||||
}
|
||||
}
|
||||
}
|
||||
for edge in routing::route_diagram(project, diagram, view)
|
||||
for edge in crate::state_label::route_diagram(project, diagram, view)
|
||||
.edges
|
||||
.values()
|
||||
{
|
||||
@@ -754,10 +754,10 @@ pub(crate) fn diagram_usages(
|
||||
if edge.id == subject || edge.label.text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// Routing already shortens external summaries before the canvas applies
|
||||
// its width limit. Compare against precisely that doubly shortened text.
|
||||
let painted = short(&edge.label.text, edge.label.bounds.size.width - 10., 10.);
|
||||
if let Some(role) = transition_label_role_in_text(project, relation, subject, &painted) {
|
||||
// The shared plan paints the complete retained raw summary. Keep its
|
||||
// original prefix here: display normalization must not shift field offsets.
|
||||
let painted = &edge.label.text;
|
||||
if let Some(role) = transition_label_role_in_text(project, relation, subject, painted) {
|
||||
result.push(Usage {
|
||||
subject,
|
||||
surface: Surface::Saved(diagram.id),
|
||||
@@ -1078,7 +1078,7 @@ impl StateCanvas<'_> {
|
||||
}
|
||||
let view = projection::project_diagram(self.project, &diagram);
|
||||
let routed = if let Some(gesture) = state.bend.as_ref().filter(|_| bend_preview.is_some()) {
|
||||
routing::route_edge_preview(
|
||||
crate::state_label::route_edge_preview(
|
||||
self.project,
|
||||
&diagram,
|
||||
&view,
|
||||
@@ -1086,7 +1086,7 @@ impl StateCanvas<'_> {
|
||||
gesture.route.edge,
|
||||
)
|
||||
} else {
|
||||
routing::route_diagram(self.project, &diagram, &view)
|
||||
crate::state_label::route_diagram(self.project, &diagram, &view)
|
||||
};
|
||||
let symbol_labels = symbol_labels(self.project, &view, &routed);
|
||||
let scene = Arc::new(Scene {
|
||||
@@ -1153,6 +1153,19 @@ impl StateCanvas<'_> {
|
||||
.map(|n| n.element)
|
||||
}
|
||||
fn hit(&self, scene: &Scene, world: Point) -> Option<Hit> {
|
||||
// State labels paint opaque backgrounds above nodes and rows. Select
|
||||
// the last painted label at an overlapping pixel, including its padding.
|
||||
// Endpoint/bend handling remains ahead of this ordinary hit path.
|
||||
if let Some(edge) = scene.routed.edges.values().rev().find(|edge| {
|
||||
edge.label.state_layout.is_some()
|
||||
&& Rectangle::new(
|
||||
point(edge.label.bounds.position),
|
||||
Size::new(edge.label.bounds.size.width, edge.label.bounds.size.height),
|
||||
)
|
||||
.contains(world)
|
||||
}) {
|
||||
return Some(Hit::Transition(edge.id));
|
||||
}
|
||||
let node = self.node_at(scene, world);
|
||||
if let Some(node) = node {
|
||||
if let Some((relation, _)) = state_action_rows(self.project, node)
|
||||
@@ -2107,21 +2120,29 @@ impl canvas::Program<Event> for StateCanvas<'_> {
|
||||
Size::new(label.bounds.size.width, label.bounds.size.height),
|
||||
Color::WHITE,
|
||||
);
|
||||
let width = label.bounds.size.width - 10.;
|
||||
label_text(
|
||||
frame,
|
||||
short(self.project.named(edge.id), width, 10.),
|
||||
point(label.bounds.position) + Vector::new(5., 0.),
|
||||
10.,
|
||||
color,
|
||||
);
|
||||
label_text(
|
||||
frame,
|
||||
short(&label.text, width, 10.),
|
||||
point(label.bounds.position) + Vector::new(5., 12.),
|
||||
10.,
|
||||
style::MUTED,
|
||||
);
|
||||
if let Some(plan) = &label.state_layout {
|
||||
for (line, offset, ink) in plan
|
||||
.title_lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, line)| (line, plan.title_line_position(index), color))
|
||||
.chain(plan.summary_lines.iter().enumerate().map(|(index, line)| {
|
||||
(line, plan.summary_line_position(index), style::MUTED)
|
||||
}))
|
||||
{
|
||||
frame.fill_text(Text {
|
||||
content: line.clone(),
|
||||
position: point(label.bounds.position)
|
||||
+ Vector::new(offset.x, offset.y),
|
||||
size: routing::StateLabelLayout::FONT_SIZE.into(),
|
||||
line_height: iced::widget::text::LineHeight::Absolute(
|
||||
routing::StateLabelLayout::LINE_HEIGHT.into(),
|
||||
),
|
||||
color: ink,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
if active
|
||||
&& self
|
||||
.current_selection()
|
||||
@@ -2595,6 +2616,7 @@ mod tests {
|
||||
|
||||
include!("state_bend_tests.rs");
|
||||
include!("state_multiselect_canvas_tests.rs");
|
||||
include!("state_label_canvas_tests.rs");
|
||||
|
||||
struct Fixture {
|
||||
project: Project,
|
||||
@@ -2803,6 +2825,45 @@ mod tests {
|
||||
assert!(!retained_label_field("1234A678", "1234…", 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_label_usage_keeps_raw_crlf_prefix_for_later_typed_fields() {
|
||||
let mut f = fixture();
|
||||
let first = f
|
||||
.project
|
||||
.add_element(Kind::Event, Layer::Sa, "漢\r\nB", None)
|
||||
.unwrap();
|
||||
let second = f
|
||||
.project
|
||||
.add_element(Kind::Event, Layer::Sa, "Visible", None)
|
||||
.unwrap();
|
||||
let edge = f
|
||||
.project
|
||||
.relate(RelationKind::StateTransition, "Ready\nfor review", f.a, f.b);
|
||||
if let RelationBody::StateTransition { triggers, .. } =
|
||||
&mut f.project.relations.get_mut(&edge).unwrap().body
|
||||
{
|
||||
*triggers = vec![first, second];
|
||||
}
|
||||
let diagram = &f.project.diagrams[&f.diagram];
|
||||
let view = projection::project_diagram(&f.project, diagram);
|
||||
let routed = crate::state_label::route_diagram(&f.project, diagram, &view);
|
||||
let label = &routed.edges[&edge].label;
|
||||
assert_eq!(label.text, "External · 漢\r\nB OR Visible");
|
||||
assert_eq!(
|
||||
label.state_layout.as_ref().unwrap().summary_lines,
|
||||
["External · 漢", "B OR Visible"]
|
||||
);
|
||||
let usages = diagram_usages(&f.project, diagram, &view, second);
|
||||
assert!(
|
||||
usages
|
||||
.iter()
|
||||
.any(|u| u.target == Target::Edge(edge) && u.role == "transition trigger")
|
||||
);
|
||||
// Feeding normalized display text to raw-prefix matching would lose
|
||||
// the second field at CRLF. Its raw name and source remain unchanged.
|
||||
assert_eq!(f.project.named(first), "漢\r\nB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagram_usage_external_labels_obey_routing_cutoff_while_internal_rows_retain_full_summary() {
|
||||
use crate::diagram_usage::Target;
|
||||
@@ -2837,9 +2898,9 @@ mod tests {
|
||||
}
|
||||
let diagram = &f.project.diagrams[&f.diagram];
|
||||
let view = projection::project_diagram(&f.project, diagram);
|
||||
let routed = routing::route_diagram(&f.project, diagram, &view);
|
||||
let routed = crate::state_label::route_diagram(&f.project, diagram, &view);
|
||||
let edge = &routed.edges[&external];
|
||||
let painted = short(&edge.label.text, edge.label.bounds.size.width - 10., 10.);
|
||||
let painted = &edge.label.text;
|
||||
// Independent painted-text oracle: 11 prefix characters + 35 As, then
|
||||
// only the space before OR survives the routing limit.
|
||||
assert_eq!(edge.label.text, format!("External · {} …", "A".repeat(35)));
|
||||
|
||||
@@ -328,7 +328,7 @@ pub fn bend_route(
|
||||
}
|
||||
let stored = project.diagrams.get(&diagram)?;
|
||||
let view = projection::project_diagram(project, stored);
|
||||
let geometry = oa_core::routing::route_diagram(project, stored, &view)
|
||||
let geometry = crate::state_label::route_diagram(project, stored, &view)
|
||||
.edges
|
||||
.remove(&relation)?;
|
||||
(!geometry.custom_suspended).then_some(geometry)
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
// Included in state_canvas::tests. Fixed world points exercise the real scene
|
||||
// and Program input, rather than choosing a successful point from returned bounds.
|
||||
|
||||
fn multiline_label_fixture() -> (Fixture, Id, Id) {
|
||||
let mut f = fixture();
|
||||
let work = f
|
||||
.project
|
||||
.add_element(Kind::Function, Layer::Sa, "Record request", None)
|
||||
.unwrap();
|
||||
let action = f
|
||||
.project
|
||||
.relate(RelationKind::StateAction, "Record shared work", f.a, work);
|
||||
if let RelationBody::StateAction { phase, .. } =
|
||||
&mut f.project.relations.get_mut(&action).unwrap().body
|
||||
{
|
||||
*phase = StateActionKind::Entry;
|
||||
}
|
||||
let generated = f
|
||||
.project
|
||||
.relate(RelationKind::StateTransition, "Ready\nfor review", f.b, f.c);
|
||||
let edge = Id::from_u128(36_001);
|
||||
let mut relation = f.project.relations.remove(&generated).unwrap();
|
||||
relation.id = edge;
|
||||
if let RelationBody::StateTransition { guard, .. } = &mut relation.body {
|
||||
*guard = "request_ready".into();
|
||||
}
|
||||
f.project.relations.insert(edge, relation);
|
||||
// B is (760,180), 224x94. Its bottom midpoint is (872,274).
|
||||
// Fraction zero plus this authored offset fixes the label center at (440,260),
|
||||
// over A's entry row (y244..270), independently of text width/routing choices.
|
||||
f.project
|
||||
.diagrams
|
||||
.get_mut(&f.diagram)
|
||||
.unwrap()
|
||||
.routes
|
||||
.push(EdgeRoute {
|
||||
edge,
|
||||
waypoints: vec![],
|
||||
source_attachment: Some(EdgeAttachment {
|
||||
side: PortAnchorSide::Bottom,
|
||||
offset: 0.5,
|
||||
}),
|
||||
target_attachment: Some(EdgeAttachment {
|
||||
side: PortAnchorSide::Top,
|
||||
offset: 0.5,
|
||||
}),
|
||||
label: Some(EdgeLabel {
|
||||
fraction: 0.,
|
||||
offset: Position { x: -432., y: -14. },
|
||||
}),
|
||||
});
|
||||
validate_candidate(&f.project).unwrap();
|
||||
(f, edge, action)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_multiline_label_owns_covered_row_and_node_pixels_but_not_exposed_row() {
|
||||
let (f, edge, action) = multiline_label_fixture();
|
||||
let before = f.project.clone();
|
||||
for zoom in [0.5, 1., 2.] {
|
||||
let canvas = canvas(&f, Tool::Select, zoom); // Nonzero pan and canvas origin.
|
||||
let scene = canvas.scene(&State::default()).unwrap();
|
||||
let label = &scene.routed.edges[&edge].label;
|
||||
let layout = label
|
||||
.state_layout
|
||||
.as_ref()
|
||||
.expect("dedicated State label plan");
|
||||
assert_eq!(layout.title_lines, ["Ready", "for review"]);
|
||||
assert_eq!(
|
||||
layout.summary_lines,
|
||||
["External · Completion [request_ready]"]
|
||||
);
|
||||
assert_eq!(label.center, Position { x: 440., y: 260. });
|
||||
assert_eq!(label.bounds.size.height, 36.);
|
||||
let node = scene.view.nodes.iter().find(|n| n.element == f.a).unwrap();
|
||||
let row = state_action_rows(&f.project, node)[0].1;
|
||||
assert!(row.contains(Point::new(440., 260.)));
|
||||
assert!(!row.contains(Point::new(440., 272.)));
|
||||
// Second title cell covers a row; summary covers the State header below it.
|
||||
for at in [Point::new(440., 260.), Point::new(440., 272.)] {
|
||||
assert_eq!(canvas.hit(&scene, at), Some(Hit::Transition(edge)));
|
||||
let events = click(&canvas, &mut State::default(), at);
|
||||
assert!(
|
||||
matches!(events.as_slice(), [Event::Select(context, id)]
|
||||
if *context == canvas.context && *id == edge),
|
||||
"{events:?}"
|
||||
);
|
||||
}
|
||||
let exposed = Point::new(200., 260.);
|
||||
assert!(row.contains(exposed));
|
||||
assert_eq!(
|
||||
canvas.hit(&scene, exposed),
|
||||
Some(Hit::Row {
|
||||
placement: node.id,
|
||||
relation: action
|
||||
})
|
||||
);
|
||||
let events = click(&canvas, &mut State::default(), exposed);
|
||||
assert!(
|
||||
matches!(events.as_slice(), [Event::Select(_, id)] if *id == action),
|
||||
"{events:?}"
|
||||
);
|
||||
for (at, object, target) in [
|
||||
(Point::new(440., 272.), edge, Target::Edge(edge)),
|
||||
(
|
||||
exposed,
|
||||
action,
|
||||
Target::StateRow {
|
||||
placement: node.id,
|
||||
relation: action,
|
||||
},
|
||||
),
|
||||
] {
|
||||
let mut state = State::default();
|
||||
let at = screen(&canvas, at);
|
||||
motion(&canvas, &mut state, at, at);
|
||||
assert!(
|
||||
matches!(input(&canvas, &mut state, mouse::Event::ButtonPressed(mouse::Button::Right), at),
|
||||
Some(Event::ContextMenu { object: actual, selection_target: Some(actual_target), .. })
|
||||
if actual == object && actual_target == target)
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
f.project, before,
|
||||
"Hit testing and input cannot author layout or semantics"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_overlapping_label_background_selects_last_painted_even_if_lower_is_selected() {
|
||||
let (mut f, lower, _) = multiline_label_fixture();
|
||||
let upper = Id::from_u128(36_002);
|
||||
let mut relation = f.project.relations[&lower].clone();
|
||||
relation.id = upper;
|
||||
relation.alias = "second_multiline_transition".into();
|
||||
f.project.relations.insert(upper, relation);
|
||||
let mut route = f.project.diagrams[&f.diagram].routes[0].clone();
|
||||
route.edge = upper;
|
||||
// Reverse authored route order: stable edge paint order must still decide.
|
||||
f.project
|
||||
.diagrams
|
||||
.get_mut(&f.diagram)
|
||||
.unwrap()
|
||||
.routes
|
||||
.insert(0, route);
|
||||
validate_candidate(&f.project).unwrap();
|
||||
let mut canvas = canvas(&f, Tool::Select, 1.);
|
||||
canvas.selected = lower;
|
||||
let selection = group_selection(&canvas, [Target::Edge(lower)]);
|
||||
canvas.selection = Some(&selection);
|
||||
let scene = canvas.scene(&State::default()).unwrap();
|
||||
assert_eq!(
|
||||
scene.routed.edges[&lower].label.bounds,
|
||||
scene.routed.edges[&upper].label.bounds
|
||||
);
|
||||
let at = Point::new(440., 260.);
|
||||
assert_eq!(canvas.hit(&scene, at), Some(Hit::Transition(upper)));
|
||||
let events = click(&canvas, &mut State::default(), at);
|
||||
assert!(
|
||||
matches!(events.as_slice(), [Event::Selection(SelectionEvent::Choose(_, Target::Edge(id), Choice::Replace))]
|
||||
if *id == upper),
|
||||
"{events:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_multiline_label_and_exposed_row_keep_shift_identity_and_refuse_mixed_drag() {
|
||||
let (f, edge, action) = multiline_label_fixture();
|
||||
let before = f.project.clone();
|
||||
let mut canvas = canvas(&f, Tool::Select, 0.75);
|
||||
let row = Target::StateRow {
|
||||
placement: placement(&f, f.a),
|
||||
relation: action,
|
||||
};
|
||||
let selection = group_selection(&canvas, [Target::Edge(edge), row.clone()]);
|
||||
let snapshot = selection
|
||||
.snapshot(selection_context(canvas.context))
|
||||
.unwrap();
|
||||
canvas.selection = Some(&selection);
|
||||
canvas.selected = edge;
|
||||
for (world, target) in [
|
||||
(Point::new(440., 260.), Target::Edge(edge)),
|
||||
(Point::new(200., 260.), row),
|
||||
] {
|
||||
let at = screen(&canvas, world);
|
||||
let mut state = State::default();
|
||||
assert!(
|
||||
canvas::Program::update(
|
||||
&canvas,
|
||||
&mut state,
|
||||
&canvas::Event::Keyboard(keyboard::Event::ModifiersChanged(
|
||||
keyboard::Modifiers::SHIFT
|
||||
)),
|
||||
bounds(),
|
||||
mouse::Cursor::Unavailable
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
motion(&canvas, &mut state, at, at);
|
||||
assert!(
|
||||
matches!(input(&canvas, &mut state, mouse::Event::ButtonPressed(mouse::Button::Left), at),
|
||||
Some(Event::Selection(SelectionEvent::Choose(context, actual, Choice::Toggle)))
|
||||
if context == selection_context(canvas.context) && actual == target)
|
||||
);
|
||||
assert!(state.group.is_none() && state.press.is_none() && state.bend.is_none());
|
||||
assert!(
|
||||
input(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left),
|
||||
at
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let mut state = State::default();
|
||||
motion(&canvas, &mut state, at, at);
|
||||
assert!(
|
||||
input(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
at
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(state.group.is_some() && state.press.is_none() && state.bend.is_none());
|
||||
let end = at + Vector::new(30., 20.);
|
||||
motion(&canvas, &mut state, end, end);
|
||||
assert!(canvas.group_preview_key(&state, Some(bounds())).is_none());
|
||||
assert!(
|
||||
matches!(input(&canvas, &mut state, mouse::Event::ButtonReleased(mouse::Button::Left), end),
|
||||
Some(Event::Selection(SelectionEvent::Refuse(actual, _))) if actual == snapshot)
|
||||
);
|
||||
assert!(state.group.is_none() && state.bend_preview.is_none());
|
||||
}
|
||||
assert_eq!(
|
||||
selection
|
||||
.snapshot(selection_context(canvas.context))
|
||||
.unwrap(),
|
||||
snapshot
|
||||
);
|
||||
assert_eq!(f.project, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_multiline_label_group_preview_replaces_old_hit_rectangle_and_cancel_restores_it() {
|
||||
let (f, edge, action) = multiline_label_fixture();
|
||||
let before = f.project.clone();
|
||||
let mut canvas = canvas(&f, Tool::Select, 0.75);
|
||||
let selection = group_selection(
|
||||
&canvas,
|
||||
[
|
||||
Target::Placement(placement(&f, f.b)),
|
||||
Target::Placement(placement(&f, f.c)),
|
||||
],
|
||||
);
|
||||
canvas.selection = Some(&selection);
|
||||
canvas.selected = f.c;
|
||||
let mut state = State::default();
|
||||
let baseline = canvas.scene(&state).unwrap();
|
||||
let old = Point::new(440., 260.);
|
||||
let moved = Point::new(440., 580.);
|
||||
assert_eq!(canvas.hit(&baseline, old), Some(Hit::Transition(edge)));
|
||||
let start = screen(&canvas, Point::new(780., 200.));
|
||||
let end = start + Vector::new(0., 320. * canvas.scale());
|
||||
motion(&canvas, &mut state, start, end);
|
||||
assert!(
|
||||
input(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
end
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(state.group.is_some());
|
||||
motion(&canvas, &mut state, end, end);
|
||||
let preview = canvas.scene_with_bounds(&state, Some(bounds())).unwrap();
|
||||
assert_eq!(
|
||||
preview.routed.edges[&edge].label.center,
|
||||
Position { x: 440., y: 580. }
|
||||
);
|
||||
assert_eq!(
|
||||
preview.routed.edges[&edge].label.state_layout,
|
||||
baseline.routed.edges[&edge].label.state_layout
|
||||
);
|
||||
assert_eq!(canvas.hit(&preview, moved), Some(Hit::Transition(edge)));
|
||||
assert_eq!(
|
||||
canvas.hit(&preview, old),
|
||||
Some(Hit::Row {
|
||||
placement: placement(&f, f.a),
|
||||
relation: action
|
||||
})
|
||||
);
|
||||
assert_eq!(f.project, before, "Group preview is derived only");
|
||||
assert!(
|
||||
group_key(
|
||||
&canvas,
|
||||
&mut state,
|
||||
keyboard::key::Named::Escape,
|
||||
keyboard::Modifiers::empty()
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(state.group.is_none());
|
||||
let restored = canvas.scene_with_bounds(&state, Some(bounds())).unwrap();
|
||||
assert_eq!(
|
||||
restored.routed.edges[&edge].label,
|
||||
baseline.routed.edges[&edge].label
|
||||
);
|
||||
assert_eq!(canvas.hit(&restored, old), Some(Hit::Transition(edge)));
|
||||
assert_ne!(canvas.hit(&restored, moved), Some(Hit::Transition(edge)));
|
||||
assert!(
|
||||
input(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left),
|
||||
end
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(f.project, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_multiline_label_group_commit_matches_fixed_project_and_one_step_history() {
|
||||
let (f, edge, _) = multiline_label_fixture();
|
||||
let b = placement(&f, f.b);
|
||||
let c = placement(&f, f.c);
|
||||
let before = f.project.clone();
|
||||
// Declare the complete expected model before driving input. No event, preview,
|
||||
// movement planner or resulting document supplies the expected displacement.
|
||||
let mut expected = before.clone();
|
||||
for id in [b, c] {
|
||||
let node = expected
|
||||
.diagrams
|
||||
.get_mut(&f.diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&id)
|
||||
.unwrap();
|
||||
node.position.x += 40.;
|
||||
node.position.y += 40.;
|
||||
}
|
||||
let original_route = before.diagrams[&f.diagram].routes[0].clone();
|
||||
let mut document = Document::from_project(before.clone(), Default::default()).unwrap();
|
||||
let original_source = document.bundle().clone();
|
||||
let mut view_canvas = canvas(&f, Tool::Select, 0.75);
|
||||
let selection = group_selection(&view_canvas, [Target::Placement(b), Target::Placement(c)]);
|
||||
let snapshot = selection
|
||||
.snapshot(selection_context(view_canvas.context))
|
||||
.unwrap();
|
||||
view_canvas.selection = Some(&selection);
|
||||
view_canvas.selected = f.c;
|
||||
let mut state = State::default();
|
||||
let baseline = view_canvas.scene(&state).unwrap();
|
||||
let baseline_label = &baseline.routed.edges[&edge].label;
|
||||
assert_eq!(baseline_label.center, Position { x: 440., y: 260. });
|
||||
// Explicit 0.75 scale: thirty screen pixels is the declared forty world
|
||||
// units. The nonzero pan/origin and ordered pointer helpers stay in play.
|
||||
let start = screen(&view_canvas, Point::new(780., 200.));
|
||||
let end = start + Vector::new(30., 30.);
|
||||
motion(&view_canvas, &mut state, start, end);
|
||||
assert!(
|
||||
input(
|
||||
&view_canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
end
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
assert!(state.group.is_some());
|
||||
motion(&view_canvas, &mut state, end, end);
|
||||
let preview = view_canvas
|
||||
.scene_with_bounds(&state, Some(bounds()))
|
||||
.unwrap();
|
||||
let preview_label = &preview.routed.edges[&edge].label;
|
||||
assert_eq!(preview_label.center, Position { x: 480., y: 300. });
|
||||
assert_eq!(
|
||||
preview_label.bounds.position,
|
||||
Position {
|
||||
x: baseline_label.bounds.position.x + 40.,
|
||||
y: baseline_label.bounds.position.y + 40.,
|
||||
}
|
||||
);
|
||||
assert_eq!(preview_label.bounds.size, baseline_label.bounds.size);
|
||||
assert_eq!(preview_label.state_layout, baseline_label.state_layout);
|
||||
assert_eq!(
|
||||
f.project, before,
|
||||
"Preview must not author its derived label"
|
||||
);
|
||||
let event = input(
|
||||
&view_canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left),
|
||||
end,
|
||||
)
|
||||
.unwrap();
|
||||
let Event::Selection(SelectionEvent::Move(actual_snapshot, delta)) = event else {
|
||||
panic!("Expected a group movement event, got {event:?}");
|
||||
};
|
||||
assert_eq!(actual_snapshot, snapshot);
|
||||
assert_eq!(delta, Position { x: 40., y: 40. });
|
||||
assert!(state.group.is_none() && state.press.is_none());
|
||||
assert!(
|
||||
input(
|
||||
&view_canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left),
|
||||
end
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
// This is the same production snapshot-to-command boundary used by App's
|
||||
// SelectionEvent::Move handler; App admission has its existing separate tests.
|
||||
let ids = crate::app::multiselect::placements(&actual_snapshot).unwrap();
|
||||
let command =
|
||||
oa_core::diagram_edit::translate_placements(document.project(), f.diagram, &ids, delta)
|
||||
.unwrap();
|
||||
document.execute(command).unwrap();
|
||||
assert_eq!(document.project(), &expected);
|
||||
assert_eq!(
|
||||
document.project().diagrams[&f.diagram].routes[0],
|
||||
original_route,
|
||||
"Authored fraction, offset, attachments and empty waypoints remain exact"
|
||||
);
|
||||
let committed = StateCanvas {
|
||||
project: document.project(),
|
||||
..canvas(&f, Tool::Select, 0.75)
|
||||
}
|
||||
.scene(&State::default())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
committed.routed.edges[&edge].label, *preview_label,
|
||||
"Measured settled label, including raw text and every line cell, matches preview"
|
||||
);
|
||||
let edited_source = document.bundle().clone();
|
||||
assert_ne!(
|
||||
edited_source, original_source,
|
||||
"The group move is a real source edit"
|
||||
);
|
||||
assert!(document.undo());
|
||||
assert_eq!(document.project(), &before);
|
||||
assert_eq!(document.bundle(), &original_source);
|
||||
assert!(
|
||||
!document.undo(),
|
||||
"One group release produces exactly one history entry"
|
||||
);
|
||||
assert!(document.redo());
|
||||
assert_eq!(document.project(), &expected);
|
||||
assert_eq!(document.bundle(), &edited_source);
|
||||
assert!(!document.redo());
|
||||
assert_eq!(
|
||||
selection
|
||||
.snapshot(selection_context(view_canvas.context))
|
||||
.unwrap(),
|
||||
snapshot
|
||||
);
|
||||
assert_eq!(f.project, before);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
app::Message,
|
||||
route_edit::{self, Context, Event as RouteEvent, Gesture, Handle, point, position},
|
||||
state_label::route_diagram,
|
||||
style,
|
||||
views::short,
|
||||
};
|
||||
@@ -13,7 +14,7 @@ use iced::{
|
||||
};
|
||||
use oa_core::{
|
||||
projection::{ProjectedNode, ProjectedView, project_view},
|
||||
routing::{RoutedView, route_diagram},
|
||||
routing::{RoutedView, StateLabelLayout},
|
||||
*,
|
||||
};
|
||||
use std::{
|
||||
@@ -34,6 +35,9 @@ mod lifecycle_tests;
|
||||
#[path = "diagram/port_hit_tests.rs"]
|
||||
mod port_hit_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "diagram/state_label_input_tests.rs"]
|
||||
mod state_label_input_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "diagram/view_options_tests.rs"]
|
||||
mod view_options_tests;
|
||||
|
||||
@@ -42,7 +46,6 @@ mod port_labels;
|
||||
pub use port_labels::PortLabels;
|
||||
#[path = "diagram/focus.rs"]
|
||||
pub(crate) mod focus;
|
||||
mod grid;
|
||||
pub(crate) mod group_gesture;
|
||||
mod multiselect;
|
||||
#[cfg(test)]
|
||||
@@ -72,7 +75,6 @@ pub struct DiagramCanvas<'a> {
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
grid: grid::Grid,
|
||||
modifiers: iced::keyboard::Modifiers,
|
||||
group: Option<multiselect::Press>,
|
||||
work: Option<Arc<crate::app::allocation_drag::Press>>,
|
||||
@@ -327,7 +329,12 @@ impl DiagramCanvas<'_> {
|
||||
request.bend = Some(index);
|
||||
return request;
|
||||
}
|
||||
if let Some(port) = self.hit_port(&scene.view, local) {
|
||||
if let Some(edge) = self.hit_state_label(&scene.routed, local) {
|
||||
request.object = edge;
|
||||
request.route_point =
|
||||
routing::project_onto_polyline(&scene.routed.edges[&edge].points, position(world))
|
||||
.map(|p| p.point);
|
||||
} else if let Some(port) = self.hit_port(&scene.view, local) {
|
||||
request.object = port.element;
|
||||
} else if let Some(node) = self.hit(&scene.view, local) {
|
||||
request.object = self.project.diagrams[&self.diagram].nodes[&node].element;
|
||||
@@ -426,13 +433,35 @@ impl DiagramCanvas<'_> {
|
||||
})
|
||||
.map(|(end, owner, _)| (end, owner))
|
||||
}
|
||||
fn hit_state_label(&self, routed: &RoutedView, p: Point) -> Option<Id> {
|
||||
let world = self.world(p);
|
||||
// State labels paint after nodes, in BTree order. Hit the frontmost first.
|
||||
routed
|
||||
.edges
|
||||
.values()
|
||||
.rev()
|
||||
.find(|edge| {
|
||||
edge.label.state_layout.is_some()
|
||||
&& !edge.label.text.is_empty()
|
||||
&& Rectangle::new(
|
||||
point(edge.label.bounds.position),
|
||||
Size::new(edge.label.bounds.size.width, edge.label.bounds.size.height),
|
||||
)
|
||||
.contains(world)
|
||||
})
|
||||
.map(|edge| edge.id)
|
||||
}
|
||||
fn hit_label(&self, routed: &RoutedView, p: Point) -> Option<Id> {
|
||||
if let Some(edge) = self.hit_state_label(routed, p) {
|
||||
return Some(edge);
|
||||
}
|
||||
let p = self.world(p);
|
||||
routed
|
||||
.edges
|
||||
.values()
|
||||
.filter(|edge| {
|
||||
!edge.label.text.is_empty()
|
||||
edge.label.state_layout.is_none()
|
||||
&& !edge.label.text.is_empty()
|
||||
&& Rectangle::new(
|
||||
point(edge.label.bounds.position),
|
||||
Size::new(edge.label.bounds.size.width, edge.label.bounds.size.height),
|
||||
@@ -571,7 +600,7 @@ impl DiagramCanvas<'_> {
|
||||
.as_ref()
|
||||
.filter(|g| g.context == self.context)
|
||||
{
|
||||
oa_core::routing::route_edge_preview(
|
||||
crate::state_label::route_edge_preview(
|
||||
self.project,
|
||||
&diagram,
|
||||
&view,
|
||||
@@ -964,8 +993,11 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
);
|
||||
}
|
||||
let label = &edge.label;
|
||||
if self.hit(view, p).is_none()
|
||||
&& !label.text.is_empty()
|
||||
if (if label.state_layout.is_some() {
|
||||
self.hit_state_label(&scene.routed, p) == Some(edge.id)
|
||||
} else {
|
||||
self.hit(view, p).is_none()
|
||||
}) && !label.text.is_empty()
|
||||
&& Rectangle::new(
|
||||
point(label.bounds.position),
|
||||
Size::new(label.bounds.size.width, label.bounds.size.height),
|
||||
@@ -984,6 +1016,9 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
return Some(Action::capture());
|
||||
}
|
||||
}
|
||||
if let Some(edge) = self.hit_state_label(&scene.routed, p) {
|
||||
return Some(Action::publish(Message::Select(edge)).and_capture());
|
||||
}
|
||||
if let Some(port) = self.hit_port(view, p) {
|
||||
if self.editable
|
||||
&& let Some(owner) = view.nodes.iter().find(|n| n.id == port.owner)
|
||||
@@ -1253,10 +1288,23 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
bounds: Rectangle,
|
||||
_: mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let background = state
|
||||
.grid
|
||||
.draw(renderer, bounds.size(), self.zoom, self.pan);
|
||||
let mut f = Frame::new(renderer, bounds.size());
|
||||
f.fill_rectangle(
|
||||
Point::ORIGIN,
|
||||
bounds.size(),
|
||||
Color::from_rgb8(246, 248, 251),
|
||||
);
|
||||
let step = (24. * self.zoom).max(14.);
|
||||
let ox = self.pan.x.rem_euclid(step);
|
||||
let oy = self.pan.y.rem_euclid(step);
|
||||
for x in 0..(bounds.width / step) as i32 + 1 {
|
||||
for y in 0..(bounds.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
style::LINE,
|
||||
);
|
||||
}
|
||||
}
|
||||
f.translate(self.pan);
|
||||
f.scale(self.zoom);
|
||||
let scene = self.scene(state);
|
||||
@@ -1337,7 +1385,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
Stroke::default().with_color(color).with_width(1.7),
|
||||
);
|
||||
}
|
||||
if !routed.label.text.is_empty() {
|
||||
if routed.label.state_layout.is_none() && !routed.label.text.is_empty() {
|
||||
let label = &routed.label;
|
||||
f.fill_rectangle(
|
||||
point(label.bounds.position),
|
||||
@@ -1476,6 +1524,47 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
f.fill(&Path::circle(center, 1.5), color);
|
||||
}
|
||||
}
|
||||
// State labels follow nodes, matching the dedicated State canvas layer.
|
||||
for routed in scene.routed.edges.values() {
|
||||
let label = &routed.label;
|
||||
let active = self.selected == routed.id
|
||||
|| highlighted.contains(&routed.id)
|
||||
|| self.selection_contains(crate::diagram_usage::Target::Edge(routed.id));
|
||||
if label.state_layout.is_some() && !label.text.is_empty() {
|
||||
f.fill_rectangle(
|
||||
point(label.bounds.position),
|
||||
Size::new(label.bounds.size.width, label.bounds.size.height),
|
||||
Color::WHITE,
|
||||
);
|
||||
if let Some(plan) = &label.state_layout {
|
||||
for (lines, title) in [(&plan.title_lines, true), (&plan.summary_lines, false)]
|
||||
{
|
||||
for (index, line) in lines.iter().enumerate() {
|
||||
let offset = if title {
|
||||
plan.title_line_position(index)
|
||||
} else {
|
||||
plan.summary_line_position(index)
|
||||
};
|
||||
f.fill_text(Text {
|
||||
content: line.clone(),
|
||||
position: point(label.bounds.position)
|
||||
+ Vector::new(offset.x, offset.y),
|
||||
color: if title {
|
||||
if active { style::TEAL } else { style::INK }
|
||||
} else {
|
||||
style::MUTED
|
||||
},
|
||||
size: StateLabelLayout::FONT_SIZE.into(),
|
||||
line_height: iced::widget::text::LineHeight::Absolute(
|
||||
StateLabelLayout::LINE_HEIGHT.into(),
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(bounds) = self.navigation_focus {
|
||||
f.stroke(
|
||||
&Path::rectangle(bounds.position(), bounds.size()),
|
||||
@@ -1659,7 +1748,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
vec![background, f.into_geometry(), overlay.into_geometry()]
|
||||
vec![f.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
@@ -1932,6 +2021,34 @@ impl std::fmt::Display for ExportError {
|
||||
}
|
||||
impl std::error::Error for ExportError {}
|
||||
|
||||
fn edge_label_svg(label: &routing::EdgeLabelGeometry) -> String {
|
||||
if label.text.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let mut svg = format!(
|
||||
"<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" fill=\"white\"/>",
|
||||
label.bounds.position.x,
|
||||
label.bounds.position.y,
|
||||
label.bounds.size.width,
|
||||
label.bounds.size.height
|
||||
);
|
||||
if let Some(plan) = &label.state_layout {
|
||||
for (lines, title) in [(&plan.title_lines, true), (&plan.summary_lines, false)] {
|
||||
for (index, line) in lines.iter().enumerate() {
|
||||
let offset = if title {
|
||||
plan.title_line_position(index)
|
||||
} else {
|
||||
plan.summary_line_position(index)
|
||||
};
|
||||
svg.push_str(&format!("<text x=\"{}\" y=\"{}\" dominant-baseline=\"text-before-edge\" xml:space=\"preserve\" font-family=\"sans-serif\" font-size=\"{}\" fill=\"{}\">{}</text>", label.bounds.position.x + offset.x, label.bounds.position.y + offset.y, StateLabelLayout::FONT_SIZE, hex(if title { style::INK } else { style::MUTED }), escape(line)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
svg.push_str(&format!("<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"sans-serif\" font-size=\"13\" fill=\"#637487\">{}</text>",label.center.x,label.center.y,escape(&label.text)));
|
||||
}
|
||||
svg
|
||||
}
|
||||
|
||||
pub fn export_svg(p: &Project, diagram: Id) -> Result<String, ExportError> {
|
||||
let d = p
|
||||
.diagrams
|
||||
@@ -2018,8 +2135,8 @@ pub fn export_svg(p: &Project, diagram: Id) -> Result<String, ExportError> {
|
||||
.join(" ");
|
||||
svg.push_str(&format!("<path data-edge=\"{}\" d=\"{path}\" fill=\"none\" stroke=\"#8999a9\" stroke-width=\"1.5\"{dashed}{arrow}><title>{}</title></path>",edge.id,escape(p.named(edge.id))));
|
||||
let label = &route.label;
|
||||
if !label.text.is_empty() {
|
||||
svg.push_str(&format!("<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" fill=\"white\"/><text x=\"{}\" y=\"{}\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"sans-serif\" font-size=\"13\" fill=\"#637487\">{}</text>",label.bounds.position.x,label.bounds.position.y,label.bounds.size.width,label.bounds.size.height,label.center.x,label.center.y,escape(&label.text)));
|
||||
if label.state_layout.is_none() {
|
||||
svg.push_str(&edge_label_svg(label));
|
||||
}
|
||||
if let Some(relation) = p.relations.get(&edge.id) {
|
||||
for end in routing::association_end_geometry(relation, route) {
|
||||
@@ -2078,6 +2195,11 @@ pub fn export_svg(p: &Project, diagram: Id) -> Result<String, ExportError> {
|
||||
}
|
||||
svg.push_str("</g>");
|
||||
}
|
||||
for route in routed.edges.values() {
|
||||
if route.label.state_layout.is_some() {
|
||||
svg.push_str(&edge_label_svg(&route.label));
|
||||
}
|
||||
}
|
||||
svg.push_str("</svg>");
|
||||
Ok(svg)
|
||||
}
|
||||
@@ -2935,6 +3057,94 @@ mod tests {
|
||||
);
|
||||
assert!(state.route_drag.is_none());
|
||||
}
|
||||
#[test]
|
||||
fn state_svg_paints_separate_escaped_lines_at_shared_layout_origins() {
|
||||
let plan = StateLabelLayout::new(
|
||||
"Ready\r\nfor <review>&",
|
||||
"External\nCompletion [ready]",
|
||||
&|line| line.len() as f32 * 8.0,
|
||||
);
|
||||
let label = routing::EdgeLabelGeometry {
|
||||
text: "External\nCompletion [ready]".into(),
|
||||
fraction: 0.5,
|
||||
anchor: Position { x: 0.0, y: 0.0 },
|
||||
center: Position { x: 0.0, y: 0.0 },
|
||||
bounds: routing::RoutingBounds {
|
||||
position: Position { x: 100.0, y: 200.0 },
|
||||
size: plan.size(),
|
||||
},
|
||||
state_layout: Some(plan),
|
||||
};
|
||||
let svg = edge_label_svg(&label);
|
||||
assert_eq!(svg.matches("<text ").count(), 4);
|
||||
for (y, color, text) in [
|
||||
(200, "#18212c", "Ready"),
|
||||
(212, "#18212c", "for <review>&"),
|
||||
(224, "#4b5968", "External"),
|
||||
(236, "#4b5968", "Completion [ready]"),
|
||||
] {
|
||||
assert!(svg.contains(&format!("<text x=\"105\" y=\"{y}\" dominant-baseline=\"text-before-edge\" xml:space=\"preserve\" font-family=\"sans-serif\" font-size=\"10\" fill=\"{color}\">{text}</text>")), "{svg}");
|
||||
}
|
||||
assert!(svg.contains("height=\"48\""));
|
||||
assert!(!svg.contains("text-anchor=\"middle\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_export_routes_measured_multiline_label_above_nodes() {
|
||||
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, "Modes", Some(owner))
|
||||
.unwrap();
|
||||
let region = p
|
||||
.add_element(Kind::Region, Layer::Oa, "Main", Some(machine))
|
||||
.unwrap();
|
||||
let a = p
|
||||
.add_element(Kind::State, Layer::Oa, "Source state", Some(region))
|
||||
.unwrap();
|
||||
let b = p
|
||||
.add_element(Kind::State, Layer::Oa, "Target state", Some(region))
|
||||
.unwrap();
|
||||
let edge = p.relate(RelationKind::StateTransition, "Ready\nfor <review>&", a, b);
|
||||
let diagram = Id::new_v4();
|
||||
p.diagrams.insert(
|
||||
diagram,
|
||||
Diagram {
|
||||
id: diagram,
|
||||
alias: "state_export".into(),
|
||||
name: "State export".into(),
|
||||
description: String::new(),
|
||||
layer: Layer::Oa,
|
||||
projection: ProjectionKind::StateMachine { machine },
|
||||
view_options: Default::default(),
|
||||
nodes: BTreeMap::new(),
|
||||
layout: None,
|
||||
routes: vec![],
|
||||
},
|
||||
);
|
||||
p.place(diagram, a, Position { x: 100.0, y: 100.0 });
|
||||
p.place(diagram, b, Position { x: 600.0, y: 100.0 });
|
||||
let view = project_view(&p, diagram);
|
||||
let routed = route_diagram(&p, &p.diagrams[&diagram], &view);
|
||||
let label = &routed.edges[&edge].label;
|
||||
let plan = label.state_layout.as_ref().unwrap();
|
||||
assert_eq!(plan.title_lines, ["Ready", "for <review>&"]);
|
||||
assert_eq!(plan.summary_lines.len(), 1);
|
||||
assert_eq!(label.bounds.size.height, 36.0);
|
||||
let svg = export_svg(&p, diagram).unwrap();
|
||||
let label_svg = edge_label_svg(label);
|
||||
assert!(svg.ends_with(&format!("{label_svg}</svg>")));
|
||||
assert!(svg.find("Target state</text>").unwrap() < svg.find("Ready</text>").unwrap());
|
||||
assert!(svg.contains("for <review>&</text>"));
|
||||
assert!(svg.contains(&format!(
|
||||
"x=\"{}\" y=\"{}\" dominant-baseline=\"text-before-edge\"",
|
||||
label.bounds.position.x + 5.0,
|
||||
label.bounds.position.y + 24.0
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routed_svg_has_same_polyline_label_and_bounds_as_canvas_scene() {
|
||||
let (mut p, diagram, edge) = route_fixture();
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//! Screen-space background geometry, independent of the model and pointer.
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Grid {
|
||||
geometry: canvas::Cache,
|
||||
camera: Cell<Option<(f32, Vector)>>,
|
||||
#[cfg(test)]
|
||||
builds: Cell<usize>,
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
pub(super) fn draw(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
) -> canvas::Geometry {
|
||||
// Cache::draw handles exact size changes. Raw camera equality is
|
||||
// deliberately conservative: do not quantize fractional antialiasing.
|
||||
if self.camera.replace(Some((zoom, pan))) != Some((zoom, pan)) {
|
||||
self.geometry.clear();
|
||||
}
|
||||
self.geometry.draw(renderer, size, |frame| {
|
||||
#[cfg(test)]
|
||||
self.builds.set(self.builds.get() + 1);
|
||||
paint(frame, size, zoom, pan);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, Color::from_rgb8(246, 248, 251));
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
style::LINE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,378 +0,0 @@
|
||||
use super::*;
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
|
||||
async fn renderer() -> Renderer {
|
||||
<Renderer as Headless>::new(iced::Font::DEFAULT, 16.into(), None)
|
||||
.await
|
||||
.expect("grid tests require the supported WGPU headless renderer")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change() {
|
||||
let renderer = renderer().await;
|
||||
let grid = Grid::default();
|
||||
let original = Size::new(320., 240.);
|
||||
let threshold = 14. / 24.;
|
||||
// Each row changes exactly one dependency from its predecessor. Returning
|
||||
// to old dimensions must invalidate too; this cache holds one viewport.
|
||||
let cases = [
|
||||
("initial", original, 1., Vector::ZERO),
|
||||
("positive x pan", original, 1., Vector::new(24., 0.)),
|
||||
("negative x pan", original, 1., Vector::new(-24., 0.)),
|
||||
("fractional x pan", original, 1., Vector::new(-24.25, 0.)),
|
||||
("positive y pan", original, 1., Vector::new(-24.25, 18.)),
|
||||
("negative y pan", original, 1., Vector::new(-24.25, -18.)),
|
||||
("fractional y pan", original, 1., Vector::new(-24.25, -18.5)),
|
||||
("return to zero pan", original, 1., Vector::ZERO),
|
||||
("zoom at zero pan", original, 1.25, Vector::ZERO),
|
||||
(
|
||||
"below spacing threshold",
|
||||
original,
|
||||
threshold - 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("at spacing threshold", original, threshold, Vector::ZERO),
|
||||
(
|
||||
"above spacing threshold",
|
||||
original,
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
(
|
||||
"width only",
|
||||
Size::new(321., 240.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("return width", original, threshold + 0.001, Vector::ZERO),
|
||||
(
|
||||
"height only",
|
||||
Size::new(320., 241.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
("return height", original, threshold + 0.001, Vector::ZERO),
|
||||
(
|
||||
"fractional width",
|
||||
Size::new(320.25, 240.),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
(
|
||||
"fractional height",
|
||||
Size::new(320.25, 240.5),
|
||||
threshold + 0.001,
|
||||
Vector::ZERO,
|
||||
),
|
||||
];
|
||||
for (index, (label, size, zoom, pan)) in cases.into_iter().enumerate() {
|
||||
let _ = grid.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(grid.builds.get(), index + 1, "{label} must rebuild");
|
||||
for _ in 0..3 {
|
||||
let _ = grid.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(grid.builds.get(), index + 1, "{label} must reuse");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Independent reference copied from the former DiagramCanvas::draw background
|
||||
// pass. Do not call grid::paint here: a shared implementation would hide changes
|
||||
// in spacing, phase, radius, color, or coverage from the parity comparison.
|
||||
fn legacy_background(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, Color::from_rgb8(246, 248, 251));
|
||||
let spacing = (24. * zoom).max(14.);
|
||||
let offset_x = pan.x.rem_euclid(spacing);
|
||||
let offset_y = pan.y.rem_euclid(spacing);
|
||||
for column in 0..(size.width / spacing) as i32 + 1 {
|
||||
for row in 0..(size.height / spacing) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(
|
||||
Point::new(
|
||||
offset_x + column as f32 * spacing,
|
||||
offset_y + row as f32 * spacing,
|
||||
),
|
||||
0.65,
|
||||
),
|
||||
Color::from_rgb(0.86, 0.89, 0.91),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn foreground(frame: &mut Frame, zoom: f32, pan: Vector, changed: bool) {
|
||||
frame.translate(pan);
|
||||
frame.scale(zoom);
|
||||
// Crossing paths pass behind opaque cards. The long paths also cross the
|
||||
// screen-space warning, making geometry order observable.
|
||||
for (start, end, color) in [
|
||||
(Point::new(0., 20.), Point::new(300., 210.), style::TEAL),
|
||||
(Point::new(0., 210.), Point::new(300., 20.), style::BOUNDARY),
|
||||
] {
|
||||
frame.stroke(
|
||||
&Path::line(start, end),
|
||||
Stroke::default().with_color(color).with_width(3.),
|
||||
);
|
||||
}
|
||||
let shift = if changed { 19.5 } else { 0. };
|
||||
frame.fill_rectangle(
|
||||
Point::new(12., 92.),
|
||||
Size::new(270., 35.),
|
||||
Color::from_rgba(0.1, 0.6, 0.5, 0.25),
|
||||
);
|
||||
for (position, size, title) in [
|
||||
(Point::new(32. + shift, 58.), Size::new(116., 72.), "Sensor"),
|
||||
(Point::new(172., 139.), Size::new(118., 66.), "Controller"),
|
||||
] {
|
||||
frame.fill_rectangle(position, size, Color::WHITE);
|
||||
frame.stroke(
|
||||
&Path::rectangle(position, size),
|
||||
Stroke::default().with_color(style::BOUNDARY).with_width(1.),
|
||||
);
|
||||
frame.fill_text(Text {
|
||||
content: title.into(),
|
||||
position: position + Vector::new(8., 12.),
|
||||
color: style::INK,
|
||||
size: 13.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
// Selection paint and handles remain live foreground data, including when
|
||||
// the underlying background cache is reused after an edit.
|
||||
frame.stroke(
|
||||
&Path::rectangle(Point::new(29. + shift, 55.), Size::new(122., 78.)),
|
||||
Stroke::default().with_color(style::TEAL).with_width(2.),
|
||||
);
|
||||
frame.fill_rectangle(Point::new(26. + shift, 52.), Size::new(6., 6.), style::TEAL);
|
||||
frame.fill_text(Text {
|
||||
content: if changed {
|
||||
"Edited connection"
|
||||
} else {
|
||||
"Connection"
|
||||
}
|
||||
.into(),
|
||||
position: Point::new(116., 113.),
|
||||
color: style::INK,
|
||||
size: 11.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn warning(frame: &mut Frame, size: Size) {
|
||||
frame.fill_rectangle(
|
||||
Point::new(8., 8.),
|
||||
Size::new((size.width - 16.).max(0.), 30.),
|
||||
Color::WHITE,
|
||||
);
|
||||
frame.fill_text(Text {
|
||||
content: "1 connection route needs attention".into(),
|
||||
position: Point::new(16., 15.),
|
||||
color: style::MUTED,
|
||||
size: 12.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn legacy_geometry(
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
changed: bool,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let mut combined = Frame::new(renderer, size);
|
||||
legacy_background(&mut combined, size, zoom, pan);
|
||||
foreground(&mut combined, zoom, pan, changed);
|
||||
let mut overlay = Frame::new(renderer, size);
|
||||
warning(&mut overlay, size);
|
||||
vec![combined.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
|
||||
fn cached_geometry(
|
||||
grid: &Grid,
|
||||
renderer: &Renderer,
|
||||
size: Size,
|
||||
zoom: f32,
|
||||
pan: Vector,
|
||||
changed: bool,
|
||||
) -> Vec<canvas::Geometry> {
|
||||
let background = grid.draw(renderer, size, zoom, pan);
|
||||
let mut content = Frame::new(renderer, size);
|
||||
foreground(&mut content, zoom, pan, changed);
|
||||
let mut overlay = Frame::new(renderer, size);
|
||||
warning(&mut overlay, size);
|
||||
vec![background, content.into_geometry(), overlay.into_geometry()]
|
||||
}
|
||||
|
||||
fn pixels(renderer: &mut Renderer, size: Size, geometry: Vec<canvas::Geometry>) -> Vec<u8> {
|
||||
// Keep physical screenshot dimensions and renderer viewport consistent even
|
||||
// for fractional canvas bounds; the geometry itself keeps its exact size.
|
||||
let physical = Size::new(size.width.ceil() as u32, size.height.ceil() as u32);
|
||||
renderer.reset(Rectangle::with_size(Size::new(
|
||||
physical.width as f32,
|
||||
physical.height as f32,
|
||||
)));
|
||||
for layer in geometry {
|
||||
renderer.draw_geometry(layer);
|
||||
}
|
||||
let pixels = Headless::screenshot(renderer, physical, 1., style::BG);
|
||||
assert_eq!(
|
||||
pixels.len(),
|
||||
physical.width as usize * physical.height as usize * 4
|
||||
);
|
||||
pixels
|
||||
}
|
||||
|
||||
fn assert_same_pixels(expected: &[u8], actual: &[u8], label: &str) {
|
||||
assert_eq!(expected.len(), actual.len(), "{label}: dimensions");
|
||||
let differing = expected
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.zip(actual.as_chunks::<4>().0)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
assert_eq!(differing, 0, "{label}: changed RGBA pixels");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes() {
|
||||
let mut renderer = renderer().await;
|
||||
let grid = Grid::default();
|
||||
let original = Size::new(320., 240.);
|
||||
let threshold = 14. / 24.;
|
||||
for (label, size, zoom, pan) in [
|
||||
("origin", original, 1., Vector::ZERO),
|
||||
("positive pan", original, 1., Vector::new(17., 11.)),
|
||||
("negative pan", original, 1., Vector::new(-17., -11.)),
|
||||
("fractional pan", original, 1., Vector::new(-17.25, 11.5)),
|
||||
("zoom zero pan", original, 1.25, Vector::ZERO),
|
||||
("below threshold", original, threshold - 0.001, Vector::ZERO),
|
||||
("at threshold", original, threshold, Vector::ZERO),
|
||||
("above threshold", original, threshold + 0.001, Vector::ZERO),
|
||||
("width resize", Size::new(327., 240.), 1., Vector::ZERO),
|
||||
("height resize", Size::new(327., 247.), 1., Vector::ZERO),
|
||||
("return size", original, 1., Vector::ZERO),
|
||||
(
|
||||
"fractional bounds",
|
||||
Size::new(319.25, 239.5),
|
||||
1.,
|
||||
Vector::new(-0.5, 0.25),
|
||||
),
|
||||
] {
|
||||
let old = legacy_geometry(&renderer, size, zoom, pan, false);
|
||||
let expected = pixels(&mut renderer, size, old);
|
||||
let layers = cached_geometry(&grid, &renderer, size, zoom, pan, false);
|
||||
let actual = pixels(&mut renderer, size, layers);
|
||||
assert_same_pixels(&expected, &actual, label);
|
||||
let builds = grid.builds.get();
|
||||
let reused = cached_geometry(&grid, &renderer, size, zoom, pan, false);
|
||||
let reused = pixels(&mut renderer, size, reused);
|
||||
assert_same_pixels(&expected, &reused, label);
|
||||
assert_eq!(grid.builds.get(), builds, "{label}: screenshot reuse");
|
||||
|
||||
let edited_old = legacy_geometry(&renderer, size, zoom, pan, true);
|
||||
let edited_expected = pixels(&mut renderer, size, edited_old);
|
||||
assert_ne!(
|
||||
expected, edited_expected,
|
||||
"{label}: edit must visibly change foreground"
|
||||
);
|
||||
let edited = cached_geometry(&grid, &renderer, size, zoom, pan, true);
|
||||
let edited = pixels(&mut renderer, size, edited);
|
||||
assert_same_pixels(&edited_expected, &edited, label);
|
||||
assert_eq!(
|
||||
grid.builds.get(),
|
||||
builds,
|
||||
"{label}: foreground edit must reuse grid"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn actual_canvas_keeps_background_cached_while_selection_and_revision_change() {
|
||||
use super::super::{Context, DiagramCanvas, State};
|
||||
let mut renderer = renderer().await;
|
||||
let mut project = Project::empty();
|
||||
let a = project
|
||||
.add_element(Kind::Entity, Layer::Oa, "Sensor", None)
|
||||
.unwrap();
|
||||
let b = project
|
||||
.add_element(Kind::Actor, Layer::Oa, "Controller", None)
|
||||
.unwrap();
|
||||
let diagram = project.ensure_diagram(Layer::Oa);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Overview;
|
||||
let node = project.place(diagram, a, Position { x: 20., y: 50. });
|
||||
project.place(diagram, b, Position { x: 100., y: 160. });
|
||||
project.relate(RelationKind::CommunicationMean, "Link", a, b);
|
||||
let mut canvas = DiagramCanvas {
|
||||
selection: None,
|
||||
allocation: None,
|
||||
project: &project,
|
||||
view_index: None,
|
||||
diagram,
|
||||
selected: a,
|
||||
navigation_focus: None,
|
||||
zoom: 0.75,
|
||||
pan: Vector::ZERO,
|
||||
context: Context {
|
||||
document: project.id,
|
||||
diagram,
|
||||
revision: 0,
|
||||
epoch: 0,
|
||||
},
|
||||
selected_bend: None,
|
||||
selected_attachment: None,
|
||||
editable: true,
|
||||
placing: false,
|
||||
connection: None,
|
||||
};
|
||||
let state = State::default();
|
||||
let size = Size::new(360., 280.);
|
||||
let bounds = Rectangle::with_size(size);
|
||||
let draw = |canvas: &DiagramCanvas<'_>, renderer: &Renderer, bounds| {
|
||||
canvas::Program::draw(
|
||||
canvas,
|
||||
&state,
|
||||
renderer,
|
||||
&crate::style::theme(),
|
||||
bounds,
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
};
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
assert_eq!(layers.len(), 3, "background, scene, warning overlay");
|
||||
let original = pixels(&mut renderer, size, layers);
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
canvas.selected = b;
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
let selected = pixels(&mut renderer, size, layers);
|
||||
assert_ne!(original, selected, "selection must remain live");
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
let mut changed = project.clone();
|
||||
changed
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&node)
|
||||
.unwrap()
|
||||
.position
|
||||
.x += 30.;
|
||||
canvas.project = &changed;
|
||||
canvas.context.revision += 1;
|
||||
let layers = draw(&canvas, &renderer, bounds);
|
||||
let moved = pixels(&mut renderer, size, layers);
|
||||
assert_ne!(selected, moved, "new model revision must remain live");
|
||||
assert_eq!(state.grid.builds.get(), 1);
|
||||
canvas.pan.x = -0.25;
|
||||
let _ = draw(&canvas, &renderer, bounds);
|
||||
assert_eq!(state.grid.builds.get(), 2);
|
||||
let _ = draw(
|
||||
&canvas,
|
||||
&renderer,
|
||||
Rectangle::with_size(Size::new(361., 280.)),
|
||||
);
|
||||
assert_eq!(state.grid.builds.get(), 3);
|
||||
}
|
||||
@@ -196,8 +196,27 @@ impl DiagramCanvas<'_> {
|
||||
let pointer = cursor.position()?;
|
||||
let local = Point::new(pointer.x - bounds.x, pointer.y - bounds.y);
|
||||
let scene = self.scene(state);
|
||||
let world = self.world(local);
|
||||
// Preserve foreground selected handles without bypassing Shift or the
|
||||
// mixed-group admission/refusal lifecycle below.
|
||||
let state_handle = self.project.diagrams[&self.diagram]
|
||||
.projection
|
||||
.state_machine()
|
||||
.is_some()
|
||||
&& scene.routed.edges.get(&self.selected).is_some_and(|edge| {
|
||||
!edge.custom_suspended
|
||||
&& (self.attachment_at(&scene, world).is_some()
|
||||
|| edge
|
||||
.pins
|
||||
.iter()
|
||||
.any(|pin| world.distance(point(*pin)) <= 8. / self.zoom))
|
||||
});
|
||||
let target = if self.allocation_surface() {
|
||||
self.allocation_press_target(&scene, local)
|
||||
} else if state_handle {
|
||||
Some(Target::Edge(self.selected))
|
||||
} else if let Some(id) = self.hit_state_label(&scene.routed, local) {
|
||||
Some(Target::Edge(id))
|
||||
} else if let Some(port) = self.hit_port(&scene.view, local) {
|
||||
Some(Target::Port {
|
||||
port: port.element,
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use super::*;
|
||||
use crate::{
|
||||
app::multiselect::{Choice, Event, context},
|
||||
diagram_selection::Selection,
|
||||
diagram_usage::Target,
|
||||
};
|
||||
|
||||
fn fixture() -> (Project, Id, Id, Id, Id) {
|
||||
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, "Modes", Some(owner))
|
||||
.unwrap();
|
||||
let region = p
|
||||
.add_element(Kind::Region, Layer::Oa, "Main", Some(machine))
|
||||
.unwrap();
|
||||
let a = p
|
||||
.add_element(Kind::State, Layer::Oa, "A", Some(region))
|
||||
.unwrap();
|
||||
let b = p
|
||||
.add_element(Kind::State, Layer::Oa, "B", Some(region))
|
||||
.unwrap();
|
||||
let mut edges = [
|
||||
p.relate(RelationKind::StateTransition, "Ready\nfor review", a, b),
|
||||
p.relate(RelationKind::StateTransition, "Ready\nfor review", a, b),
|
||||
];
|
||||
edges.sort();
|
||||
let diagram = Id::new_v4();
|
||||
p.diagrams.insert(
|
||||
diagram,
|
||||
Diagram {
|
||||
id: diagram,
|
||||
alias: "state_input".into(),
|
||||
name: "State input".into(),
|
||||
description: String::new(),
|
||||
layer: Layer::Oa,
|
||||
projection: ProjectionKind::StateMachine { machine },
|
||||
view_options: Default::default(),
|
||||
nodes: BTreeMap::new(),
|
||||
layout: None,
|
||||
routes: edges
|
||||
.iter()
|
||||
.map(|edge| EdgeRoute {
|
||||
edge: *edge,
|
||||
source_attachment: None,
|
||||
target_attachment: None,
|
||||
waypoints: vec![Position { x: 200., y: 150. }],
|
||||
label: None,
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
let node = p.place(diagram, a, Position { x: 100., y: 100. });
|
||||
p.place(diagram, b, Position { x: 650., y: 100. });
|
||||
let routed = route_diagram(&p, &p.diagrams[&diagram], &project_view(&p, diagram));
|
||||
for route in &mut p.diagrams.get_mut(&diagram).unwrap().routes {
|
||||
let label = &routed.edges[&route.edge].label;
|
||||
route.label = Some(EdgeLabel {
|
||||
fraction: label.fraction,
|
||||
offset: Position {
|
||||
x: 200. - label.anchor.x,
|
||||
y: 150. - label.anchor.y,
|
||||
},
|
||||
});
|
||||
}
|
||||
(p, diagram, edges[0], edges[1], node)
|
||||
}
|
||||
|
||||
fn canvas(p: &Project, diagram: Id, selected: Id, zoom: f32) -> DiagramCanvas<'_> {
|
||||
DiagramCanvas {
|
||||
selection: None,
|
||||
project: p,
|
||||
view_index: None,
|
||||
diagram,
|
||||
selected,
|
||||
navigation_focus: None,
|
||||
zoom,
|
||||
pan: Vector::new(100., 100.),
|
||||
context: Context {
|
||||
document: p.id,
|
||||
diagram,
|
||||
revision: 0,
|
||||
epoch: 0,
|
||||
},
|
||||
selected_bend: None,
|
||||
selected_attachment: None,
|
||||
editable: true,
|
||||
placing: false,
|
||||
connection: None,
|
||||
allocation: None,
|
||||
}
|
||||
}
|
||||
fn screen(canvas: &DiagramCanvas<'_>, y: f32) -> Point {
|
||||
Point::new(
|
||||
200. * canvas.zoom + canvas.pan.x,
|
||||
y * canvas.zoom + canvas.pan.y,
|
||||
)
|
||||
}
|
||||
fn pointer(
|
||||
canvas: &DiagramCanvas<'_>,
|
||||
state: &mut State,
|
||||
event: mouse::Event,
|
||||
at: Point,
|
||||
) -> Option<Message> {
|
||||
canvas::Program::update(
|
||||
canvas,
|
||||
state,
|
||||
&canvas::Event::Mouse(event),
|
||||
Rectangle::with_size(Size::new(1800., 1200.)),
|
||||
mouse::Cursor::Available(at),
|
||||
)
|
||||
.and_then(|a| a.into_inner().0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_generic_front_label_beats_node_and_selected_back_label_at_three_zooms() {
|
||||
for zoom in [0.5, 1., 1.5] {
|
||||
let (p, diagram, low, high, _) = fixture();
|
||||
let mut canvas = canvas(&p, diagram, low, zoom);
|
||||
// Ten world pixels below center remains on both labels but avoids bends.
|
||||
let at = screen(&canvas, 160.);
|
||||
let scene = canvas.scene(&State::default());
|
||||
assert!(canvas.hit(&scene.view, at).is_some());
|
||||
assert_eq!(canvas.hit_label(&scene.routed, at), Some(high));
|
||||
// Selected bends have a screen radius: move further down at half zoom.
|
||||
let at = if zoom == 0.5 {
|
||||
screen(&canvas, 167.)
|
||||
} else {
|
||||
at
|
||||
};
|
||||
assert_eq!(
|
||||
canvas
|
||||
.context_request(
|
||||
&State::default(),
|
||||
at,
|
||||
Rectangle::with_size(Size::new(1800., 1200.))
|
||||
)
|
||||
.object,
|
||||
high
|
||||
);
|
||||
let mut state = State::default();
|
||||
assert!(
|
||||
matches!(pointer(&canvas, &mut state, mouse::Event::ButtonPressed(mouse::Button::Left), at), Some(Message::Select(id)) if id == high)
|
||||
);
|
||||
assert!(state.drag.is_none() && state.route_drag.is_none());
|
||||
canvas.selected = high;
|
||||
let mut state = State::default();
|
||||
pointer(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
at,
|
||||
);
|
||||
assert!(matches!(
|
||||
state.route_drag.as_ref().map(|g| &g.handle),
|
||||
Some(Handle::Label { .. })
|
||||
));
|
||||
canvas.editable = false;
|
||||
let mut state = State::default();
|
||||
assert!(
|
||||
matches!(pointer(&canvas, &mut state, mouse::Event::ButtonPressed(mouse::Button::Left), at), Some(Message::Select(id)) if id == high)
|
||||
);
|
||||
assert!(state.drag.is_none() && state.route_drag.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_generic_shift_label_and_handle_keep_exact_targets_and_mixed_refusal() {
|
||||
let (p, diagram, low, high, node) = fixture();
|
||||
let base = canvas(&p, diagram, low, 1.);
|
||||
let mut single_state = State::default();
|
||||
assert!(
|
||||
matches!(pointer(&base, &mut single_state, mouse::Event::ButtonPressed(mouse::Button::Left), screen(&base, 150.)), Some(Message::Route(RouteEvent::SelectBend(id, 0))) if id == low)
|
||||
);
|
||||
assert!(matches!(
|
||||
single_state.route_drag.as_ref().map(|g| &g.handle),
|
||||
Some(Handle::Bend(0))
|
||||
));
|
||||
let mut selection = Selection::new(context(base.context).scope);
|
||||
selection.add(Target::Edge(low), |_| true);
|
||||
selection.add(Target::Placement(node), |_| true);
|
||||
let canvas = DiagramCanvas {
|
||||
selection: Some(&selection),
|
||||
..base
|
||||
};
|
||||
for (y, expected) in [(160., high), (150., low)] {
|
||||
let mut state = State {
|
||||
modifiers: iced::keyboard::Modifiers::SHIFT,
|
||||
..State::default()
|
||||
};
|
||||
assert!(
|
||||
matches!(pointer(&canvas, &mut state, mouse::Event::ButtonPressed(mouse::Button::Left), screen(&canvas,y)), Some(Message::DiagramSelection(Event::Choose(_, Target::Edge(id), Choice::Toggle))) if id == expected)
|
||||
);
|
||||
assert!(state.drag.is_none() && state.route_drag.is_none());
|
||||
}
|
||||
let mut state = State::default();
|
||||
let at = screen(&canvas, 150.);
|
||||
assert_eq!(
|
||||
canvas
|
||||
.context_request(&state, at, Rectangle::with_size(Size::new(1800., 1200.)))
|
||||
.bend,
|
||||
Some(0)
|
||||
);
|
||||
pointer(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
at,
|
||||
);
|
||||
assert!(state.group.is_some() && state.route_drag.is_none());
|
||||
let moved = at + Vector::new(20., 20.);
|
||||
pointer(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::CursorMoved { position: moved },
|
||||
moved,
|
||||
);
|
||||
assert!(matches!(
|
||||
pointer(
|
||||
&canvas,
|
||||
&mut state,
|
||||
mouse::Event::ButtonReleased(mouse::Button::Left),
|
||||
moved
|
||||
),
|
||||
Some(Message::DiagramSelection(Event::Refuse(_, _)))
|
||||
));
|
||||
assert!(state.route_drag.is_none() && state.drag.is_none());
|
||||
}
|
||||
@@ -6,7 +6,7 @@ impl DiagramCanvas<'_> {
|
||||
pub(crate) fn work_dirty_point(&self, bounds: Rectangle, screen: Point) -> bool {
|
||||
let diagram = &self.project.diagrams[&self.diagram];
|
||||
let view = self.projected(diagram);
|
||||
let routed = routing::route_diagram(self.project, diagram, &view);
|
||||
let routed = crate::state_label::route_diagram(self.project, diagram, &view);
|
||||
self.work_dirty_press(
|
||||
&view,
|
||||
&routed,
|
||||
@@ -243,21 +243,6 @@ impl canvas::Program<Message> for WorkFeedback<'_> {
|
||||
let Some(frozen) = &feedback.frozen else {
|
||||
return vec![];
|
||||
};
|
||||
if let Some(delta) = feedback.ghost_delta {
|
||||
for region in &frozen.moving {
|
||||
let top = Point::new(
|
||||
(region.x + delta.x) * self.0.zoom + self.0.pan.x,
|
||||
(region.y + delta.y) * self.0.zoom + self.0.pan.y,
|
||||
);
|
||||
frame.stroke(
|
||||
&Path::rectangle(
|
||||
top,
|
||||
iced::Size::new(region.width * self.0.zoom, region.height * self.0.zoom),
|
||||
),
|
||||
Stroke::default().with_color(color).with_width(2.),
|
||||
);
|
||||
}
|
||||
}
|
||||
for index in &feedback.targets {
|
||||
let target = &frozen.targets[*index];
|
||||
let region = target.region;
|
||||
|
||||
@@ -327,7 +327,7 @@ pub(crate) fn saved_usages_for(p: &Project, subject: Id, only_diagram: Option<Id
|
||||
"state machine view"
|
||||
}
|
||||
.into(),
|
||||
bounds: routed_bounds(routing::route_diagram(p, diagram, &view).bounds),
|
||||
bounds: routed_bounds(crate::state_label::route_diagram(p, diagram, &view).bounds),
|
||||
});
|
||||
}
|
||||
for node in &view.nodes {
|
||||
@@ -407,7 +407,7 @@ pub(crate) fn saved_usages_for(p: &Project, subject: Id, only_diagram: Option<Id
|
||||
}
|
||||
}
|
||||
if view.edges.iter().any(|edge| edge.id == subject)
|
||||
&& let Some(edge) = routing::route_diagram(p, diagram, &view)
|
||||
&& let Some(edge) = crate::state_label::route_diagram(p, diagram, &view)
|
||||
.edges
|
||||
.get(&subject)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@ mod source_navigation;
|
||||
mod source_rename;
|
||||
mod source_tree;
|
||||
mod source_workspace;
|
||||
mod state_label;
|
||||
mod style;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//! Desktop routing uses the same font/shaping defaults as canvas text.
|
||||
use iced::advanced::{
|
||||
graphics::text::Paragraph,
|
||||
text::{self, Paragraph as _},
|
||||
};
|
||||
use oa_core::{
|
||||
Diagram, Id, Project,
|
||||
projection::ProjectedView,
|
||||
routing::{self, RoutedView, StateLabelLayout},
|
||||
};
|
||||
|
||||
fn measure_line(line: &str) -> f32 {
|
||||
let defaults = iced::widget::canvas::Text::default();
|
||||
Paragraph::with_text(text::Text {
|
||||
content: line,
|
||||
bounds: iced::Size::INFINITE,
|
||||
size: StateLabelLayout::FONT_SIZE.into(),
|
||||
line_height: text::LineHeight::Absolute(StateLabelLayout::LINE_HEIGHT.into()),
|
||||
font: defaults.font,
|
||||
align_x: defaults.align_x,
|
||||
align_y: defaults.align_y,
|
||||
shaping: defaults.shaping,
|
||||
wrapping: text::Wrapping::None,
|
||||
})
|
||||
.min_width()
|
||||
}
|
||||
|
||||
pub fn route_diagram(project: &Project, diagram: &Diagram, view: &ProjectedView) -> RoutedView {
|
||||
routing::route_diagram_with_state_label_measurement(project, diagram, view, &measure_line)
|
||||
}
|
||||
|
||||
pub fn route_edge_preview(
|
||||
project: &Project,
|
||||
diagram: &Diagram,
|
||||
view: &ProjectedView,
|
||||
baseline: &RoutedView,
|
||||
edge: Id,
|
||||
) -> RoutedView {
|
||||
routing::route_edge_preview_with_state_label_measurement(
|
||||
project,
|
||||
diagram,
|
||||
view,
|
||||
baseline,
|
||||
edge,
|
||||
&measure_line,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wide_glyphs_use_shaped_width_instead_of_character_estimate() {
|
||||
let wide = "W".repeat(40);
|
||||
let narrow = "i".repeat(40);
|
||||
let width = measure_line(&wide);
|
||||
assert!(width > measure_line(&narrow) * 2.0);
|
||||
assert!(width > 40.0 * 7.0);
|
||||
let plan = StateLabelLayout::new(&wide, &wide, &measure_line);
|
||||
assert_eq!(
|
||||
plan.size().width,
|
||||
width + 2.0 * StateLabelLayout::HORIZONTAL_PADDING
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_fallback_and_combining_text_have_finite_measured_bounds() {
|
||||
for line in ["漢字 Ελληνικά العربية", "e\u{301} cafe\u{301}", "🙂 🧭"]
|
||||
{
|
||||
let width = measure_line(line);
|
||||
assert!(width.is_finite() && width > 0.0, "{line:?}: {width}");
|
||||
let plan = StateLabelLayout::new(line, line, &measure_line);
|
||||
assert_eq!(
|
||||
plan.size().width,
|
||||
width + 2.0 * StateLabelLayout::HORIZONTAL_PADDING
|
||||
);
|
||||
}
|
||||
assert!(measure_line("e\u{301}") < measure_line("ee"));
|
||||
}
|
||||
}
|
||||
+1
-13
@@ -2702,19 +2702,7 @@ performer change with diagram placement. The preview MUST identify the
|
||||
destination and preserve readable work and destination identity. Destination
|
||||
identity is shown outside the canvas, independently of explanatory or refusal
|
||||
text. The outlined target's hit region remains fixed throughout the gesture;
|
||||
feedback MUST NOT obscure the work. Preserve pointer-relative placement when
|
||||
the requested position is valid. When an outgoing drop would leave work
|
||||
overlapping its former performer, the editor MAY propose a minimal grid-aligned
|
||||
translation through a crossed performer boundary, with one grid unit of
|
||||
clearance. It MUST preview that exact position, translate the complete affected
|
||||
work group rigidly, and recheck all semantic and collision constraints on release.
|
||||
It MUST NOT move unrelated placements or search arbitrary free space. If no
|
||||
proposal is valid, a pointer-following outline MAY accompany the last valid
|
||||
preview; that retained preview MUST NOT authorize a refused release.
|
||||
Outgoing work headers MAY be partly outside the viewport provided each retains
|
||||
at least a 24-by-24 logical-pixel grab region. Releasing the pointer outside the
|
||||
viewport still cancels. Other assignment and Arrange gestures retain their
|
||||
complete-header visibility requirement.
|
||||
feedback MUST NOT obscure the work or change its pointer-relative placement.
|
||||
A clean click selects without applying unrelated drafts. A refused work press
|
||||
with an unapplied property draft MUST preserve its text, caret and focused
|
||||
editor. An accepted drag is one atomic model/source/history transaction:
|
||||
|
||||
+1
-13
@@ -2128,19 +2128,7 @@ performer change with diagram placement. The preview MUST identify the
|
||||
destination and preserve readable work and destination identity. Destination
|
||||
identity is shown outside the canvas, independently of explanatory or refusal
|
||||
text. The outlined target's hit region remains fixed throughout the gesture;
|
||||
feedback MUST NOT obscure the work. Preserve pointer-relative placement when
|
||||
the requested position is valid. When an outgoing drop would leave work
|
||||
overlapping its former performer, the editor MAY propose a minimal grid-aligned
|
||||
translation through a crossed performer boundary, with one grid unit of
|
||||
clearance. It MUST preview that exact position, translate the complete affected
|
||||
work group rigidly, and recheck all semantic and collision constraints on release.
|
||||
It MUST NOT move unrelated placements or search arbitrary free space. If no
|
||||
proposal is valid, a pointer-following outline MAY accompany the last valid
|
||||
preview; that retained preview MUST NOT authorize a refused release.
|
||||
Outgoing work headers MAY be partly outside the viewport provided each retains
|
||||
at least a 24-by-24 logical-pixel grab region. Releasing the pointer outside the
|
||||
viewport still cancels. Other assignment and Arrange gestures retain their
|
||||
complete-header visibility requirement.
|
||||
feedback MUST NOT obscure the work or change its pointer-relative placement.
|
||||
A clean click selects without applying unrelated drafts. A refused work press
|
||||
with an unapplied property draft MUST preserve its text, caret and focused
|
||||
editor. An accepted drag is one atomic model/source/history transaction:
|
||||
|
||||
@@ -498,3 +498,9 @@ editing remains separate from semantic reconnect. The [decision and evidence
|
||||
map](edge-attachments-20260922.md) records alternatives, lifecycle rules,
|
||||
independent falsifiers, evaluators and the Process/Scenario applicability limits.
|
||||
This is branch-specific work; it does not establish an integrated release.
|
||||
|
||||
## External State transition label measurement — 2026-09-24
|
||||
|
||||
Issue #36's reproduced collision comes from a two-line title painted in space reserved for one line, with bounds sized from its summary. Adopt one derived hard-line plan measured before routing, shared by paint/hit/preview/focus/SVG. Preserve raw excerpt provenance, authored centers/routes and model/source bytes. Desktop measures with the canvas Paragraph defaults; core retains an explicit approximate fallback. Opaque State label backgrounds own covered hits in reverse paint order while selected handles and group admission retain precedence.
|
||||
|
||||
The [contract](state-label-layout-contract.md) is authoritative for exact rules and alternatives. The [verification](experiments/state-label-layout-20260924/README.md) records the width-estimate and generic-hit falsifiers, independent review, scoped Windows outcomes, restored-window input limitation and reasons not to infer universal font or collision-free layout support. Revisit if new ink-overhang, shaping, routing-cost, or authored-occlusion evidence invalidates the bounded design. Dynamic node/row sizing and source/model changes were deliberately excluded.
|
||||
|
||||
+9
-1
@@ -1,4 +1,12 @@
|
||||
# Integrated prerelease source checkpoint — 2026-09-24 UTC
|
||||
# Verified branch checkpoint — State label follow-up
|
||||
|
||||
This branch's code through **70bdcdf8fec1728e971cd90ad7f79a1df99777ff** corrects external State transition title/summary layout and measured label hit areas. The [contract](state-label-layout-contract.md) and [verification](experiments/state-label-layout-20260924/README.md) identify exact source, commands, native fixtures, original evidence and limits. Windows focused core68, full desktop1540/7 existing ignores, formatting, strict workspace/all-target Clippy, builds, and six maximized-window B/W/U Save/reopen checks pass. Restored-window coordinate input remains an explicit native-test limitation. This is branch-specific evidence, not merged-main or complete-application acceptance; live integration status is [issue36](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/36).
|
||||
|
||||
The separate [September24 integrated prerelease](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/releases/tag/v0.2.0-preview.integrated.20260924) is published at **715036e132677dd2bc2fb37ed316d93dc9987aec** with owner publication receipt **76acfb11085ae36bd65971f5f1cf34f45d362ada**. The label follow-up does not replace that tag or its artifacts. Linux remains excluded. Later private analysis branch status must be read from Gitea and that branch, not inferred from this checkpoint.
|
||||
|
||||
## Historical source checkpoint inherited from715036e — 2026-09-24 UTC
|
||||
|
||||
The source-preparation statements below describe that earlier checkpoint. Its then-pending prerelease gates were subsequently completed in the publication linked above; its other scoped evidence retains its original code versions.
|
||||
|
||||
This branch assembles peer contributions for the Windows prerelease in issue37.
|
||||
[PROJECT_GOAL.md](../PROJECT_GOAL.md) remains authoritative. The complete OA/SA
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# Outgoing allocation dragging
|
||||
|
||||
Scope: [issue 40](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/40),
|
||||
based on published Windows source `715036e132677dd2bc2fb37ed316d93dc9987aec`.
|
||||
This contribution is not integrated release acceptance. User requirements remain
|
||||
in `PROJECT_GOAL.md`; this note records a revision to derived interaction policy.
|
||||
|
||||
## Reproduction and cause
|
||||
|
||||
The released executable SHA256
|
||||
`2c03bd6cc3f1110c219e4f57ab94874a121590f83779239a93289b7170664399`
|
||||
opened a copied Allocation/Process companion fixture on Windows. At 1440x900,
|
||||
72% Fit, the OA assign view's X-receive was dragged from window (486,546) to
|
||||
(365,546), just beyond A's left edge. It remained assigned at its original
|
||||
position, reporting enlarged card overlap. A control drag to (625,572) removed
|
||||
assignment; Undo restored the original scene. These are actual native end-state
|
||||
observations; intermediate jitter was diagnosed from source, not frame capture.
|
||||
|
||||
Frozen targets resolve from the pointer, while placement retains its grab
|
||||
offset. The pointer therefore leaves its performer before the full work card
|
||||
does. Former parent/child intersection becomes unrelated overlap after removing
|
||||
allocation. Refused motion previously cleared the preview and redrew the saved
|
||||
scene. Additionally, whole-header viewport containment rejects a useful outgoing
|
||||
position in the fitted reproduction even when its model geometry is valid.
|
||||
|
||||
## Selected policy
|
||||
|
||||
Try exact snapped pointer placement first. Only an outgoing assignment candidate
|
||||
with a placement refusal may propose bounded source clearance: move the entire
|
||||
captured work footprint through a crossed frozen source boundary, clearing the
|
||||
post-removal source enclosure by one 10-world-unit grid cell. Consider at most
|
||||
four directions, ordered by press-to-pointer boundary-crossing fraction and a
|
||||
fixed direction tie-break. Existing placement, collision, semantic, exact-command
|
||||
and source/history admission remain authoritative. No neighbor is moved, no
|
||||
general packing/search occurs, and no semantic refusal is overridden.
|
||||
|
||||
The accepted/candidate semantic indexes are reused during fallback projections.
|
||||
Outgoing headers may be partly clipped but each must retain a 24x24 logical-pixel
|
||||
grab region. Other assignment and Arrange visibility rules remain unchanged.
|
||||
The exact corrected candidate is previewed; release recomputes it. Blocked
|
||||
in-bounds motion retains its last admitted scene and adds an orange pointer
|
||||
outline. This display state confers no permission to commit on refused release.
|
||||
Cancel, outside release and gesture invalidation retire it normally.
|
||||
|
||||
Rejected alternatives: retaining a moving outline alone leaves the boundary
|
||||
dead band; allowing unrelated overlap makes unassigned work look allocated;
|
||||
general auto-layout changes unrelated placement and makes drag intent harder to
|
||||
predict. The previous unconditional pointer-offset rule is narrowed in
|
||||
`docs/language.rst` and its HTML copy. Real neighboring-card collisions and
|
||||
insufficient visible grab area can still refuse a drop.
|
||||
|
||||
## Verification status
|
||||
|
||||
Independent source diagnosis and repair critique found no semantic/stale/cancel
|
||||
bypass. Review exposed the remaining whole-header camera restriction and an
|
||||
obsolete retained-preview assertion; both were revised. Added checks cover
|
||||
OA/SA boundary clearance, corners, source shrinkage, rigid descendants/groups,
|
||||
real App frame-by-frame input, exact source/history, cancellation, blocked release
|
||||
and outgoing grab-area boundaries. At clean source
|
||||
`1a669130fb6d2b65279aab3847e966a1a282c191`, the Windows allocation-drag
|
||||
suite passes 121 tests (none failed or ignored; 1,424 filtered out), strict
|
||||
desktop all-target Clippy passes, and the normal desktop development build
|
||||
passes. [Raw results and reproduction commands](experiments/drag-out-20260925/README.md)
|
||||
preserve the source, platform, failed attempts and exact scope. These checks do
|
||||
not prove native usability. Computer Use was stopped with the physical Escape
|
||||
key before the changed executable could be inspected. Fitted-view dragging,
|
||||
clipped-card identification/regrabbing, native Undo/Redo and Save/reopen remain
|
||||
unverified. The issue remains open; no integration or release acceptance follows
|
||||
from the targeted gates.
|
||||
|
||||
Test-only successor `24b1eab07d306d8cb091bb46f7daf9f027a8f1c0` additionally
|
||||
checks an actual fallback blocked by unrelated work, including preservation of
|
||||
an existing redo branch, and an outgoing move while an allocated sibling keeps
|
||||
the source enclosure expanded. Both run for OA and SA. All 123 allocation-drag
|
||||
tests and strict desktop Clippy pass at that successor. Fresh independent review
|
||||
confirms those tests exercise their claimed invariants, with no material source
|
||||
finding in this scope. The same linked evidence includes the new logs and review;
|
||||
production code and native-acceptance limits remain unchanged.
|
||||
|
||||
The first build attempt failed before tests because the sparse checkout omitted
|
||||
`build/windows_manifest.rs`; restoring the tracked build directory corrected the
|
||||
checkout. The local drive initially had about 22 MB free. Git sparse checkout and
|
||||
Cargo's package-scoped cleanup of inactive desktop artifacts recovered capacity.
|
||||
Automatic review rejected recursive deletion of the extracted preview; it was
|
||||
left intact. No published package or unique evidence was deleted.
|
||||
The second attempt also exposed omitted tracked harness fixtures and a `Self`
|
||||
typo in a standalone test helper; both were corrected before the passing run.
|
||||
That failed compiler process was terminated after its errors were captured.
|
||||
@@ -1 +0,0 @@
|
||||
*.log -text
|
||||
@@ -1,91 +0,0 @@
|
||||
# Small architecture diagram CPU investigation
|
||||
|
||||
Question: does core projection/routing explain [issue 44's low FPS](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/44)
|
||||
with roughly eight architecture cards? This experiment narrows that question;
|
||||
it neither reproduces native frame rate nor establishes a complete cause.
|
||||
|
||||
## Workload and source
|
||||
|
||||
The screenshot uses names from `examples/release1`. Its exact model, projection,
|
||||
camera and authored geometry are unavailable. The probe loads the unchanged
|
||||
`examples/release1/model.json`, measures its five saved views, and constructs a
|
||||
clearly labeled eight-card SA Overview approximation using those semantic
|
||||
elements. Approximate coordinates and stable placement IDs are in the probe;
|
||||
it has 8 projected nodes, 6 ports and 13 projected edges. It is not a recovered
|
||||
copy of the user's diagram. All data changes occur in memory.
|
||||
|
||||
Source: `24b1eab07d306d8cb091bb46f7daf9f027a8f1c0`; core, manifest and lockfile
|
||||
are unchanged from the drag contribution's `1a669130` production source.
|
||||
Windows 10.0.26200, Rust 1.98.1, x86_64-pc-windows-gnullvm, LLVM-MinGW
|
||||
20260908 UCRT, reported CPU Intel Core i7-10870H with 4 exposed cores/threads.
|
||||
The probe was optimized with `-O` and linked to the existing **unoptimized
|
||||
development-profile core library** used by the completed desktop test build.
|
||||
This distinction matters: optimizing the driver does not optimize that library.
|
||||
The desktop test process had completed before measurement; no Cargo compilation
|
||||
was started until after measurement. Other operating-system workloads were not
|
||||
controlled, so outliers are retained rather than interpreted as stable capacity.
|
||||
|
||||
## Method and results
|
||||
|
||||
Each phase gets 3 warmups and 40 timed samples, with returned values passed to
|
||||
`black_box`. Reported p50/p95 select sorted index `ceil((n-1)*p)` as implemented
|
||||
in the source. Returned output destruction is outside each timed phase. Diagram
|
||||
movement uses a deterministic small delta sequence and the existing descendant
|
||||
and authored-route translation APIs. Index creation, projection, routing, and
|
||||
combined movement preparation are measured separately. The probe does not model
|
||||
allocation transaction admission or an Iced frame.
|
||||
|
||||
For the eight-card approximation, `performance-01.log` records:
|
||||
|
||||
| Phase | p50 ms | p95 ms | max ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Reused-index projection | 0.3885 | 0.7550 | 1.2965 |
|
||||
| Fresh-index projection | 1.7135 | 2.7022 | 4.2204 |
|
||||
| Settled routing | 2.4525 | 4.4617 | 4.7735 |
|
||||
| Moved routing | 3.1907 | 4.4781 | 4.5672 |
|
||||
| Movement preparation + projection + routing | 4.2640 | 6.8259 | 8.8052 |
|
||||
|
||||
This workload does not expose severe core latency sufficient by itself to
|
||||
explain very low FPS. It does not rule out different authored routes, allocation
|
||||
admission, hardware/rendering, or the user's exact scene. Repeated fresh-index
|
||||
projection has a measurable cost, but no change is justified solely by this
|
||||
small sample. No FPS or performance-fix claim follows.
|
||||
|
||||
## Source observations and next discriminating measurement
|
||||
|
||||
Independent read-only hot-path review found that `DiagramCanvas::scene` already
|
||||
caches projection and routing by semantic context and transient geometry.
|
||||
Ordinary node movement changes preview geometry and routes the full view;
|
||||
route gestures use `route_edge_preview`. Ordinary hover need not reroute.
|
||||
|
||||
`diagram/port_labels.rs` requests redraw on every mouse event, including when
|
||||
no tooltip is visible. `DiagramCanvas::draw` rebuilds geometry and each dot-grid
|
||||
circle on every draw. At a 1900x660 canvas this yields about 2,240 circles at
|
||||
zoom 1 and 6,528 at minimum spacing. Scene caching does not cache that geometry.
|
||||
`views.rs` also performs an unindexed `project_view` during app view rebuilding;
|
||||
legacy node-motion redraw does not itself require that full UI rebuild.
|
||||
|
||||
Next, measure actual canvas draw/frame costs independently for idle hover, pan,
|
||||
ordinary node drag and allocation drag, with grid generation separated from
|
||||
node/edge/text rendering and frame presentation. Compare the same scene/camera
|
||||
and renderer before changing production code. Native automation was stopped
|
||||
with Escape; no GPU/frame measurement was attempted in this experiment. Live
|
||||
ownership and subsequent results belong to issue 44.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Build `oa-core` at the identified source with the stated Windows toolchain, then
|
||||
compile this standalone probe against that exact library and matching dependency
|
||||
directory. For a fresh standard Cargo development build:
|
||||
|
||||
```powershell
|
||||
cargo build --locked -p oa-core
|
||||
rustc --edition=2024 -O harness/experiments/architecture-perf-20260925/architecture_perf_probe.rs --extern oa_core=target/debug/liboa_core.rlib -L dependency=target/debug/deps -C linker=clang.exe -o architecture_perf_probe.exe
|
||||
./architecture_perf_probe.exe examples/release1/model.json 40
|
||||
```
|
||||
|
||||
The actual run linked `target/debug/deps/liboa_core-2f3c5174cb6398fd.rlib`
|
||||
and used the same dependency directory. Set the linker/runtime PATH as required
|
||||
by the Windows toolchain. Compiler flags, core profile, machine load and fixture
|
||||
all affect timings; preserve them when comparing candidates. This standalone
|
||||
probe is experimental evidence, not a production dependency or a timing test.
|
||||
@@ -1,166 +0,0 @@
|
||||
//! Local-only CPU diagnosis. Does not measure native FPS, canvas redraw, or GPU.
|
||||
//! Usage: architecture_perf_probe.exe PATH_TO_RELEASE1_MODEL_JSON [SAMPLES=40]
|
||||
use oa_core::{persistence, projection, routing, view_options::ViewIndex, *};
|
||||
use std::{collections::BTreeMap, hint::black_box, time::Instant};
|
||||
|
||||
fn measure<T>(label: &str, samples: usize, mut f: impl FnMut(usize) -> T) {
|
||||
for i in 0..3 {
|
||||
black_box(f(i));
|
||||
}
|
||||
let mut ms = Vec::with_capacity(samples);
|
||||
for i in 0..samples {
|
||||
let start = Instant::now();
|
||||
let output = black_box(f(i));
|
||||
ms.push(start.elapsed().as_secs_f64() * 1000.0);
|
||||
drop(output); // Exclude destruction consistently from each timed phase.
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
let percentile = |p: f64| ms[((samples - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={samples} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
percentile(0.5),
|
||||
percentile(0.95),
|
||||
ms[samples - 1]
|
||||
);
|
||||
}
|
||||
|
||||
fn probe(p: &Project, d: &Diagram, samples: usize) {
|
||||
let index = ViewIndex::new(p);
|
||||
let view = projection::project_diagram_with_index(p, d, &index);
|
||||
println!(
|
||||
"\nview={} projection={:?} nodes={} ports={} edges={}",
|
||||
d.name,
|
||||
d.projection,
|
||||
view.nodes.len(),
|
||||
view.ports.len(),
|
||||
view.edges.len()
|
||||
);
|
||||
measure("index_new", samples, |_| ViewIndex::new(black_box(p)));
|
||||
measure("projection_reused_index", samples, |_| {
|
||||
projection::project_diagram_with_index(black_box(p), black_box(d), black_box(&index))
|
||||
});
|
||||
measure("projection_fresh_index", samples, |_| {
|
||||
projection::project_diagram(black_box(p), black_box(d))
|
||||
});
|
||||
measure("routing_settled", samples, |_| {
|
||||
routing::route_diagram(black_box(p), black_box(d), black_box(&view))
|
||||
});
|
||||
// Deterministic slight motion of the first root card. Includes the same
|
||||
// descendant/route translation as legacy single-card canvas drag. This is
|
||||
// presentation-only, not the allocation transaction pipeline.
|
||||
let Some(root) = view.nodes.iter().find(|n| n.visual_parent.is_none()) else {
|
||||
return;
|
||||
};
|
||||
println!("moving_root={} element={}", root.id, p.named(root.element));
|
||||
let mut moved = projection::descendant_nodes(p, d.id, root.id);
|
||||
moved.push(root.id);
|
||||
let make_moved = |i: usize| {
|
||||
let delta = Position {
|
||||
x: (i % 21) as f32 * 3.0,
|
||||
y: (i % 11) as f32 * 2.0,
|
||||
};
|
||||
let mut preview = d.clone();
|
||||
let deltas = moved
|
||||
.iter()
|
||||
.map(|id| (*id, delta))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for route in projection::translated_routes(p, d, &deltas) {
|
||||
if let Some(stored) = preview.routes.iter_mut().find(|r| r.edge == route.edge) {
|
||||
*stored = route;
|
||||
} else {
|
||||
preview.routes.push(route);
|
||||
}
|
||||
}
|
||||
for id in &moved {
|
||||
if let Some(n) = preview.nodes.get_mut(id) {
|
||||
n.position.x += delta.x;
|
||||
n.position.y += delta.y;
|
||||
}
|
||||
}
|
||||
preview
|
||||
};
|
||||
let previews = (0..samples.max(3)).map(make_moved).collect::<Vec<_>>();
|
||||
let projected = previews
|
||||
.iter()
|
||||
.map(|d| projection::project_diagram_with_index(p, d, &index))
|
||||
.collect::<Vec<_>>();
|
||||
measure("moved_projection", samples, |i| {
|
||||
projection::project_diagram_with_index(p, black_box(&previews[i]), &index)
|
||||
});
|
||||
measure("moved_routing", samples, |i| {
|
||||
routing::route_diagram(p, black_box(&previews[i]), black_box(&projected[i]))
|
||||
});
|
||||
measure("moved_prepare_projection_routing", samples, |i| {
|
||||
let preview = make_moved(i);
|
||||
let view = projection::project_diagram_with_index(p, &preview, &index);
|
||||
routing::route_diagram(p, &preview, &view)
|
||||
});
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
let path = args
|
||||
.get(1)
|
||||
.ok_or("usage: architecture_perf_probe PATH [SAMPLES]")?;
|
||||
let samples = args
|
||||
.get(2)
|
||||
.map(|s| s.parse::<usize>())
|
||||
.transpose()?
|
||||
.unwrap_or(40)
|
||||
.max(1);
|
||||
let p = persistence::decode(&std::fs::read(path)?)?;
|
||||
println!(
|
||||
"CPU-only probe; samples={samples}; fixture={path}; elements={} relations={}",
|
||||
p.elements.len(),
|
||||
p.relations.len()
|
||||
);
|
||||
for d in p.diagrams.values() {
|
||||
probe(&p, d, samples);
|
||||
}
|
||||
// Approximation of the reported eight-card screenshot, not an exact repro:
|
||||
// fixture semantics, Overview projection, synthetic positions/card sizes.
|
||||
let mut approx = p.clone();
|
||||
let id: Id = "f0440000-0000-4000-8000-000000000001".parse()?;
|
||||
approx.diagrams.insert(
|
||||
id,
|
||||
Diagram {
|
||||
id,
|
||||
alias: "fps44_approx_overview".into(),
|
||||
name: "APPROXIMATION eight-card SA Overview".into(),
|
||||
description: "Synthetic positions, not user geometry".into(),
|
||||
layer: Layer::Sa,
|
||||
projection: ProjectionKind::Overview,
|
||||
view_options: Default::default(),
|
||||
nodes: BTreeMap::new(),
|
||||
routes: vec![],
|
||||
layout: None,
|
||||
},
|
||||
);
|
||||
for (ordinal, (alias, x, y)) in [
|
||||
("sa_assessment_procedure", 30., 240.),
|
||||
("sa_triage", 510., 240.),
|
||||
("sa_control", 990., 240.),
|
||||
("sa_technician", 530., 440.),
|
||||
("sa_assess", 1490., 240.),
|
||||
("sa_repair", 1030., 440.),
|
||||
("sa_confirm", 1530., 440.),
|
||||
("sa_reset", 30., 440.),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let element = approx
|
||||
.elements
|
||||
.values()
|
||||
.find(|e| e.alias == alias)
|
||||
.ok_or_else(|| format!("missing alias {alias}"))?
|
||||
.id;
|
||||
let generated = approx.place(id, element, Position { x, y });
|
||||
let diagram = approx.diagrams.get_mut(&id).unwrap();
|
||||
let mut node = diagram.nodes.remove(&generated).unwrap();
|
||||
node.id = format!("f0440000-0000-4000-8000-{:012}", ordinal + 2).parse()?;
|
||||
diagram.nodes.insert(node.id, node);
|
||||
}
|
||||
probe(&approx, &approx.diagrams[&id], samples);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"source": "24b1eab07d306d8cb091bb46f7daf9f027a8f1c0",
|
||||
"core_profile": "dev unoptimized",
|
||||
"driver_optimization": "rustc -O",
|
||||
"samples_per_phase": 40,
|
||||
"warmups_per_phase": 3,
|
||||
"rlib_sha256": "E10C81BF28A33CD0FB6C4C26884CFFA480EF05328E78BCDB138DD4B635B2531C",
|
||||
"fixture_sha256": "EF0EF4006C3E09D5DB6E1B621256B3F7D6642F3A7B09CC34607CFBCCA830C727",
|
||||
"driver_exe_sha256": "4C66E9CDE4F08D6DFD2A0DC3DBF1A80B97E340F3F22CA001A76301F06F47C2E2",
|
||||
"native_fps_measured": false
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
CPU-only probe; samples=40; fixture=.local/drag-out-01a0d6fd/examples/release1/model.json; elements=94 relations=62
|
||||
|
||||
view=1 · OA responsibility and repair traffic projection=Allocation nodes=6 ports=0 edges=3
|
||||
index_new: n=40 p50_ms=1.0106 p95_ms=2.0583 max_ms=2.0910
|
||||
projection_reused_index: n=40 p50_ms=0.5702 p95_ms=1.2320 max_ms=1.3490
|
||||
projection_fresh_index: n=40 p50_ms=1.5505 p95_ms=3.6681 max_ms=5.1533
|
||||
routing_settled: n=40 p50_ms=0.1944 p95_ms=0.3391 max_ms=0.4246
|
||||
moving_root=3191e05c-1c7f-5f0e-beb0-5f3bded54c48 element=Control room
|
||||
moved_projection: n=40 p50_ms=0.4848 p95_ms=1.2745 max_ms=1.5797
|
||||
moved_routing: n=40 p50_ms=0.1994 p95_ms=0.7971 max_ms=1.1414
|
||||
moved_prepare_projection_routing: n=40 p50_ms=0.9250 p95_ms=2.6847 max_ms=10.4908
|
||||
|
||||
view=5 · SA pressure supervision projection=StateMachine { machine: 132b3f28-3f88-5327-9868-8c8b645ed412 } nodes=5 ports=0 edges=3
|
||||
index_new: n=40 p50_ms=0.9602 p95_ms=2.1394 max_ms=2.4512
|
||||
projection_reused_index: n=40 p50_ms=0.1697 p95_ms=0.7629 max_ms=1.1780
|
||||
projection_fresh_index: n=40 p50_ms=1.1189 p95_ms=2.6324 max_ms=3.3912
|
||||
routing_settled: n=40 p50_ms=0.7063 p95_ms=1.0812 max_ms=1.1149
|
||||
moving_root=a676fbb9-0833-59a8-98cf-a10d8f4b7434 element=Pressure supervision
|
||||
moved_projection: n=40 p50_ms=0.1565 p95_ms=0.3445 max_ms=0.3790
|
||||
moved_routing: n=40 p50_ms=0.6113 p95_ms=1.3199 max_ms=1.3733
|
||||
moved_prepare_projection_routing: n=40 p50_ms=0.8259 p95_ms=1.7640 max_ms=2.1752
|
||||
|
||||
view=2 · SA service boundary and functions projection=Allocation nodes=6 ports=6 edges=3
|
||||
index_new: n=40 p50_ms=0.7820 p95_ms=3.4180 max_ms=6.8439
|
||||
projection_reused_index: n=40 p50_ms=0.7938 p95_ms=1.9946 max_ms=2.1411
|
||||
projection_fresh_index: n=40 p50_ms=2.0597 p95_ms=3.4554 max_ms=3.6616
|
||||
routing_settled: n=40 p50_ms=0.1478 p95_ms=0.7886 max_ms=1.0081
|
||||
moving_root=8345c76a-8d85-5c31-902e-ec1cd33ed4a3 element=Pressure coordination service
|
||||
moved_projection: n=40 p50_ms=0.6914 p95_ms=1.5286 max_ms=1.8658
|
||||
moved_routing: n=40 p50_ms=0.1862 p95_ms=0.5658 max_ms=2.8924
|
||||
moved_prepare_projection_routing: n=40 p50_ms=1.1724 p95_ms=3.1806 max_ms=4.2620
|
||||
|
||||
view=4 · SA mission and included capability projection=Capabilities nodes=4 ports=1 edges=3
|
||||
index_new: n=40 p50_ms=0.6879 p95_ms=1.3582 max_ms=1.5266
|
||||
projection_reused_index: n=40 p50_ms=0.1562 p95_ms=0.6767 max_ms=1.0102
|
||||
projection_fresh_index: n=40 p50_ms=1.2929 p95_ms=2.3543 max_ms=4.0762
|
||||
routing_settled: n=40 p50_ms=0.2553 p95_ms=1.4389 max_ms=1.6677
|
||||
moving_root=069a3fb3-077c-59d8-91da-27b0665143c7 element=Assess an alarm
|
||||
moved_projection: n=40 p50_ms=0.1243 p95_ms=0.1928 max_ms=0.2212
|
||||
moved_routing: n=40 p50_ms=0.2271 p95_ms=0.6733 max_ms=1.2408
|
||||
moved_prepare_projection_routing: n=40 p50_ms=0.5542 p95_ms=1.3344 max_ms=1.8957
|
||||
|
||||
view=3 · SA service conversation only projection=Allocation nodes=6 ports=6 edges=1
|
||||
index_new: n=40 p50_ms=0.9618 p95_ms=1.6890 max_ms=2.0995
|
||||
projection_reused_index: n=40 p50_ms=0.6661 p95_ms=2.3388 max_ms=2.6429
|
||||
projection_fresh_index: n=40 p50_ms=2.3913 p95_ms=4.1941 max_ms=4.8120
|
||||
routing_settled: n=40 p50_ms=0.0660 p95_ms=0.2156 max_ms=0.6289
|
||||
moving_root=40210589-69f7-5209-b329-10bb83cf615c element=Field technician
|
||||
moved_projection: n=40 p50_ms=0.5527 p95_ms=1.1239 max_ms=1.1362
|
||||
moved_routing: n=40 p50_ms=0.0495 p95_ms=0.1771 max_ms=0.2209
|
||||
moved_prepare_projection_routing: n=40 p50_ms=0.7963 p95_ms=4.1835 max_ms=6.7551
|
||||
|
||||
view=APPROXIMATION eight-card SA Overview projection=Overview nodes=8 ports=6 edges=13
|
||||
index_new: n=40 p50_ms=0.7487 p95_ms=1.9597 max_ms=2.2005
|
||||
projection_reused_index: n=40 p50_ms=0.3885 p95_ms=0.7550 max_ms=1.2965
|
||||
projection_fresh_index: n=40 p50_ms=1.7135 p95_ms=2.7022 max_ms=4.2204
|
||||
routing_settled: n=40 p50_ms=2.4525 p95_ms=4.4617 max_ms=4.7735
|
||||
moving_root=f0440000-0000-4000-8000-000000000002 element=Assess an alarm procedure
|
||||
moved_projection: n=40 p50_ms=0.4691 p95_ms=0.8354 max_ms=1.0408
|
||||
moved_routing: n=40 p50_ms=3.1907 p95_ms=4.4781 max_ms=4.5672
|
||||
moved_prepare_projection_routing: n=40 p50_ms=4.2640 p95_ms=6.8259 max_ms=8.8052
|
||||
@@ -1 +0,0 @@
|
||||
*.log -text
|
||||
@@ -1,74 +0,0 @@
|
||||
# Outgoing allocation drag evidence
|
||||
|
||||
Contribution source: `1a669130fb6d2b65279aab3847e966a1a282c191`, based on
|
||||
published preview `715036e132677dd2bc2fb37ed316d93dc9987aec`.
|
||||
This is branch-specific Windows evidence for [issue 40](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/40).
|
||||
It is not acceptance of an integrated release or the complete application.
|
||||
The [decision and native baseline](../../drag-out-20260925.md) explain the repair.
|
||||
|
||||
## Environment and commands
|
||||
|
||||
Windows 10.0.26200, Rust/Cargo 1.98.1, host/target
|
||||
`x86_64-pc-windows-gnullvm`, LLVM 22.1.8, LLVM-MinGW 20260908 UCRT linker
|
||||
`clang.exe`, `CARGO_BUILD_JOBS=1`, `CARGO_INCREMENTAL=0`, default development
|
||||
profile. Run at the identified source with the Windows GNU LLVM toolchain and
|
||||
its linker/runtime available on PATH. A sparse checkout must include `build`
|
||||
and `harness/fixtures` as well as crates. The actual commands were:
|
||||
|
||||
```powershell
|
||||
cargo test --locked --offline -p oa-desktop allocation_drag -- --test-threads=1
|
||||
cargo clippy --locked --offline -p oa-desktop --all-targets -- -D warnings
|
||||
cargo build --locked --offline -p oa-desktop --bin arcadia-studio
|
||||
```
|
||||
|
||||
Offline commands require dependencies already cached. A fresh environment must
|
||||
fetch the locked dependencies first. Raw logs retain original absolute paths;
|
||||
these paths identify this run, not required directories on another machine.
|
||||
|
||||
## Results and boundaries
|
||||
|
||||
- Test-only successor `24b1eab07d306d8cb091bb46f7daf9f027a8f1c0` adds two
|
||||
OA/SA App regressions: a clearance proposal blocked by unrelated work and an
|
||||
outgoing move with a retained sibling enlarging the source enclosure.
|
||||
`tests-04.log` passes all 123 allocation-drag tests (zero failed/ignored,
|
||||
1,424 filtered) in 54.86s; `clippy-02.log` passes the same strict command in
|
||||
1m27s. Production inputs are unchanged from `1a669130`; its normal binary
|
||||
remains the native candidate. [Independent review](independent-review.md)
|
||||
covers the source, new test design and evidence limits.
|
||||
- `tests-03.log`: 121 passed, zero failed/ignored, 1,424 filtered; 35.33 seconds
|
||||
test execution. This is the allocation-drag subset, not the full desktop suite.
|
||||
- `clippy-01.log`: strict desktop all-target Clippy passed in 4m30s.
|
||||
- `build-01.log`: normal GUI build passed in 5m16s. The resulting local executable
|
||||
SHA256 is `9bda1a6a1d24fdc98c7a8822400c12fea9afe1de828a82ba9273f03b99e47c07`.
|
||||
It was staged with the published preview's `libunwind.dll` and started, but
|
||||
Computer Use was stopped before any changed-binary native observation.
|
||||
- `context.json`: recorded source/toolchain, passing test-log and baseline-image
|
||||
hashes, and clean source after the test run.
|
||||
- `tests-01.log`: compilation failed before tests because `build/windows_manifest.rs`
|
||||
was excluded from the initial sparse checkout.
|
||||
- `tests-02.log`: compilation failed before tests because harness fixture files
|
||||
were excluded and a standalone test helper used `Self` instead of `Fixture`.
|
||||
The compiler was terminated after reporting errors; its interrupted exit is
|
||||
preserved. The missing tracked files and typo were repaired before run 03.
|
||||
|
||||
The tests exercise source-clearance proposals, OA/SA App event sequences,
|
||||
preview/release agreement, exact source/history, cancellation, and retained
|
||||
preview without commit authority on refused release. Existing collision and
|
||||
semantic checks still govern all proposals. Independent source review found the
|
||||
whole-header viewport restriction and obsolete preview assertion; both were
|
||||
corrected before the passing run. This is source/test review, not native review.
|
||||
|
||||
`baseline-left-refusal.png` is an unmodified actual native capture from the
|
||||
published preview executable, SHA256
|
||||
`2c03bd6cc3f1110c219e4f57ab94874a121590f83779239a93289b7170664399`.
|
||||
In the copied Allocation companion fixture, OA assign view at 1440x900 content
|
||||
and Fit 72%, dragging X from window (486,546) to (365,546) leaves allocation and
|
||||
placement unchanged and reports overlap. A control drag to (625,572) removes
|
||||
allocation, and Undo restores it. Intermediate jitter was diagnosed from source;
|
||||
this image is end-state evidence only.
|
||||
|
||||
Changed-binary native acceptance is still required: repeat the short fitted-view
|
||||
drag, identify and regrab the partially clipped card, check refused-motion
|
||||
feedback, Cancel and Undo/Redo, save the full bundle, close and reopen in a fresh
|
||||
process, and verify identity/allocation/placement and unchanged sibling data for
|
||||
OA and SA. Linux/WSL remains deferred by the user. No published package changed.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 129 KiB |
@@ -1,5 +0,0 @@
|
||||
Compiling iced_widget v0.14.2 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\vendor\iced_widget)
|
||||
Compiling iced v0.14.0
|
||||
Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-core)
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 5m 16s
|
||||
@@ -1,6 +0,0 @@
|
||||
Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-core)
|
||||
Checking iced_widget v0.14.2 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\vendor\iced_widget)
|
||||
Compiling oa-server v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-server)
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Checking iced v0.14.0
|
||||
Finished `dev` profile [unoptimized] target(s) in 4m 30s
|
||||
@@ -1,2 +0,0 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 1m 27s
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"source": "1a669130fb6d2b65279aab3847e966a1a282c191",
|
||||
"platform": "Microsoft Windows 10.0.26200",
|
||||
"compiler": [
|
||||
"rustc 1.98.1 (48a229cea 2026-09-01)",
|
||||
"binary: rustc",
|
||||
"commit-hash: 48a229ceaefd4985c50990b14116b6d856af0985",
|
||||
"commit-date: 2026-09-01",
|
||||
"host: x86_64-pc-windows-gnullvm",
|
||||
"release: 1.98.1",
|
||||
"LLVM version: 22.1.8"
|
||||
],
|
||||
"cargo": "cargo 1.98.1 (797e8a9bc 2026-08-05)",
|
||||
"test_log_sha256": "8EB57F522FA0DCCA327DC13C67A9C1B14F5FAB09C5599C108C0BA6D875C78886",
|
||||
"baseline_image_sha256": "AAF162619582018D1197D255B4BA1122747AA22703D8DEDFC7FCEFCB42AEFE38",
|
||||
"target": "x86_64-pc-windows-gnullvm",
|
||||
"incremental": "0",
|
||||
"jobs": "1",
|
||||
"working_tree_clean_after_test": true
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# Independent source and evidence review
|
||||
|
||||
Fresh read-only GPT-6 Astra review, 2026-09-25. Production source
|
||||
`1a669130fb6d2b65279aab3847e966a1a282c191`; test-only successor
|
||||
`24b1eab07d306d8cb091bb46f7daf9f027a8f1c0`. No GUI actions or test execution
|
||||
were performed by this reviewer.
|
||||
|
||||
No material correctness finding in the bounded scope:
|
||||
|
||||
- The outgoing collision regression in `app/allocation_drag_tests.rs` proves a
|
||||
real source-boundary crossing and unobscured OutsidePerformers target, a
|
||||
nonempty clearance proposal, and intersection with unrelated work. Ordinary
|
||||
placement rejects that proposal. Release preserves project, source bundle,
|
||||
revision, snapshot identity and redo availability.
|
||||
- The retained-sibling regression first requires exact requested placement to
|
||||
fail. The admitted correction clears the enlarged source enclosure while
|
||||
preserving its projection, sibling allocation/placement and all other stored
|
||||
placements. Preview/release and source Undo/Redo comparisons are exact.
|
||||
- In `app/allocation_drag.rs`, clearance remains downstream of semantic
|
||||
admission and retries ordinary placement. Release recomputes placement and
|
||||
passes through `finish` and document execution. No collision, stale-state or
|
||||
retained-preview commit bypass was found.
|
||||
- The committed passing log's bytes and SHA256 match `context.json`; the
|
||||
baseline image hash also matches. Logs support 121 passing tests, strict
|
||||
Clippy and normal development build for the production source. They do not
|
||||
establish execution of the two successor tests; consult the linked raw logs
|
||||
and README for subsequent execution evidence.
|
||||
|
||||
The review supports the source policy and test design, not native usability.
|
||||
The 24x24 visible-header threshold alone does not prove readable identity or
|
||||
practical regrabbing. Changed-binary fitted-view dragging, feedback, recovery
|
||||
and Save/reopen remain unverified. This review is not the broad final
|
||||
requirements/domain/UX/release review required by `PROJECT_GOAL.md`.
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"source": "24b1eab07d306d8cb091bb46f7daf9f027a8f1c0",
|
||||
"production_source": "1a669130fb6d2b65279aab3847e966a1a282c191",
|
||||
"test_log": "tests-04.log",
|
||||
"test_log_sha256": "C98E0F70D60788B484146B3C8785891888BA696130ACC4BFD7D30DA570E6B1E1",
|
||||
"passed": 123,
|
||||
"failed": 0,
|
||||
"ignored": 0,
|
||||
"filtered": 1424,
|
||||
"native_candidate_sha256": "9BDA1A6A1D24FDC98C7A8822400C12FEA9AFE1DE828A82BA9273F03B99E47C07",
|
||||
"native_acceptance": "unverified"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-core)
|
||||
error: couldn't read `crates\oa-core\../../build/windows_manifest.rs`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-core\build.rs:1:1
|
||||
|
|
||||
1 | include!("../../build/windows_manifest.rs");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: could not compile `oa-core` (build script) due to 1 previous error
|
||||
@@ -1,68 +0,0 @@
|
||||
Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-core)
|
||||
Compiling oa-server v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-server)
|
||||
Compiling iced_widget v0.14.2 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\vendor\iced_widget)
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Compiling iced v0.14.0
|
||||
error: couldn't read `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop/../../harness/fixtures/interface-operation-probe/output/boundary-only.json`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-desktop\src\app\reference_authoring\tests\mounted.rs:755:37
|
||||
|
|
||||
755 | let mut p = persistence::decode(include_bytes!(concat!(
|
||||
| _____________________________________^
|
||||
756 | | env!("CARGO_MANIFEST_DIR"),
|
||||
757 | | "/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
758 | | )))
|
||||
| |______^
|
||||
|
||||
error: couldn't read `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop/../../harness/fixtures/interface-operation-probe/output/boundary-only.json`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-desktop\src\behavior\scenario\usage\tests.rs:235:37
|
||||
|
|
||||
235 | let mut p = persistence::decode(include_bytes!(concat!(
|
||||
| _____________________________________^
|
||||
236 | | env!("CARGO_MANIFEST_DIR"),
|
||||
237 | | "/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
238 | | )))
|
||||
| |______^
|
||||
|
||||
error: couldn't read `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop/../../harness/fixtures/interface-operation-probe/output/boundary-only.json`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-desktop\src\behavior\scenario_operation_tests.rs:6:25
|
||||
|
|
||||
6 | persistence::decode(include_bytes!(concat!(
|
||||
| _________________________^
|
||||
7 | | env!("CARGO_MANIFEST_DIR"),
|
||||
8 | | "/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
9 | | )))
|
||||
| |______^
|
||||
|
||||
error: couldn't read `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop/../../harness/fixtures/source7-native/base/expected-baseline.json`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-desktop\src\behavior\topology\contextual_routing.rs:61:29
|
||||
|
|
||||
61 | persistence::decode(include_bytes!(concat!(
|
||||
| _____________________________^
|
||||
62 | | env!("CARGO_MANIFEST_DIR"),
|
||||
63 | | "/../../harness/fixtures/source7-native/base/expected-baseline.json"
|
||||
64 | | )))
|
||||
| |__________^
|
||||
|
||||
error: couldn't read `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop/../../harness/fixtures/interface-operation-probe/output/boundary-only.json`: The system cannot find the path specified. (os error 3)
|
||||
--> crates\oa-desktop\src\semantic_review\source7_tests.rs:5:25
|
||||
|
|
||||
5 | persistence::decode(include_bytes!(concat!(
|
||||
| _________________________^
|
||||
6 | | env!("CARGO_MANIFEST_DIR"),
|
||||
7 | | "/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
8 | | )))
|
||||
| |______^
|
||||
|
||||
error[E0411]: cannot find struct, variant or union type `Self` in this scope
|
||||
--> crates\oa-desktop\src\app\allocation_drag\clearance.rs:314:9
|
||||
|
|
||||
287 | fn fixture(layer: Layer) -> Fixture {
|
||||
| ------- `Self` not allowed in a function
|
||||
...
|
||||
314 | Self {
|
||||
| ^^^^ `Self` is only available in impls, traits, and type definitions
|
||||
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio" test) due to 6 previous errors
|
||||
|
||||
Caused by:
|
||||
process didn't exit successfully: `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\rustup\toolchains\stable-x86_64-pc-windows-gnullvm\bin\rustc.exe --crate-name arcadia_studio --edition=2024 crates\oa-desktop\src\main.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --emit=dep-info,link -C embed-bitcode=no --test --check-cfg cfg(docsrs,test) --check-cfg "cfg(feature, values())" -C metadata=5e2096130f19ead2 -C extra-filename=-c5b42126ba36cd38 --out-dir C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps -C linker=clang.exe -C strip=debuginfo -L dependency=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps --extern axum=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libaxum-ccc7aea466db9258.rlib --extern iced=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libiced-594f7d417d69ed72.rlib --extern iced_runtime=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libiced_runtime-4d31a36fe5cef0b0.rlib --extern oa_core=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\liboa_core-2f3c5174cb6398fd.rlib --extern oa_server=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\liboa_server-bb42169ca297f969.rlib --extern reqwest=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libreqwest-31ac987472fa8038.rlib --extern rfd=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\librfd-17cc7ba2db223d7b.rlib --extern serde=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libserde-60a32311146a42e4.rlib --extern serde_json=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libserde_json-da2c4f333fc47818.rlib --extern tempfile=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libtempfile-86aac28d6be16d2f.rlib --extern tokio=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libtokio-5e0ca96735e3073a.rlib --extern unicode_segmentation=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\libunicode_segmentation-85698043f7780858.rlib -L native=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\build\ring-5f8d2d20440f09b4\out -L native=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows_x86_64_gnullvm-0.52.6\lib -C link-arg=C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\build\oa-desktop-307d320bbc13ccd5\out\normal-user-manifest.o` (exit code: 0xffffffff)
|
||||
@@ -1,130 +0,0 @@
|
||||
Blocking waiting for file lock on build directory
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 8m 13s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 121 tests
|
||||
test allocation_drag::outgoing_header_retains_a_grab_region_without_requiring_full_camera_containment ... ok
|
||||
test allocation_drag::tests::allocation_artifact_and_manual_trace_cascades_are_named_and_refused ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_detects_hidden_moved_child_and_stationary_enclosure_growth ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_distributed_oa_sa_exact_scope_routes_source_history_and_anchor ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_noop_stale_tamper_target_and_all_selected_headers ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_performer_closure_does_not_acquire_incidental_semantic_root ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_existing_overlap_but_refuses_its_growth ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_roles_and_does_not_invent_unplaced_descendants ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_unrelated_folded_interior_diagnostic_boundary ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_simultaneous_roots_may_enter_each_others_vacated_bounds ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_validates_all_original_members_before_normalizing ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_checks_original_identity_and_visibility_before_normalization ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_distributed_parent_refuses_but_its_independent_leaves_can_reassign ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_finish_rejects_stale_project_and_each_tampered_result ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_normalizes_parent_child_and_assigns_unplaced_leaf_once ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_requires_non_anchor_selected_header_inside_viewport ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_unresolved_oa_role_leaf_accepts_explicit_target_but_not_outside ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_accumulates_stable_ids_and_aliases_without_partial_reuse ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_collateral_surviving_constraint_refuses_whole_semantic_candidate ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_deletes_only_own_routes_across_views_and_keeps_internal_flow ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_independent_performers_preserves_already_correct_provenance ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_source_failure_preserves_existing_history_and_retry_ids ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_unfolds_only_changed_destination_and_refuses_newly_exposed_overlap ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_uses_vacated_space_and_one_complete_route_owner_map ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_zero_delta_changes_semantics_once_and_then_is_noop ... ok
|
||||
test allocation_drag::tests::cache_plan_uses_candidate_index_and_same_pixel_different_target_changes_preview ... ok
|
||||
test allocation_drag::tests::collapsed_target_unfold_is_explicit_and_no_other_fold_changes ... ok
|
||||
test allocation_drag::tests::direct_exit_revealing_another_role_performer_has_truthful_refusal ... ok
|
||||
test allocation_drag::tests::explicit_different_performer_retains_advisory_role_warning ... ok
|
||||
test allocation_drag::tests::folded_subtree_cannot_hide_new_collision ... ok
|
||||
test allocation_drag::tests::geometry_refuses_new_overlap_bounds_and_invisible_headers_and_preserves_existing_overlap ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_detects_moved_folded_child_collision ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_detects_stationary_enclosure_growth_collision ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_distributed_root_and_performer_can_translate_rigidly ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_unrelated_folded_interiors_are_outside_diagnostic_domain ... ok
|
||||
test allocation_drag::tests::malformed_scope_wrong_projection_duplicate_placement_and_stable_id_collision_refuse ... ok
|
||||
test allocation_drag::tests::mixed_p_x_y_refuses_semantic_and_arrange_instead_of_stretching_destination ... ok
|
||||
test allocation_drag::tests::oa_communication_incompleteness_remains_advisory_without_collateral_loss ... ok
|
||||
test allocation_drag::tests::oa_sa_incoming_between_outgoing_preserve_identity_source_history_and_other_views ... ok
|
||||
test allocation_drag::tests::oa_sa_one_leaf_move_refuses_when_ancestor_regrouping_introduces_unrelated_overlap ... ok
|
||||
test allocation_drag::tests::oa_sa_one_leaf_reassignment_and_outward_move_regroup_ancestors_without_moving_sibling ... ok
|
||||
test allocation_drag::tests::oa_sa_populated_collapsed_target_accepts_room_and_refuses_collision_without_relocating_contents ... ok
|
||||
test allocation_drag::tests::oa_sa_state_owned_action_and_availability_reject_reassignment_atomically ... ok
|
||||
test allocation_drag::tests::oa_sa_unfolding_prior_hidden_overlap_refuses_even_when_insertion_does_not_collide ... ok
|
||||
test allocation_drag::tests::own_deleted_allocation_routes_may_disappear_in_every_diagram_only ... ok
|
||||
test allocation_drag::tests::role_only_same_effective_performer_is_semantic_noop_and_outgoing_refuses ... ok
|
||||
test allocation_drag::tests::sa_port_and_deferred_exchange_allocation_cascade_refuses_and_retarget_is_invalid ... ok
|
||||
test allocation_drag::tests::same_performer_readability_preserves_snapped_noop_and_allocation_identity ... ok
|
||||
test allocation_drag::tests::scope_detects_first_child_allocation_and_role_transfer_and_finish_rejects_live_edits ... ok
|
||||
test allocation_drag::tests::selected_header_visibility_allows_viewport_edge_but_refuses_clipping ... ok
|
||||
test allocation_drag::tests::snapped_noop_returns_no_command_and_keeps_source_revision_and_redo ... ok
|
||||
test allocation_drag::tests::source_budget_refusal_keeps_project_bundle_history_and_retry_identity_exact ... ok
|
||||
test allocation_drag::tests::surviving_constraint_mutation_is_refused_even_when_core_candidate_is_valid ... ok
|
||||
test allocation_drag::tests::target_presentation_requires_accepted_identity_and_selected_header_visibility ... ok
|
||||
test allocation_drag::tests::toolbar_identity_allows_exact_incoming_placement_at_old_label_overlap ... ok
|
||||
test allocation_drag::tests::uniform_folded_grandchild_and_unplaced_scope_moves_exactly_q_and_all_l_oa_sa ... ok
|
||||
test allocation_drag::tests::uniform_role_only_and_mixed_direct_role_composites_same_target_do_not_allocate ... ok
|
||||
test allocation_drag::tests::uniformly_unassigned_composite_creates_only_stable_leaf_allocations_including_unplaced ... ok
|
||||
test allocation_drag::tests::unrequested_view_option_cleanup_is_not_whitelisted ... ok
|
||||
test allocation_drag::tests::unresolved_role_leaf_can_arrange_or_accept_explicit_target_without_guessing ... ok
|
||||
test app::allocation_drag::clearance::tests::corner_order_follows_boundary_crossing_and_is_deterministic ... ok
|
||||
test app::allocation_drag::clearance::tests::descendant_footprint_controls_clearance_without_mutating_projects ... ok
|
||||
test app::allocation_drag::clearance::tests::each_outgoing_edge_clears_the_source_and_passes_ordinary_placement ... ok
|
||||
test app::allocation_drag::clearance::tests::independent_selected_roots_keep_one_rigid_group_translation ... ok
|
||||
test app::allocation_drag::clearance::tests::no_crossing_no_overlap_no_source_and_nonfinite_requests_are_empty ... ok
|
||||
test app::allocation_drag::clearance::tests::source_shrink_uses_candidate_enclosure_not_frozen_target ... ok
|
||||
test app::allocation_drag::tests::actual_canvas_origin_change_cancels_held_work_before_any_legacy_release ... ok
|
||||
test app::allocation_drag::tests::arrange_is_one_gesture_and_snapped_noop_preserves_redo_and_source ... ok
|
||||
test app::allocation_drag::tests::candidate_semantics_are_cached_per_destination_with_stable_ids_and_own_index ... ok
|
||||
test app::allocation_drag::tests::destination_identity_does_not_describe_blocked_or_outside_viewport_as_unallocation ... ok
|
||||
test app::allocation_drag::tests::frozen_collapsed_target_uses_only_visible_header_and_port_edge_controls_occlude ... ok
|
||||
test app::allocation_drag::tests::frozen_targets_use_half_open_semantic_priority_and_refuse_incomparable_overlap ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_description_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_name_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_keyboard_arrange_shortcut_consumes_once_escape_cancels_and_dirty_owner_refuses ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_rendered_canvas_pixels_do_not_change_when_toolbar_feedback_text_changes ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_toolbar_hover_inspects_last_full_name_without_authorizing_an_outside_drop ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_tree_outgoing_boundary_motion_preview_release_and_cancel ... ok
|
||||
test app::allocation_drag::tests::full_tree::control_focus::dirty_selected_pin_outer_ring_keeps_real_name_focus_and_caret ... ok
|
||||
test app::allocation_drag::tests::group::allocation_bare_moves_cannot_bypass_armed_active_or_retired_lifetime ... ok
|
||||
test app::allocation_drag::tests::group::completed_text_apply_rejects_old_allocation_packets_without_retiring_a_new_drag ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_after_cancel_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_context_mismatches_preserve_the_current_session ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_current_restart_and_pair_commit_after_tool_boundary ... ok
|
||||
test app::allocation_drag::tests::group::group_arrange_work_and_performer_anchors_preserve_every_responsibility ... ok
|
||||
test app::allocation_drag::tests::group::group_assignment_split_rebuild_preserves_primary_and_commits_one_complete_candidate ... ok
|
||||
test app::allocation_drag::tests::group::group_out_and_back_assigns_at_zero_delta_then_true_noop_preserves_redo ... ok
|
||||
test app::allocation_drag::tests::group::group_selection_aba_and_old_press_cannot_retire_a_new_token ... ok
|
||||
test app::allocation_drag::tests::group::group_source_refusal_retains_primary_source_and_both_history_paths ... ok
|
||||
test app::allocation_drag::tests::group::idle_group_press_rejects_selection_generation_aba_with_unchanged_other_stamps ... ok
|
||||
test app::allocation_drag::tests::group::ordinary_off_grid_performer_out_and_back_is_noop_with_redo ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::captured_route_context_fields_independently_reject_before_retirement ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::current_route_commits_without_session_preserve_exact_route_history ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::same_context_route_attachment_after_selection_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::same_context_route_commit_after_selection_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::stale_route_attachment_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::stale_route_commit_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::unsupported_group_click_collapses_but_threshold_crossing_never_becomes_click ... ok
|
||||
test app::allocation_drag::tests::modal_roundtrip_equal_fit_undo_resize_and_focus_retire_press_before_replay ... ok
|
||||
test app::allocation_drag::tests::mounted_clean_stationary_selects_exact_work_without_revision_or_legacy_apply ... ok
|
||||
test app::allocation_drag::tests::mounted_dirty_port_press_cannot_fall_through_to_applying_port_selection ... ok
|
||||
test app::allocation_drag::tests::mounted_dirty_property_press_does_not_select_apply_or_destroy_draft ... ok
|
||||
test app::allocation_drag::tests::mounted_oa_sa_ordered_incoming_between_outgoing_are_exact_single_history_steps ... ok
|
||||
test app::allocation_drag::tests::mounted_role_only_outgoing_refusal_retains_placement_and_same_performer_creates_no_allocation ... ok
|
||||
test app::allocation_drag::tests::mounted_uniform_parent_moves_hidden_placements_and_assigns_only_leaves ... ok
|
||||
test app::allocation_drag::tests::navigation_union::old_chooser_envelopes_preserve_a_current_canvas_gesture ... ok
|
||||
test app::allocation_drag::tests::navigation_union::stale_gesture_packets_preserve_navigation_and_a_new_live_cancel_still_works ... ok
|
||||
test app::allocation_drag::tests::oa_sa_clipped_selected_header_refuses_preview_release_and_retries_without_partial_source_or_history ... ok
|
||||
test app::allocation_drag::tests::oa_sa_toolbar_identity_accepts_exact_receiver_and_collapsed_header_drag ... ok
|
||||
test app::allocation_drag::tests::outgoing_boundary_drop_clears_source_without_changing_other_placements ... ok
|
||||
test app::allocation_drag::tests::outside_release_is_cancel_and_does_not_remove_an_assignment ... ok
|
||||
test app::allocation_drag::tests::pending_behavior_draft_refuses_actual_work_press_and_equal_document_replacement_retires_packet ... ok
|
||||
test app::allocation_drag::tests::refused_motion_retains_last_scene_but_release_never_commits_it ... ok
|
||||
test app::allocation_drag::tests::release_uses_ordered_native_position_before_later_motion_in_same_batch ... ok
|
||||
test app::allocation_drag::tests::same_snapped_position_on_opposite_shared_border_rebuilds_semantics_even_if_layout_refuses ... ok
|
||||
test app::allocation_drag::tests::source_and_modal_admission_reject_prebuilt_clean_press_without_selecting ... ok
|
||||
test app::allocation_drag::tests::source_refusal_is_atomic_and_a_new_gesture_can_retry_against_live_revision ... ok
|
||||
test app::allocation_drag::tests::stale_packets_do_not_cancel_a_newer_token_and_each_release_is_consumed_once ... ok
|
||||
test app::allocation_drag::tests::toolbar_destination_names_transformed_target_whose_header_is_clipped ... ok
|
||||
test app::allocation_drag::tests::toolbar_status_leaves_transformed_top_edge_work_header_readable ... ok
|
||||
|
||||
test result: ok. 121 passed; 0 failed; 0 ignored; 0 measured; 1424 filtered out; finished in 35.33s
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 6m 05s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 123 tests
|
||||
test allocation_drag::outgoing_header_retains_a_grab_region_without_requiring_full_camera_containment ... ok
|
||||
test allocation_drag::tests::allocation_artifact_and_manual_trace_cascades_are_named_and_refused ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_detects_hidden_moved_child_and_stationary_enclosure_growth ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_distributed_oa_sa_exact_scope_routes_source_history_and_anchor ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_noop_stale_tamper_target_and_all_selected_headers ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_performer_closure_does_not_acquire_incidental_semantic_root ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_existing_overlap_but_refuses_its_growth ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_roles_and_does_not_invent_unplaced_descendants ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_preserves_unrelated_folded_interior_diagnostic_boundary ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_simultaneous_roots_may_enter_each_others_vacated_bounds ... ok
|
||||
test allocation_drag::tests::arrange_tests::explicit_arrange_validates_all_original_members_before_normalizing ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_checks_original_identity_and_visibility_before_normalization ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_distributed_parent_refuses_but_its_independent_leaves_can_reassign ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_finish_rejects_stale_project_and_each_tampered_result ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_normalizes_parent_child_and_assigns_unplaced_leaf_once ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_requires_non_anchor_selected_header_inside_viewport ... ok
|
||||
test allocation_drag::tests::assignment_boundary_tests::group_assignment_unresolved_oa_role_leaf_accepts_explicit_target_but_not_outside ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_accumulates_stable_ids_and_aliases_without_partial_reuse ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_collateral_surviving_constraint_refuses_whole_semantic_candidate ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_deletes_only_own_routes_across_views_and_keeps_internal_flow ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_independent_performers_preserves_already_correct_provenance ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_source_failure_preserves_existing_history_and_retry_ids ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_unfolds_only_changed_destination_and_refuses_newly_exposed_overlap ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_uses_vacated_space_and_one_complete_route_owner_map ... ok
|
||||
test allocation_drag::tests::assignment_tests::group_assignment_zero_delta_changes_semantics_once_and_then_is_noop ... ok
|
||||
test allocation_drag::tests::cache_plan_uses_candidate_index_and_same_pixel_different_target_changes_preview ... ok
|
||||
test allocation_drag::tests::collapsed_target_unfold_is_explicit_and_no_other_fold_changes ... ok
|
||||
test allocation_drag::tests::direct_exit_revealing_another_role_performer_has_truthful_refusal ... ok
|
||||
test allocation_drag::tests::explicit_different_performer_retains_advisory_role_warning ... ok
|
||||
test allocation_drag::tests::folded_subtree_cannot_hide_new_collision ... ok
|
||||
test allocation_drag::tests::geometry_refuses_new_overlap_bounds_and_invisible_headers_and_preserves_existing_overlap ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_detects_moved_folded_child_collision ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_detects_stationary_enclosure_growth_collision ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_distributed_root_and_performer_can_translate_rigidly ... ok
|
||||
test allocation_drag::tests::group_arrange_experiment::mixed_arrange_experiment_unrelated_folded_interiors_are_outside_diagnostic_domain ... ok
|
||||
test allocation_drag::tests::malformed_scope_wrong_projection_duplicate_placement_and_stable_id_collision_refuse ... ok
|
||||
test allocation_drag::tests::mixed_p_x_y_refuses_semantic_and_arrange_instead_of_stretching_destination ... ok
|
||||
test allocation_drag::tests::oa_communication_incompleteness_remains_advisory_without_collateral_loss ... ok
|
||||
test allocation_drag::tests::oa_sa_incoming_between_outgoing_preserve_identity_source_history_and_other_views ... ok
|
||||
test allocation_drag::tests::oa_sa_one_leaf_move_refuses_when_ancestor_regrouping_introduces_unrelated_overlap ... ok
|
||||
test allocation_drag::tests::oa_sa_one_leaf_reassignment_and_outward_move_regroup_ancestors_without_moving_sibling ... ok
|
||||
test allocation_drag::tests::oa_sa_populated_collapsed_target_accepts_room_and_refuses_collision_without_relocating_contents ... ok
|
||||
test allocation_drag::tests::oa_sa_state_owned_action_and_availability_reject_reassignment_atomically ... ok
|
||||
test allocation_drag::tests::oa_sa_unfolding_prior_hidden_overlap_refuses_even_when_insertion_does_not_collide ... ok
|
||||
test allocation_drag::tests::own_deleted_allocation_routes_may_disappear_in_every_diagram_only ... ok
|
||||
test allocation_drag::tests::role_only_same_effective_performer_is_semantic_noop_and_outgoing_refuses ... ok
|
||||
test allocation_drag::tests::sa_port_and_deferred_exchange_allocation_cascade_refuses_and_retarget_is_invalid ... ok
|
||||
test allocation_drag::tests::same_performer_readability_preserves_snapped_noop_and_allocation_identity ... ok
|
||||
test allocation_drag::tests::scope_detects_first_child_allocation_and_role_transfer_and_finish_rejects_live_edits ... ok
|
||||
test allocation_drag::tests::selected_header_visibility_allows_viewport_edge_but_refuses_clipping ... ok
|
||||
test allocation_drag::tests::snapped_noop_returns_no_command_and_keeps_source_revision_and_redo ... ok
|
||||
test allocation_drag::tests::source_budget_refusal_keeps_project_bundle_history_and_retry_identity_exact ... ok
|
||||
test allocation_drag::tests::surviving_constraint_mutation_is_refused_even_when_core_candidate_is_valid ... ok
|
||||
test allocation_drag::tests::target_presentation_requires_accepted_identity_and_selected_header_visibility ... ok
|
||||
test allocation_drag::tests::toolbar_identity_allows_exact_incoming_placement_at_old_label_overlap ... ok
|
||||
test allocation_drag::tests::uniform_folded_grandchild_and_unplaced_scope_moves_exactly_q_and_all_l_oa_sa ... ok
|
||||
test allocation_drag::tests::uniform_role_only_and_mixed_direct_role_composites_same_target_do_not_allocate ... ok
|
||||
test allocation_drag::tests::uniformly_unassigned_composite_creates_only_stable_leaf_allocations_including_unplaced ... ok
|
||||
test allocation_drag::tests::unrequested_view_option_cleanup_is_not_whitelisted ... ok
|
||||
test allocation_drag::tests::unresolved_role_leaf_can_arrange_or_accept_explicit_target_without_guessing ... ok
|
||||
test app::allocation_drag::clearance::tests::corner_order_follows_boundary_crossing_and_is_deterministic ... ok
|
||||
test app::allocation_drag::clearance::tests::descendant_footprint_controls_clearance_without_mutating_projects ... ok
|
||||
test app::allocation_drag::clearance::tests::each_outgoing_edge_clears_the_source_and_passes_ordinary_placement ... ok
|
||||
test app::allocation_drag::clearance::tests::independent_selected_roots_keep_one_rigid_group_translation ... ok
|
||||
test app::allocation_drag::clearance::tests::no_crossing_no_overlap_no_source_and_nonfinite_requests_are_empty ... ok
|
||||
test app::allocation_drag::clearance::tests::source_shrink_uses_candidate_enclosure_not_frozen_target ... ok
|
||||
test app::allocation_drag::tests::actual_canvas_origin_change_cancels_held_work_before_any_legacy_release ... ok
|
||||
test app::allocation_drag::tests::arrange_is_one_gesture_and_snapped_noop_preserves_redo_and_source ... ok
|
||||
test app::allocation_drag::tests::candidate_semantics_are_cached_per_destination_with_stable_ids_and_own_index ... ok
|
||||
test app::allocation_drag::tests::destination_identity_does_not_describe_blocked_or_outside_viewport_as_unallocation ... ok
|
||||
test app::allocation_drag::tests::frozen_collapsed_target_uses_only_visible_header_and_port_edge_controls_occlude ... ok
|
||||
test app::allocation_drag::tests::frozen_targets_use_half_open_semantic_priority_and_refuse_incomparable_overlap ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_description_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_name_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_keyboard_arrange_shortcut_consumes_once_escape_cancels_and_dirty_owner_refuses ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_rendered_canvas_pixels_do_not_change_when_toolbar_feedback_text_changes ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_toolbar_hover_inspects_last_full_name_without_authorizing_an_outside_drop ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_tree_outgoing_boundary_motion_preview_release_and_cancel ... ok
|
||||
test app::allocation_drag::tests::full_tree::control_focus::dirty_selected_pin_outer_ring_keeps_real_name_focus_and_caret ... ok
|
||||
test app::allocation_drag::tests::group::allocation_bare_moves_cannot_bypass_armed_active_or_retired_lifetime ... ok
|
||||
test app::allocation_drag::tests::group::completed_text_apply_rejects_old_allocation_packets_without_retiring_a_new_drag ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_after_cancel_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_context_mismatches_preserve_the_current_session ... ok
|
||||
test app::allocation_drag::tests::group::connection_lifetime::captured_connection_current_restart_and_pair_commit_after_tool_boundary ... ok
|
||||
test app::allocation_drag::tests::group::group_arrange_work_and_performer_anchors_preserve_every_responsibility ... ok
|
||||
test app::allocation_drag::tests::group::group_assignment_split_rebuild_preserves_primary_and_commits_one_complete_candidate ... ok
|
||||
test app::allocation_drag::tests::group::group_out_and_back_assigns_at_zero_delta_then_true_noop_preserves_redo ... ok
|
||||
test app::allocation_drag::tests::group::group_selection_aba_and_old_press_cannot_retire_a_new_token ... ok
|
||||
test app::allocation_drag::tests::group::group_source_refusal_retains_primary_source_and_both_history_paths ... ok
|
||||
test app::allocation_drag::tests::group::idle_group_press_rejects_selection_generation_aba_with_unchanged_other_stamps ... ok
|
||||
test app::allocation_drag::tests::group::ordinary_off_grid_performer_out_and_back_is_noop_with_redo ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::captured_route_context_fields_independently_reject_before_retirement ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::current_route_commits_without_session_preserve_exact_route_history ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::same_context_route_attachment_after_selection_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::same_context_route_commit_after_selection_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::stale_route_attachment_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::route_lifetime::stale_route_commit_preserves_arrange_arm_and_live_group ... ok
|
||||
test app::allocation_drag::tests::group::unsupported_group_click_collapses_but_threshold_crossing_never_becomes_click ... ok
|
||||
test app::allocation_drag::tests::modal_roundtrip_equal_fit_undo_resize_and_focus_retire_press_before_replay ... ok
|
||||
test app::allocation_drag::tests::mounted_clean_stationary_selects_exact_work_without_revision_or_legacy_apply ... ok
|
||||
test app::allocation_drag::tests::mounted_dirty_port_press_cannot_fall_through_to_applying_port_selection ... ok
|
||||
test app::allocation_drag::tests::mounted_dirty_property_press_does_not_select_apply_or_destroy_draft ... ok
|
||||
test app::allocation_drag::tests::mounted_oa_sa_ordered_incoming_between_outgoing_are_exact_single_history_steps ... ok
|
||||
test app::allocation_drag::tests::mounted_role_only_outgoing_refusal_retains_placement_and_same_performer_creates_no_allocation ... ok
|
||||
test app::allocation_drag::tests::mounted_uniform_parent_moves_hidden_placements_and_assigns_only_leaves ... ok
|
||||
test app::allocation_drag::tests::navigation_union::old_chooser_envelopes_preserve_a_current_canvas_gesture ... ok
|
||||
test app::allocation_drag::tests::navigation_union::stale_gesture_packets_preserve_navigation_and_a_new_live_cancel_still_works ... ok
|
||||
test app::allocation_drag::tests::oa_sa_clipped_selected_header_refuses_preview_release_and_retries_without_partial_source_or_history ... ok
|
||||
test app::allocation_drag::tests::oa_sa_toolbar_identity_accepts_exact_receiver_and_collapsed_header_drag ... ok
|
||||
test app::allocation_drag::tests::outgoing_boundary_drop_clears_source_without_changing_other_placements ... ok
|
||||
test app::allocation_drag::tests::outgoing_clearance_collision_refuses_release_without_source_or_history_mutation ... ok
|
||||
test app::allocation_drag::tests::outgoing_clearance_preserves_source_enclosure_and_allocated_sibling ... ok
|
||||
test app::allocation_drag::tests::outside_release_is_cancel_and_does_not_remove_an_assignment ... ok
|
||||
test app::allocation_drag::tests::pending_behavior_draft_refuses_actual_work_press_and_equal_document_replacement_retires_packet ... ok
|
||||
test app::allocation_drag::tests::refused_motion_retains_last_scene_but_release_never_commits_it ... ok
|
||||
test app::allocation_drag::tests::release_uses_ordered_native_position_before_later_motion_in_same_batch ... ok
|
||||
test app::allocation_drag::tests::same_snapped_position_on_opposite_shared_border_rebuilds_semantics_even_if_layout_refuses ... ok
|
||||
test app::allocation_drag::tests::source_and_modal_admission_reject_prebuilt_clean_press_without_selecting ... ok
|
||||
test app::allocation_drag::tests::source_refusal_is_atomic_and_a_new_gesture_can_retry_against_live_revision ... ok
|
||||
test app::allocation_drag::tests::stale_packets_do_not_cancel_a_newer_token_and_each_release_is_consumed_once ... ok
|
||||
test app::allocation_drag::tests::toolbar_destination_names_transformed_target_whose_header_is_clipped ... ok
|
||||
test app::allocation_drag::tests::toolbar_status_leaves_transformed_top_edge_work_header_readable ... ok
|
||||
|
||||
test result: ok. 123 passed; 0 failed; 0 ignored; 0 measured; 1424 filtered out; finished in 54.86s
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
*.log -text
|
||||
*.rs -text
|
||||
@@ -1,153 +0,0 @@
|
||||
# Cold construction and pan alternatives
|
||||
|
||||
Follow-up to [the stationary-camera cache](README.md), for issue 44. Application
|
||||
source remains `336720559bcec511949caa8a42bee8a55c3822da`, with evidence checkpoint
|
||||
`2ca84676ee4dc30846208881a27d90a901482085`. These standalone experiments change no
|
||||
application code, dependencies, event handling or release. Native interaction
|
||||
remains paused; no result below establishes native FPS.
|
||||
|
||||
## Question and method
|
||||
|
||||
The existing cache removes repeated construction at identical camera inputs but
|
||||
misses during pan/zoom. Can simpler geometry or renderer-level translation remove
|
||||
that cost without changing the grid's appearance? Use the same unoptimized Iced
|
||||
0.14 WGPU dependencies and Windows environment recorded in `context.json`.
|
||||
Compile the new drivers with the same `rustc`/extern/linker arguments described
|
||||
in README, replacing the driver filename and output executable. No desktop window
|
||||
is created and no input is sent. Run `cold_grid_probe` with argument `20`; phase
|
||||
drivers take no arguments.
|
||||
|
||||
The cold driver compares original circles, one reusable origin circle transformed
|
||||
for each fill, one path per column, and direct 1.15-unit squares. Every candidate
|
||||
also constructs one origin-circle template; this small common overhead is retained.
|
||||
Three warmups precede 20 changing-pan samples. Construction ends before geometry
|
||||
destruction, rendering and readback. Percentiles use sorted index `ceil((n-1)*p)`.
|
||||
Fixed candidate ordering and uncontrolled OS load limit small timing comparisons.
|
||||
|
||||
## Cold construction results
|
||||
|
||||
`cold_grid_probe.rs` produces `grid-cold-01.log`. Median milliseconds:
|
||||
|
||||
| Canvas, zoom | Original | Reused circle | Column batch | Square |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 61.0564 | 55.3975 | 54.3594 | 3.5527 |
|
||||
| 1900x660, 0.5 | 215.8919 | 162.3073 | 158.5968 | 7.5699 |
|
||||
| 1440x900, 1.0 | 60.1683 | 43.3860 | 60.8198 | 2.0340 |
|
||||
| 1440x900, 0.5 | 165.2049 | 133.3420 | 143.1489 | 6.8178 |
|
||||
|
||||
Both circle alternatives exactly match original pixels in all 12 sampled cases
|
||||
(four integer canvas/zoom pairs, three pan offsets, scale 1). This does not prove
|
||||
equivalence at fractional zoom, other DPI or other camera inputs. Squares match
|
||||
eight of those cases but differ at the third pan offset in each canvas/zoom pair:
|
||||
4,266–13,184 changed pixels, maximum channel difference 7. Their different shape
|
||||
and area are an explicit appearance change, not a pixel-preserving optimization.
|
||||
|
||||
Reusing circles and column batches leave substantial cold cost; the experiment
|
||||
does not justify adding them to production as the solution to low FPS. The square
|
||||
alternative is much faster in this scope, but is not adopted without evaluating
|
||||
its appearance and the actual interaction result. The previous all-grid single
|
||||
path experiment remains rejected for its much higher construction cost.
|
||||
|
||||
## Source explanation and independent review
|
||||
|
||||
A fresh read-only critic checked the probes and actual local Iced source. Each
|
||||
`Path::circle` allocates/builds a path and converts its arc into quadratic segments.
|
||||
Each WGPU `Frame::fill` allocates a boxed geometry builder and invokes Lyon fill
|
||||
tessellation. Same-color fills already share a solid mesh buffer: this is not
|
||||
one GPU draw call per dot. Frame translation transforms paths before tessellation,
|
||||
so path reuse alone does not reuse triangles.
|
||||
|
||||
Renderer-level `with_translation` can reuse cached triangles. Stock Canvas cannot
|
||||
attach a separate transform to each returned geometry; application adoption would
|
||||
need a small background widget or wrapper, with layout, clipping, lifecycle and
|
||||
interaction checks. Scaling the grid to handle zoom is unsuitable because the
|
||||
dot radius must remain fixed in screen units.
|
||||
|
||||
The critic also identified two important controls. Original opaque background
|
||||
geometry must not silently become screenshot-clear color: MSAA/alpha composition
|
||||
can change quantized pixels. Also, `iced_wgpu::triangle::Storage::prepare` rewrites
|
||||
cached mesh data when its transform changes. Construction/command-recording time
|
||||
therefore excludes real preparation/upload costs. The later probe measures a
|
||||
rendering/readback path separately; neither timer represents native presentation.
|
||||
|
||||
Primary sources: Iced 0.14 `iced_graphics/src/geometry/path{,/builder}.rs`,
|
||||
`iced_wgpu/src/{geometry,triangle}.rs`, `iced_core/src/renderer.rs`, and
|
||||
`iced_widget/src/canvas.rs`. The reviewer changed no files and did not execute the
|
||||
probes; execution and interpretation remain the parent's responsibility.
|
||||
|
||||
## Renderer-translated phase cache
|
||||
|
||||
`phase_grid_probe_v1.rs` / `grid-phase-01.log` cache zero-phase circles once for
|
||||
each size/spacing pair, translate by `pan.rem_euclid(step)`, and externally clip
|
||||
to the original viewport. The original nonnegative row/column indices and fixed
|
||||
screen-space radius are retained. Cache clip bounds include the radius margins.
|
||||
This first prototype uses screenshot clear color instead of opaque background
|
||||
geometry; its widespread one-level pixel differences are not accepted as parity.
|
||||
Its compiler emitted one duplicate Renderer-import warning, retained in the
|
||||
original driver. Later drivers remove that import and compile with `-D warnings`.
|
||||
|
||||
`phase_grid_opaque_probe.rs` / `grid-phase-opaque-01.log` add an oversized opaque
|
||||
background to the same cached geometry as the circles. Its bounds extend from
|
||||
`(-step,-step)` to `(width+2*step,height+2*step)`, covering the viewport at every
|
||||
positive remainder without adding extra dot rows or columns. This directly
|
||||
compares against the unchanged legacy painter. A proposed separate-background
|
||||
control was not executed; no result is claimed for it.
|
||||
|
||||
Of 75 comparisons (five size/zoom pairs, five offsets, three DPI scales), 73 are
|
||||
byte-exact. All 60 cases at integer canvas sizes and spacing 14 or 24 are exact.
|
||||
At 301.5x211.25, zoom 0.73, pan (-13.25,9.75), scale 1 changes 12 pixels by at
|
||||
most one channel level; scale 2 changes one pixel by at most six levels. These
|
||||
are failures of exact parity and are preserved, not hidden behind a tolerance.
|
||||
|
||||
The targeted `phase_grid_diagnostic.rs` / `grid-phase-diagnostic-01.log` locates
|
||||
all 12 scale-1 differences at the rightmost physical column x=301. The scale-2
|
||||
single-pixel difference does not recur. That diagnostic skips earlier large
|
||||
cases and their rendering/timing sequence: it does not exonerate the earlier
|
||||
failure or establish that every residual difference is at an edge.
|
||||
|
||||
The replay driver's first compilation failed with E0689 because the new control's
|
||||
`Vector::new(-13.25,9.75)` left its numeric type ambiguous before `rem_euclid`.
|
||||
An explicit `Vector<f32>` annotation corrects the experiment harness. No probe
|
||||
executed during that failed attempt; no application source changed.
|
||||
|
||||
`phase_grid_replay.rs` preserves the full opaque probe's rendering/timing sequence
|
||||
and adds coordinate diagnostics without extra renders until that sequence ends.
|
||||
`grid-phase-replay-01.log` reproduces both failures: the 12 right-edge pixels and
|
||||
one **interior** pixel at physical (149,263), where legacy RGBA is
|
||||
`[240,243,246,255]` and cached RGBA is `[246,248,251,255]`. The latter cannot be
|
||||
dismissed as viewport-edge rounding. Afterwards, all 12 same-renderer repeat
|
||||
controls (three repetitions of old/old and cached/cached at two scales) are
|
||||
byte-exact; the control cache builds once. This finite control does not establish
|
||||
universal determinism, but supports investigating differing geometry coverage
|
||||
instead of treating the reproduced pixel as random readback noise.
|
||||
|
||||
Cold construction plus command recording takes 68.42–173.72ms median for the four
|
||||
large viewports; cached translation takes 0.0008–0.0011ms. Each per-size/spacing
|
||||
cache builds exactly once through all pan comparisons and measurements. Zoom
|
||||
and resize through a single retained cache are not tested by these drivers.
|
||||
The labels containing `submit` mean renderer command recording, not GPU submission.
|
||||
|
||||
Two selected cases include construction, recording, backend preparation and GPU
|
||||
readback in a separate timer (two warmups, eight samples, sorted-index median):
|
||||
|
||||
| Canvas, zoom | Original p50 ms | Phase cache p50 ms |
|
||||
| --- | ---: | ---: |
|
||||
| 1900x660, 1.0 | 259.9265 | 212.6536 |
|
||||
| 1440x900, 0.5 | 337.2560 | 170.3780 |
|
||||
|
||||
These support a benefit within the headless path, but retain significant renderer
|
||||
and readback cost and variable outliers. They are not native frame times. Sample
|
||||
size, sequential ordering and uncontrolled machine load prohibit a precise
|
||||
general speedup claim. All driver/log hashes and execution mapping are in
|
||||
`cold-pan-context.json`.
|
||||
|
||||
The phase candidate removes the measured pan tessellation cost, but does not
|
||||
meet exact visual preservation. Do not adopt it as an invisible optimization.
|
||||
The small circle rewrites leave the main cost and squares change appearance.
|
||||
Further work should investigate the reproduced interior coverage difference or
|
||||
compare optimized dependency/build profiles before adding a custom widget.
|
||||
Any adopted rendering candidate must then verify a real mounted scene at a
|
||||
nonzero widget origin, foreground ordering, viewport clipping, pan/zoom/resize
|
||||
cache lifecycle and unchanged event behavior. Native interaction/FPS verification
|
||||
remains a separate required gate. No application implementation is changed by
|
||||
these experiments.
|
||||
@@ -1,180 +0,0 @@
|
||||
# Architecture background geometry cache
|
||||
|
||||
Scope: [issue 44](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/44).
|
||||
Production/test candidate: `636093aae67aa48fa1d35e403a205227e81d04de`, based on
|
||||
`c0908f101ac376afb8021e60e454e2a176e49d45`. The base includes the separately
|
||||
unaccepted outgoing-drag contribution for issue 40. This is a contribution
|
||||
experiment and repair, not an integrated release or full FPS acceptance.
|
||||
|
||||
## Question and evidence
|
||||
|
||||
The [preceding core probe](../architecture-perf-20260925/README.md) measured
|
||||
approximately 4.26ms median for preparation/projection/routing in an eight-card
|
||||
approximation. It did not reproduce the reported severe slowdown. Source review
|
||||
then identified repeated grid construction on every canvas redraw, despite the
|
||||
grid's independence from model edits, selection and pointer position.
|
||||
|
||||
The standalone headless Iced probe copies the former grid loop: background fill,
|
||||
then one radius-0.65 circle per grid point, with the same spacing, pan remainder
|
||||
and colors. It measures geometry construction independently from offscreen
|
||||
rendering/readback, using the actual compiled development-profile Iced renderer.
|
||||
It creates no desktop window and sends no input. Three warmups precede 20 samples
|
||||
per phase. The timer includes construction and returning geometry; returned
|
||||
geometry destruction occurs afterwards. Sorted index `ceil((n-1)*p)` supplies
|
||||
reported percentiles. Renderer startup is outside the timed phases.
|
||||
|
||||
`grid-performance-01.log`, produced by `grid_render_probe_v1.rs`, records:
|
||||
|
||||
| Canvas, zoom | Dots | Fresh geometry p50 ms | Cache retrieval p50 ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 2,240 | 67.8095 | 0.0001 |
|
||||
| 1900x660, 0.5 | 6,528 | 192.5581 | 0.0001 |
|
||||
| 1440x900, 1.0 | 2,318 | 66.2206 | 0.0002 |
|
||||
| 1440x900, 0.5 | 6,695 | 219.1771 | 0.0002 |
|
||||
|
||||
Cold and warm cached grid pixels equal the uncached result byte for byte in all
|
||||
four cases. Cache misses remain comparable to fresh construction; caching does
|
||||
not improve continuously changing camera inputs. Offscreen screenshot/readback
|
||||
cost remains substantial and variable. It must not be converted to native FPS.
|
||||
|
||||
## Candidate comparison and chosen change
|
||||
|
||||
1. Rebuild each circle every draw: exact current appearance, but the measured
|
||||
repeated CPU cost alone is large in this development build.
|
||||
2. Batch circles into one path: keeps the same pixels in this experiment but is
|
||||
materially slower. `grid_render_probe.rs` and `grid-performance-02.log` retain
|
||||
this rejected candidate. Its construction medians range from 274.66ms to
|
||||
1,225.53ms, versus 63.82–178.24ms for the original loop in the same run.
|
||||
3. Cache background geometry while camera and size stay identical: selected.
|
||||
It preserves the original painter and removes its repeated construction from
|
||||
stationary-camera interactions. It adds a small local cache, not a second
|
||||
geometry implementation or a model-level cache.
|
||||
|
||||
`diagram/grid.rs` owns an Iced geometry cache and an exact raw `(zoom, pan)` key.
|
||||
Iced's cache checks exact frame size. Camera changes explicitly clear it. The
|
||||
colors and dot radius remain compile-time constants; future runtime styling
|
||||
must invalidate this cache too. The conservative key rebuilds after a whole
|
||||
grid-period pan and after zoom changes within the minimum-spacing clamp. No
|
||||
fractional coordinates are rounded or tolerance-compared.
|
||||
|
||||
Drawing order becomes `[cached background and dots, live diagram, warnings]`.
|
||||
The opaque background is in the first geometry, so it cannot cover the dots.
|
||||
Existing pan/zoom transforms still apply only to the live model geometry.
|
||||
The cache lives in the retained canvas State; model revisions and selections
|
||||
do not invalidate it. Existing focus/suspension tree resets cause a safe cold
|
||||
rebuild. No routing, model semantics, hit testing or gesture admission changes.
|
||||
|
||||
`production_grid_probe.rs` imports the actual candidate cache. Its exploratory
|
||||
run 03 was performed before the source commit and formatting; the raw log is
|
||||
retained as local precommit evidence, not relabeled as a binary from the commit.
|
||||
The same source is supplied for reproduction at the identified candidate.
|
||||
|
||||
The separately recompiled run 04 imports the committed `636093aa` cache and ran
|
||||
after the full desktop test process exited, before Clippy/build compilation.
|
||||
`grid-performance-04.log` records exact baseline/cached pixels and these medians:
|
||||
|
||||
| Canvas, zoom | Fresh geometry ms | Production cache hit ms | Changing-pan rebuild ms |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| 1900x660, 1.0 | 61.8281 | 0.0002 | 59.2995 |
|
||||
| 1900x660, 0.5 | 158.8603 | 0.0002 | 163.4359 |
|
||||
| 1440x900, 1.0 | 50.5385 | 0.0002 | 53.5840 |
|
||||
| 1440x900, 0.5 | 168.4092 | 0.0002 | 175.2457 |
|
||||
|
||||
These results support the same bounded conclusion as the exploratory trials.
|
||||
The run record preserves linked-library, driver and log hashes; timing noise
|
||||
and screenshot readback remain explicit limitations.
|
||||
Run 04 directly compares the production cache's cold pixels; its generic
|
||||
cold-and-warm marker refers to the Iced-cache control. Warm production-cache
|
||||
pixel parity is established separately by the committed grid tests.
|
||||
|
||||
## Verification and boundaries
|
||||
|
||||
Three committed headless tests check:
|
||||
|
||||
- Cache construction count through 18 exact camera/size changes, including
|
||||
negative/fractional pan, zero-pan zoom changes, the minimum-spacing transition,
|
||||
independent width/height resizing and return to previous dimensions.
|
||||
- Exact RGBA equality against an independent copy of the legacy loop and
|
||||
unsplit geometry. The scenes contain opaque cards, crossing edges, text,
|
||||
translucent overdraw, selection handles and a warning overlay. Foreground
|
||||
edits must visibly change pixels while retaining the background cache.
|
||||
- The actual `DiagramCanvas` with one retained State: selection and model
|
||||
revision changes must alter rendered pixels without rebuilding the grid;
|
||||
pan and resize must rebuild it. This is drawing/scene-invalidation evidence,
|
||||
not an event-driven native gesture test.
|
||||
|
||||
Fresh read-only independent review found no concrete cache-key, lifetime,
|
||||
ordering or test-oracle defect. It checked the corresponding Iced 0.14 cache
|
||||
and mesh-layer implementations. This review does not establish execution.
|
||||
At `636093aa`, `grid-tests-01.log` passes all three targeted tests in 2.97s.
|
||||
`grid-desktop-01.log` passes the full desktop suite in 362.14s: 1,543 passed,
|
||||
zero failed, seven existing manual/diagnostic tests ignored, none filtered out.
|
||||
All 1,550 distinct test names were counted; `context.json` lists the seven
|
||||
ignored cases and records raw log hashes. This is desktop regression evidence;
|
||||
it does not relabel the ignored checks as passing or establish native FPS.
|
||||
The [independent review](independent-review.md) states its exact scope.
|
||||
|
||||
The initial strict Clippy run (`grid-clippy-01.log`) failed on two uses of
|
||||
`chunks_exact(4)` in the new test helper. Test-only successor
|
||||
`336720559bcec511949caa8a42bee8a55c3822da` uses `as_chunks::<4>()`; it leaves
|
||||
production inputs unchanged. The original full-suite result remains associated
|
||||
with `636093aa`, not relabeled as execution at the successor.
|
||||
At the successor, strict all-target Clippy passes in 2m01s
|
||||
(`grid-clippy-02.log`) and the three affected grid tests pass again in 4.13s
|
||||
(`grid-tests-02.log`). No warning suppression or test exclusion was introduced.
|
||||
|
||||
The normal Windows GUI build at `33672055` also passes in 2m15s
|
||||
(`desktop-build-02.log`). The executable and required `libunwind.dll` are staged
|
||||
locally for later native verification, but this candidate has not been launched.
|
||||
The executable SHA-256 is
|
||||
`78548e93d4b57130ed507ed0f29eddbdbafb4721b38b0cd6d06101d5324114dd`;
|
||||
`context.json` records both staged file hashes. Staging is not publication of a
|
||||
new runnable release or native acceptance.
|
||||
|
||||
Remaining limits: the user's exact saved scene is unavailable; these results do
|
||||
not establish end-to-end native FPS. Continuous pan/zoom still pays the cold
|
||||
construction cost. Native desktop verification remains paused after user Escape.
|
||||
Neither issue 40 nor issue 44 is closed by this headless experiment. Linux/WSL
|
||||
remains deferred by the user. No published release has been replaced.
|
||||
|
||||
## Environment and reproduction
|
||||
|
||||
The subsequent [cold-construction and pan experiments](COLD-PAN.md) compare
|
||||
additional approaches, including a renderer-translated cache. They preserve
|
||||
their own pixel differences, timing limits and adoption gates; they do not
|
||||
change the production implementation verified above.
|
||||
|
||||
Windows 10.0.26200, Rust 1.98.1, x86_64-pc-windows-gnullvm, development profile
|
||||
with debug information disabled. The exposed CPU is Intel Core i7-10870H with
|
||||
4 cores/threads; Windows reports VMware SVGA 3D, driver 9.17.11.4. The headless
|
||||
renderer identifies itself as `wgpu`; the probe does not identify its selected
|
||||
adapter or equate it with the desktop presenter's adapter. Other OS workloads
|
||||
were not controlled, so measured outliers are preserved.
|
||||
|
||||
At the candidate source, the normal gates are:
|
||||
|
||||
```powershell
|
||||
cargo test --locked --offline -p oa-desktop diagram::grid::tests -- --test-threads=1
|
||||
cargo test --locked --offline -p oa-desktop -- --test-threads=1
|
||||
cargo clippy --locked --offline -p oa-desktop --all-targets -- -D warnings
|
||||
cargo build --locked --offline -p oa-desktop --bin arcadia-studio
|
||||
```
|
||||
|
||||
For the standalone probes, first build the desktop dependencies in that profile.
|
||||
Compile with `rustc --edition=2024 -C opt-level=0`, supplying `--extern iced=...`
|
||||
and `--extern tokio=...` for the matching rlibs, plus `-L dependency=...` for
|
||||
their directory. On this toolchain the link also needs `-C linker=clang.exe`
|
||||
and `-L native=.../windows_x86_64_gnullvm-0.52.6/lib`. The original inputs were
|
||||
`libiced-594f7d417d69ed72.rlib` and `libtokio-5e0ca96735e3073a.rlib` from
|
||||
`target/debug/deps`. Run the resulting executable with argument `20`.
|
||||
The production probe's relative module path requires compiling it at its
|
||||
versioned location. Keep linker and runtime DLLs on PATH.
|
||||
|
||||
The first experimental compile failed because the configured futures crate
|
||||
lacked its executor feature and an inherent screenshot method shadowed the
|
||||
Headless trait method. Using the existing Tokio runtime and explicitly calling
|
||||
`Headless::screenshot` corrected those driver errors. A second link failed for
|
||||
the Windows import library search path; `grid-build-02.log` retains that failure.
|
||||
Adding the native library path allowed subsequent probes to run. These failures
|
||||
occurred in the experimental driver, before execution; they are not passing
|
||||
application checks.
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"application_source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"base_evidence": "2ca84676ee4dc30846208881a27d90a901482085",
|
||||
"production_changes": false,
|
||||
"environment": "Same unoptimized Windows Iced/Tokio artifacts as context.json",
|
||||
"logs": [
|
||||
{
|
||||
"driver": "cold_grid_probe.rs",
|
||||
"exit_code": 0,
|
||||
"log": "grid-cold-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_probe_v1.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warning": "Unused duplicate Renderer trait import; retained in original driver",
|
||||
"log": "grid-phase-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_opaque_probe.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"log": "grid-phase-opaque-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_diagnostic.rs",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"log": "grid-phase-diagnostic-01.log"
|
||||
},
|
||||
{
|
||||
"driver": "phase_grid_replay.rs",
|
||||
"log": "grid-phase-replay-01.log",
|
||||
"exit_code": 0,
|
||||
"compile_warnings_denied": true,
|
||||
"initial_compile": "E0689 ambiguous Vector scalar before rem_euclid, fixed with Vector<f32>; no execution on failed attempt"
|
||||
}
|
||||
],
|
||||
"sha256": {
|
||||
"cold_grid_probe.rs": "a4cc6543229ae4901d54061823a3b05ec6fe3b988f245df12898e013dbb93323",
|
||||
"phase_grid_probe_v1.rs": "c4293e8bb022fae5bffe67002a6f7c867549f2fa8a865b107f39070655bc57fc",
|
||||
"phase_grid_opaque_probe.rs": "43ac5be462ca31f9d1c49554291e08b60bac9de38face7138eaa850e4dcc0ab9",
|
||||
"phase_grid_diagnostic.rs": "8de3c980ac65eef4fcb32b1b121d14aca35b93c704c5b19a440ad9dc7042248c",
|
||||
"grid-cold-01.log": "ca5cac0be409bafcea2c154d42998cbd622c9e898892574bcd5a2df0c1a70886",
|
||||
"grid-phase-01.log": "e0b2e2a3e24d642e1b6c2b1b418f24c952399758159de051f7c1ba7efbb39c4a",
|
||||
"grid-phase-opaque-01.log": "2716cbeb589f4b23a48575680022231c0b94b56ba9c06ec1adbf9e3e6d07f7cd",
|
||||
"grid-phase-diagnostic-01.log": "1dbb0f745169b88c56a21683dba6797a3dbbe753e8dd742bc9ecc8ca24788886",
|
||||
"phase_grid_replay.rs": "96e62ba5d08f7e26d9705d3f90e3785151597b526293439345b59ed842de4971",
|
||||
"grid-phase-replay-01.log": "040034be1b8eb3d4fc010bbe34c5ded47a953b8295bcf2f9440507d1a149da1f"
|
||||
},
|
||||
"native_fps": "unverified"
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
//! Experimental cold-grid candidates; no window or input automation.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Frame, Geometry, Path},
|
||||
};
|
||||
use std::{hint::black_box, time::Instant};
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Candidate {
|
||||
Original,
|
||||
Template,
|
||||
Column,
|
||||
Square,
|
||||
}
|
||||
fn draw(r: &Renderer, size: Size, zoom: f32, pan: Vector, candidate: Candidate) -> Geometry {
|
||||
let mut f = Frame::new(r, size);
|
||||
f.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
let circle = Path::circle(Point::ORIGIN, 0.65);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
if matches!(candidate, Candidate::Column) {
|
||||
let p = Path::new(|p| {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
p.circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65);
|
||||
p.close();
|
||||
}
|
||||
});
|
||||
f.fill(&p, DOT);
|
||||
continue;
|
||||
}
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
let center = Point::new(ox + x as f32 * step, oy + y as f32 * step);
|
||||
match candidate {
|
||||
Candidate::Original => f.fill(&Path::circle(center, 0.65), DOT),
|
||||
Candidate::Template => f.with_save(|f| {
|
||||
f.translate(Vector::new(center.x, center.y));
|
||||
f.fill(&circle, DOT);
|
||||
}),
|
||||
Candidate::Square => f.fill_rectangle(
|
||||
center - Vector::new(0.575, 0.575),
|
||||
Size::new(1.15, 1.15),
|
||||
DOT,
|
||||
),
|
||||
Candidate::Column => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
f.into_geometry()
|
||||
}
|
||||
fn pixels(r: &mut Renderer, size: Size, g: Geometry) -> Vec<u8> {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
r.draw_geometry(g);
|
||||
Headless::screenshot(r, Size::new(size.width as u32, size.height as u32), 1., BG)
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut r = rt
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
println!(
|
||||
"renderer={} n={n}; construction only, unoptimized linked dependencies; NOT native FPS",
|
||||
r.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
] {
|
||||
println!("size={}x{} zoom={zoom}", size.width, size.height);
|
||||
for candidate in [
|
||||
Candidate::Original,
|
||||
Candidate::Template,
|
||||
Candidate::Column,
|
||||
Candidate::Square,
|
||||
] {
|
||||
for pan in [
|
||||
Vector::new(-13.25, 9.75),
|
||||
Vector::new(0., 0.),
|
||||
Vector::new(6.125, -8.5),
|
||||
] {
|
||||
let base = draw(&r, size, zoom, pan, Candidate::Original);
|
||||
let base = pixels(&mut r, size, base);
|
||||
let test = draw(&r, size, zoom, pan, candidate);
|
||||
let test = pixels(&mut r, size, test);
|
||||
let changed = base
|
||||
.chunks_exact(4)
|
||||
.zip(test.chunks_exact(4))
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = base
|
||||
.iter()
|
||||
.zip(&test)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"{candidate:?} pan={},{} changed_pixels={changed} max_channel_delta={max}",
|
||||
pan.x, pan.y
|
||||
);
|
||||
}
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..n + 3 {
|
||||
let pan = Vector::new(-13.25 + i as f32 * 0.25, 9.75);
|
||||
let now = Instant::now();
|
||||
let g = black_box(draw(&r, size, zoom, pan, candidate));
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.;
|
||||
drop(g);
|
||||
if i >= 3 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"{candidate:?} changing_pan n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
ms[((n - 1) as f64 * 0.5).ceil() as usize],
|
||||
ms[((n - 1) as f64 * 0.95).ceil() as usize],
|
||||
ms[n - 1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"source": "636093aae67aa48fa1d35e403a205227e81d04de",
|
||||
"target": "x86_64-pc-windows-gnullvm",
|
||||
"rustc": "1.98.1",
|
||||
"rust_llvm": "22.1.8",
|
||||
"linker_clang": "23.1.1",
|
||||
"profile": "dev/test unoptimized debug=0",
|
||||
"incremental": false,
|
||||
"build_jobs": 1,
|
||||
"full_test_count": 1550,
|
||||
"passed": 1543,
|
||||
"ignored": 7,
|
||||
"failed": 0,
|
||||
"ignored_tests": [
|
||||
"app::source_rename::timing_tests::accepted_release1_prepare_commit_sync_and_confirm_timing",
|
||||
"app::source_rename::timing_tests::layout_timing_tests::accepted_release1_constructor_vs_reuse_through_next_highlight",
|
||||
"app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses",
|
||||
"behavior::scenario::spacing::controller::timing_tests::saved_spacing_capture_prepare_20_lanes_1000_messages_3_views",
|
||||
"source_annotations::anchor_tests::final_multi_glyph_cluster_anchor_uses_logical_trailing_advance_edge",
|
||||
"source_annotations::anchor_tests::mixed_direction_omitted_separator_has_one_logical_predecessor_anchor",
|
||||
"source_annotations::performance_tests::annotation_projection_cost_distributions"
|
||||
],
|
||||
"log_sha256": {
|
||||
"grid-tests-01.log": "ed76ce90e9246dfddf1d3a96d6a1b91d0d0719701b1d09ddb639a1277c089612",
|
||||
"grid-desktop-01.log": "2000cf03685d6fde73c3dede90f84153a45c6ad0a5c2886a2bc09dda98ba63c3",
|
||||
"grid-performance-04.log": "c492aba0bbe600fa1c2a7f0ac52e5706d29cb67aca95fa3a31641ff7f90c2412",
|
||||
"grid-clippy-01.log": "2498a3d0be9738b8e43ae588c23d6e48f531fdbfc5f47d2f79c1a2f0f1b5bdc1",
|
||||
"grid-clippy-02.log": "69de93cce0c53f37f3c82eb28a28c1610548b30fec4680f02b9b7b118606040f",
|
||||
"grid-tests-02.log": "54c1d72402310be8b3026886dfda0339aa1a81c76b8165c40c97ea075524bff2",
|
||||
"desktop-build-02.log": "e39d1eaaceb8a5805a486c204d6eca7e1ce2b9b39b232e770ab0ad33cbe0e860"
|
||||
},
|
||||
"native_acceptance": "unverified",
|
||||
"source_clean_after_full_test": true,
|
||||
"timing_run": {
|
||||
"source": "636093aae67aa48fa1d35e403a205227e81d04de",
|
||||
"samples": 20,
|
||||
"warmups": 3,
|
||||
"probe_exe_sha256": "79c2e0b64cfacf24b7ee10d8690969d961753945f45d16a743ea750b9eb049d0",
|
||||
"probe_source_sha256": "e6efe1e510164444912d05a96e49c55d791b7046102013458a5ad94bd474a6cd",
|
||||
"grid_git_blob_sha256": "5e7c54d1b4da2e2c082f8ef011a3755f8808fcd67f1babf3be5448cd94240e6c",
|
||||
"iced_rlib_sha256": "9fec4bea1a5ac7e1ae75d786b76e3dbe97a6f63b30d11b6753f268c2ad9efbc6",
|
||||
"tokio_rlib_sha256": "78587ae94762065914b25065c85d9cb66106b71619ec7b65f5e16cdeb689abb9"
|
||||
},
|
||||
"test_only_successor": {
|
||||
"source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"change": "chunks_exact(4) replaced by as_chunks::<4>() in the pixel test helper",
|
||||
"targeted_passed": 3,
|
||||
"targeted_failed": 0,
|
||||
"targeted_filtered": 1547,
|
||||
"strict_clippy": "passed",
|
||||
"normal_build": "passed"
|
||||
},
|
||||
"staged_candidate": {
|
||||
"source": "336720559bcec511949caa8a42bee8a55c3822da",
|
||||
"launched": false,
|
||||
"files_sha256": {
|
||||
"arcadia-studio.exe": "78548e93d4b57130ed507ed0f29eddbdbafb4721b38b0cd6d06101d5324114dd",
|
||||
"libunwind.dll": "aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 2m 15s
|
||||
File diff suppressed because one or more lines are too long
@@ -1,25 +0,0 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
error: using `chunks_exact` with a constant chunk size
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:231:10
|
||||
|
|
||||
231 | .chunks_exact(4)
|
||||
| ^^^^^^^^^^^^^^^ help: consider using `as_chunks` instead: `as_chunks::<4>().0.iter()`
|
||||
|
|
||||
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
|
||||
= note: `-D clippy::chunks-exact-to-as-chunks` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::chunks_exact_to_as_chunks)]`
|
||||
|
||||
error: using `chunks_exact` with a constant chunk size
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:232:21
|
||||
|
|
||||
232 | .zip(actual.chunks_exact(4))
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: consider using `as_chunks::<4>()` instead
|
||||
--> crates\oa-desktop\src\diagram\grid\tests.rs:232:21
|
||||
|
|
||||
232 | .zip(actual.chunks_exact(4))
|
||||
| ^^^^^^^^^^^^^^^
|
||||
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#chunks_exact_to_as_chunks
|
||||
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio" test) due to 2 previous errors
|
||||
@@ -1,2 +0,0 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 2m 01s
|
||||
@@ -1,69 +0,0 @@
|
||||
renderer=wgpu n=20; construction only, unoptimized linked dependencies; NOT native FPS
|
||||
size=1900x660 zoom=1
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=61.0564 p95_ms=111.2137 max_ms=111.2137
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=55.3975 p95_ms=73.8790 max_ms=73.8790
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=54.3594 p95_ms=69.6607 max_ms=69.6607
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=4266 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=3.5527 p95_ms=5.6911 max_ms=5.6911
|
||||
size=1900x660 zoom=0.5
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=215.8919 p95_ms=253.9666 max_ms=253.9666
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=162.3073 p95_ms=212.2833 max_ms=212.2833
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=158.5968 p95_ms=449.6066 max_ms=449.6066
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=12784 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=7.5699 p95_ms=13.3416 max_ms=13.3416
|
||||
size=1440x900 zoom=1
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=60.1683 p95_ms=88.4967 max_ms=88.4967
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=43.3860 p95_ms=53.1866 max_ms=53.1866
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=60.8198 p95_ms=80.8932 max_ms=80.8932
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=4440 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=2.0340 p95_ms=4.0620 max_ms=4.0620
|
||||
size=1440x900 zoom=0.5
|
||||
Original pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Original pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Original changing_pan n=20 p50_ms=165.2049 p95_ms=203.5331 max_ms=203.5331
|
||||
Template pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Template pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Template changing_pan n=20 p50_ms=133.3420 p95_ms=143.9790 max_ms=143.9790
|
||||
Column pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Column pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
Column changing_pan n=20 p50_ms=143.1489 p95_ms=155.3680 max_ms=155.3680
|
||||
Square pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
Square pan=6.125,-8.5 changed_pixels=13184 max_channel_delta=7
|
||||
Square changing_pan n=20 p50_ms=6.8178 p95_ms=10.8775 max_ms=10.8775
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=67.8095 p95_ms=77.2227 max_ms=77.2227
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=171.4916 p95_ms=231.2029 max_ms=231.2029
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=99.1916 p95_ms=125.0517 max_ms=125.0517
|
||||
cache_forced_miss_geometry: n=20 p50_ms=65.6279 p95_ms=75.6531 max_ms=75.6531
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=192.5581 p95_ms=237.9956 max_ms=237.9956
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0009 max_ms=0.0009
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=328.0257 p95_ms=498.0908 max_ms=498.0908
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=108.5102 p95_ms=119.6021 max_ms=119.6021
|
||||
cache_forced_miss_geometry: n=20 p50_ms=183.2740 p95_ms=212.6681 max_ms=212.6681
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=66.2206 p95_ms=89.5746 max_ms=89.5746
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0010 max_ms=0.0010
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=194.1484 p95_ms=445.2525 max_ms=445.2525
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=113.8430 p95_ms=152.8658 max_ms=152.8658
|
||||
cache_forced_miss_geometry: n=20 p50_ms=81.6705 p95_ms=200.7735 max_ms=200.7735
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=219.1771 p95_ms=270.8434 max_ms=270.8434
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=387.7051 p95_ms=600.4906 max_ms=600.4906
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=119.9417 p95_ms=157.2978 max_ms=157.2978
|
||||
cache_forced_miss_geometry: n=20 p50_ms=193.8187 p95_ms=222.6229 max_ms=222.6229
|
||||
@@ -1,41 +0,0 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=63.8161 p95_ms=74.3558 max_ms=74.3558
|
||||
batched_geometry: n=20 p50_ms=280.9291 p95_ms=361.9218 max_ms=361.9218
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0004 max_ms=0.0004
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=203.0484 p95_ms=314.9968 max_ms=314.9968
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=109.2162 p95_ms=470.8047 max_ms=470.8047
|
||||
cache_forced_miss_geometry: n=20 p50_ms=73.1544 p95_ms=112.9079 max_ms=112.9079
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=178.2363 p95_ms=204.1913 max_ms=204.1913
|
||||
batched_geometry: n=20 p50_ms=1225.5328 p95_ms=1791.5758 max_ms=1791.5758
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0008 max_ms=0.0008
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=281.4035 p95_ms=308.5933 max_ms=308.5933
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=100.8220 p95_ms=114.5026 max_ms=114.5026
|
||||
cache_forced_miss_geometry: n=20 p50_ms=179.6717 p95_ms=319.2162 max_ms=319.2162
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=67.1604 p95_ms=76.3298 max_ms=76.3298
|
||||
batched_geometry: n=20 p50_ms=274.6607 p95_ms=551.4707 max_ms=551.4707
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=168.4817 p95_ms=294.9456 max_ms=294.9456
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=92.0521 p95_ms=105.3303 max_ms=105.3303
|
||||
cache_forced_miss_geometry: n=20 p50_ms=70.1733 p95_ms=83.6053 max_ms=83.6053
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=177.7526 p95_ms=205.8862 max_ms=205.8862
|
||||
batched_geometry: n=20 p50_ms=1012.6393 p95_ms=1103.8729 max_ms=1103.8729
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0006 max_ms=0.0006
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=293.3138 p95_ms=315.0118 max_ms=315.0118
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=110.6309 p95_ms=127.6823 max_ms=127.6823
|
||||
cache_forced_miss_geometry: n=20 p50_ms=195.3297 p95_ms=327.8771 max_ms=327.8771
|
||||
@@ -1,45 +0,0 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=70.2238 p95_ms=101.8816 max_ms=101.8816
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0005 max_ms=0.0005
|
||||
production_changing_pan_geometry: n=20 p50_ms=74.2265 p95_ms=150.4436 max_ms=150.4436
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=179.3216 p95_ms=272.4518 max_ms=272.4518
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=108.8949 p95_ms=138.2430 max_ms=138.2430
|
||||
cache_forced_miss_geometry: n=20 p50_ms=73.2982 p95_ms=229.4395 max_ms=229.4395
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=190.5914 p95_ms=245.6332 max_ms=245.6332
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0004 max_ms=0.0004
|
||||
production_changing_pan_geometry: n=20 p50_ms=200.1888 p95_ms=243.7068 max_ms=243.7068
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=356.6151 p95_ms=438.3689 max_ms=438.3689
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=151.2161 p95_ms=236.6296 max_ms=236.6296
|
||||
cache_forced_miss_geometry: n=20 p50_ms=198.4648 p95_ms=312.0322 max_ms=312.0322
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=83.2017 p95_ms=284.6453 max_ms=284.6453
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0010 max_ms=0.0010
|
||||
production_changing_pan_geometry: n=20 p50_ms=61.6783 p95_ms=79.8293 max_ms=79.8293
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=168.7804 p95_ms=206.0726 max_ms=206.0726
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=114.7579 p95_ms=130.0840 max_ms=130.0840
|
||||
cache_forced_miss_geometry: n=20 p50_ms=103.6019 p95_ms=215.1691 max_ms=215.1691
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=202.9283 p95_ms=247.2353 max_ms=247.2353
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0004 max_ms=0.0004
|
||||
production_changing_pan_geometry: n=20 p50_ms=223.5437 p95_ms=243.7359 max_ms=243.7359
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=299.4320 p95_ms=359.2924 max_ms=359.2924
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=113.6960 p95_ms=180.7261 max_ms=180.7261
|
||||
cache_forced_miss_geometry: n=20 p50_ms=191.9370 p95_ms=294.2203 max_ms=294.2203
|
||||
@@ -1,45 +0,0 @@
|
||||
renderer=wgpu samples=20; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation
|
||||
|
||||
size=1900x660 zoom=1 dots=2240
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=61.8281 p95_ms=68.9849 max_ms=68.9849
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=59.2995 p95_ms=69.6670 max_ms=69.6670
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0029 max_ms=0.0029
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=136.3939 p95_ms=160.0453 max_ms=160.0453
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=82.0448 p95_ms=105.5853 max_ms=105.5853
|
||||
cache_forced_miss_geometry: n=20 p50_ms=52.1529 p95_ms=58.0832 max_ms=58.0832
|
||||
|
||||
size=1900x660 zoom=0.5 dots=6528
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=158.8603 p95_ms=169.1878 max_ms=169.1878
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=163.4359 p95_ms=262.5349 max_ms=262.5349
|
||||
cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=226.3661 p95_ms=247.0635 max_ms=247.0635
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=78.5761 p95_ms=99.7537 max_ms=99.7537
|
||||
cache_forced_miss_geometry: n=20 p50_ms=154.7299 p95_ms=177.8883 max_ms=177.8883
|
||||
|
||||
size=1440x900 zoom=1 dots=2318
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=50.5385 p95_ms=57.4277 max_ms=57.4277
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0002 max_ms=0.0002
|
||||
production_changing_pan_geometry: n=20 p50_ms=53.5840 p95_ms=60.8569 max_ms=60.8569
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0005 max_ms=0.0005
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=124.3827 p95_ms=140.3617 max_ms=140.3617
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=75.7445 p95_ms=97.7246 max_ms=97.7246
|
||||
cache_forced_miss_geometry: n=20 p50_ms=54.7114 p95_ms=61.8326 max_ms=61.8326
|
||||
|
||||
size=1440x900 zoom=0.5 dots=6695
|
||||
batched_rgba_byte_differences=0
|
||||
pixel_parity=cold_and_warm_exact
|
||||
fresh_geometry: n=20 p50_ms=168.4092 p95_ms=186.4184 max_ms=186.4184
|
||||
production_cached_geometry: n=20 p50_ms=0.0002 p95_ms=0.0003 max_ms=0.0003
|
||||
production_changing_pan_geometry: n=20 p50_ms=175.2457 p95_ms=205.4313 max_ms=205.4313
|
||||
cached_geometry: n=20 p50_ms=0.0001 p95_ms=0.0002 max_ms=0.0002
|
||||
fresh_geometry_and_screenshot: n=20 p50_ms=253.4003 p95_ms=420.1746 max_ms=420.1746
|
||||
cached_geometry_and_screenshot: n=20 p50_ms=85.1261 p95_ms=107.0600 max_ms=107.0600
|
||||
cache_forced_miss_geometry: n=20 p50_ms=155.4512 p95_ms=163.8325 max_ms=163.8325
|
||||
@@ -1,96 +0,0 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=2212 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=2133 max_channel_delta=1
|
||||
scale=1 pan=23.875,23.75 changed_pixels=2133 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=4424 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=8745 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=2212 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=4266 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=6557 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=8848 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=4266 max_channel_delta=1
|
||||
scale=2 pan=23.875,23.75 changed_pixels=10665 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=6478 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=66.6997 p95_ms=77.3888 max_ms=77.3888
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0017 max_ms=0.0017
|
||||
cache_builds=1
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=6392 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=6392 max_channel_delta=1
|
||||
scale=1 pan=13.875,13.75 changed_pixels=6345 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=12960 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=12781 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=9588 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=11288 max_channel_delta=1
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=9494 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=12801 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=25568 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=12784 max_channel_delta=1
|
||||
scale=2 pan=13.875,13.75 changed_pixels=31725 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=19170 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=189.4570 p95_ms=221.8240 max_ms=221.8240
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=2280 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=2220 max_channel_delta=1
|
||||
scale=1 pan=23.875,23.75 changed_pixels=2220 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=4522 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=9000 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=2280 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=4403 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=6742 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=9120 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=4440 max_channel_delta=1
|
||||
scale=2 pan=23.875,23.75 changed_pixels=11026 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=6683 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=66.8644 p95_ms=93.9216 max_ms=93.9216
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=6592 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=6592 max_channel_delta=1
|
||||
scale=1 pan=13.875,13.75 changed_pixels=6528 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=13260 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=13306 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=9952 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=11488 max_channel_delta=1
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=9792 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=13209 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=26368 max_channel_delta=1
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=13184 max_channel_delta=1
|
||||
scale=2 pan=13.875,13.75 changed_pixels=32640 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=19686 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=202.5700 p95_ms=482.7433 max_ms=482.7433
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0041 max_ms=0.0041
|
||||
cache_builds=1
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=263 max_channel_delta=1
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=251 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=250 max_channel_delta=1
|
||||
scale=1 pan=17.395,17.27 changed_pixels=238 max_channel_delta=1
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=241 max_channel_delta=1
|
||||
scale=1.25 pan=0,0 changed_pixels=379 max_channel_delta=1
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=395 max_channel_delta=1
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=332 max_channel_delta=1
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=289 max_channel_delta=1
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=372 max_channel_delta=1
|
||||
scale=2 pan=0,0 changed_pixels=759 max_channel_delta=1
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=626 max_channel_delta=6
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=731 max_channel_delta=1
|
||||
scale=2 pan=17.395,17.27 changed_pixels=576 max_channel_delta=1
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=784 max_channel_delta=1
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=6.6260 p95_ms=21.1743 max_ms=21.1743
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0012 max_ms=0.0012
|
||||
cache_builds=1
|
||||
@@ -1,32 +0,0 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
difference_xy=301,26 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,27 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,62 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,80 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,97 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,115 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,132 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,150 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,167 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,184 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,185 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,202 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=6.1755 p95_ms=8.4025 max_ms=8.4025
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0019 max_ms=0.0019
|
||||
cache_builds=1
|
||||
@@ -1,100 +0,0 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=72.0271 p95_ms=99.9725 max_ms=99.9725
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0011 p95_ms=0.0015 max_ms=0.0015
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=259.9265 min_ms=253.3506 max_ms=281.4865
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=212.6536 min_ms=184.5191 max_ms=299.2711
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=167.4776 p95_ms=186.8968 max_ms=186.8968
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=68.4232 p95_ms=97.6147 max_ms=97.6147
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0458 max_ms=0.0458
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=173.7177 p95_ms=215.3188 max_ms=215.3188
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0009 max_ms=0.0009
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=337.2560 min_ms=319.0543 max_ms=366.4956
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=170.3780 min_ms=144.1992 max_ms=188.4337
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=1 max_channel_delta=6
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=5.4797 p95_ms=6.8957 max_ms=6.8957
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
@@ -1,127 +0,0 @@
|
||||
renderer=wgpu; phase-cache prototype; NOT native FPS
|
||||
size=1900x660 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=68.3424 p95_ms=88.0883 max_ms=88.0883
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0010 p95_ms=0.0012 max_ms=0.0012
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=249.7269 min_ms=216.9378 max_ms=267.1425
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=215.6865 min_ms=177.7329 max_ms=262.5719
|
||||
size=1900x660 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=190.0141 p95_ms=231.4193 max_ms=231.4193
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=1 step=24
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=23.875,23.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=72.2449 p95_ms=86.5021 max_ms=86.5021
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
size=1440x900 zoom=0.5 step=14
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=13.875,13.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=209.4728 p95_ms=329.0977 max_ms=329.0977
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0008 p95_ms=0.0009 max_ms=0.0009
|
||||
cache_builds=1
|
||||
cached=false construct_submit_screenshot changing_pan n=8 p50_ms=384.7680 min_ms=347.4879 max_ms=420.2894
|
||||
cached=true construct_submit_screenshot changing_pan n=8 p50_ms=164.1611 min_ms=150.1276 max_ms=186.4567
|
||||
size=301.5x211.25 zoom=0.73 step=17.52
|
||||
scale=1 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-13.25,9.75 changed_pixels=12 max_channel_delta=1
|
||||
difference_xy=301,26 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,27 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,62 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,80 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,97 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,115 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,132 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,150 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,167 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,184 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,185 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
difference_xy=301,202 expected=[240, 243, 247, 255] actual=[240, 243, 246, 255]
|
||||
scale=1 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=1.25 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=0,0 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-13.25,9.75 changed_pixels=1 max_channel_delta=6
|
||||
difference_xy=149,263 expected=[240, 243, 246, 255] actual=[246, 248, 251, 255]
|
||||
scale=2 pan=6.125,-8.5 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=17.395,17.27 changed_pixels=0 max_channel_delta=0
|
||||
scale=2 pan=-0.125,0.125 changed_pixels=0 max_channel_delta=0
|
||||
cached=false construct_and_submit changing_pan n=20 p50_ms=5.5680 p95_ms=7.0634 max_ms=7.0634
|
||||
cached=true construct_and_submit changing_pan n=20 p50_ms=0.0007 p95_ms=0.0008 max_ms=0.0008
|
||||
cache_builds=1
|
||||
original_sequence_complete; beginning same-renderer repeat controls
|
||||
comparison=old/old repetition=1 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=1 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=1 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=1 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=2 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=2 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=2 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=2 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=3 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=3 size=301.5x211.25 scale=1 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=old/old repetition=3 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
comparison=cached/cached repetition=3 size=301.5x211.25 scale=2 pan=-13.25,9.75 changed_pixels=0 max_channel_delta=0
|
||||
control_cache_builds=1
|
||||
@@ -1,11 +0,0 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 4m 23s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 3 tests
|
||||
test diagram::grid::tests::actual_canvas_keeps_background_cached_while_selection_and_revision_change ... ok
|
||||
test diagram::grid::tests::cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change ... ok
|
||||
test diagram::grid::tests::cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes ... ok
|
||||
|
||||
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 1547 filtered out; finished in 2.97s
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\drag-out-01a0d6fd\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 3m 31s
|
||||
Running unittests src\main.rs (C:/Users/User/Documents/Git/ARCADIA-Studio/target\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 3 tests
|
||||
test diagram::grid::tests::actual_canvas_keeps_background_cached_while_selection_and_revision_change ... ok
|
||||
test diagram::grid::tests::cache_reuses_unchanged_inputs_and_rebuilds_for_each_camera_or_size_change ... ok
|
||||
test diagram::grid::tests::cached_background_preserves_pixels_and_live_foreground_across_camera_and_size_changes ... ok
|
||||
|
||||
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 1547 filtered out; finished in 4.13s
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn batched(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
frame.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
let path = Path::new(|p| {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
p.circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65);
|
||||
p.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.fill(&path, DOT);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = batched(&renderer, size, zoom, pan);
|
||||
let pixels = screenshot(&mut renderer, size, geometry);
|
||||
let differences = baseline_pixels
|
||||
.iter()
|
||||
.zip(&pixels)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
println!("batched_rgba_byte_differences={differences}");
|
||||
assert_eq!(
|
||||
differences, 0,
|
||||
"batched path must preserve exact grid pixels"
|
||||
);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
measure("batched_geometry", n, || {
|
||||
batched(&renderer, size, zoom, pan)
|
||||
});
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
# Independent cache and evaluator review
|
||||
|
||||
Read-only GPT-6 Astra review of
|
||||
`636093aae67aa48fa1d35e403a205227e81d04de`, 2026-09-25. The reviewer did not
|
||||
edit files, compile, launch a desktop window or control the user interface.
|
||||
|
||||
No concrete cache-key, lifetime or drawing-order defect was found. The review
|
||||
checked Iced 0.14 source as well as the application: Iced compares exact cache
|
||||
size, explicit camera changes clear geometry, and context/gesture retirement
|
||||
retains the model-independent grid. Normal canvas state survives view rebuilds;
|
||||
the focus wrapper's suspension reset causes a safe cold miss. Cached and live
|
||||
mesh submissions retain their invocation order. Background geometry has no
|
||||
text/images, and existing model transforms still apply to the live scene.
|
||||
|
||||
The evaluator uses an independent copy of the original grid painter, rather
|
||||
than calling the candidate's painter to produce its expected image. It compares
|
||||
the former combined frame with the split cached background, including opaque
|
||||
and translucent foreground, text, overlay, fractional inputs and live edits.
|
||||
The real DiagramCanvas check demonstrates changing selection/model revision
|
||||
pixels while the grid construction count remains one, then checks pan/resize
|
||||
invalidation. It exercises retained-state drawing and scene invalidation;
|
||||
event-driven native gesture acceptance is outside that test.
|
||||
|
||||
The reviewer inspected `grid-tests-01.log`: all three tests pass, none fail or
|
||||
are ignored, 1,547 are filtered, and execution takes 2.97s. Source identity is
|
||||
provided by the accompanying run record and clean tracked implementation, not
|
||||
by an embedded hash in the Cargo log. Full-suite/build/Clippy results published
|
||||
later remain separate evidence and must be read in their own logs.
|
||||
|
||||
The measurements support avoiding repeated grid construction for an unchanged
|
||||
camera. They do not establish native FPS, performance on another renderer, or
|
||||
improvement during continuously changing pan/zoom. No broad final application
|
||||
or release acceptance is claimed by this review.
|
||||
|
||||
A second read-only audit checked the full desktop log's 1,550 distinct names,
|
||||
1,543 passes and seven ignored cases against `context.json`. It verified all
|
||||
three recorded log hashes, driver-source hash, committed grid blob, linked Iced
|
||||
and Tokio libraries, timing tables, sample method and rejected-batch ranges.
|
||||
It did not independently verify the probe executable, execution chronology,
|
||||
Clippy/build gates or environment assertions. It clarified that run 04's generic
|
||||
cold-and-warm marker is not a production-cache warm-pixel check; that assertion
|
||||
belongs to the separate committed tests.
|
||||
@@ -1,209 +0,0 @@
|
||||
//! Renderer-level phase translation experiment, not application code.
|
||||
use iced::advanced::{Renderer as _, graphics::geometry::Renderer as _, renderer::Headless};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
fn dots(f: &mut Frame, size: Size, step: f32, phase: Vector) {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(
|
||||
Point::new(phase.x + x as f32 * step, phase.y + y as f32 * step),
|
||||
0.65,
|
||||
),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn old(r: &Renderer, size: Size, step: f32, phase: Vector) -> Geometry {
|
||||
let mut f = Frame::new(r, size);
|
||||
f.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
dots(&mut f, size, step, phase);
|
||||
f.into_geometry()
|
||||
}
|
||||
fn phase(r: &Renderer, cache: &Cache, size: Size, step: f32, builds: &Cell<usize>) -> Geometry {
|
||||
cache.draw_with_bounds(
|
||||
r,
|
||||
Rectangle {
|
||||
x: -step,
|
||||
y: -step,
|
||||
width: size.width + 3. * step,
|
||||
height: size.height + 3. * step,
|
||||
},
|
||||
|f| {
|
||||
builds.set(builds.get() + 1);
|
||||
f.fill_rectangle(
|
||||
Point::new(-step, -step),
|
||||
Size::new(size.width + 3. * step, size.height + 3. * step),
|
||||
BG,
|
||||
);
|
||||
dots(f, size, step, Vector::ZERO);
|
||||
},
|
||||
)
|
||||
}
|
||||
fn submit(r: &mut Renderer, size: Size, g: Geometry, pan: Vector) {
|
||||
r.with_layer(Rectangle::with_size(size), |r| {
|
||||
r.with_translation(pan, |r| r.draw_geometry(g))
|
||||
});
|
||||
}
|
||||
fn shot(r: &mut Renderer, size: Size, scale: f32) -> Vec<u8> {
|
||||
Headless::screenshot(
|
||||
r,
|
||||
Size::new(
|
||||
(size.width * scale).ceil() as u32,
|
||||
(size.height * scale).ceil() as u32,
|
||||
),
|
||||
scale,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut r = rt
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
println!(
|
||||
"renderer={}; phase-cache prototype; NOT native FPS",
|
||||
r.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
(Size::new(301.5, 211.25), 0.73),
|
||||
] {
|
||||
if size.width != 301.5 {
|
||||
continue;
|
||||
}
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
let cache = Cache::new();
|
||||
let builds = Cell::new(0);
|
||||
println!(
|
||||
"size={}x{} zoom={zoom} step={step}",
|
||||
size.width, size.height
|
||||
);
|
||||
for scale in [1., 1.25, 2.] {
|
||||
for pan in [
|
||||
Vector::ZERO,
|
||||
Vector::new(-13.25, 9.75),
|
||||
Vector::new(6.125, -8.5),
|
||||
Vector::new(step - 0.125, step - 0.25),
|
||||
Vector::new(-0.125, 0.125),
|
||||
] {
|
||||
let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step));
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let expected = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let actual = shot(&mut r, size, scale);
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
for (index, (a, b)) in expected
|
||||
.chunks_exact(4)
|
||||
.zip(actual.chunks_exact(4))
|
||||
.enumerate()
|
||||
.filter(|(_, (a, b))| a != b)
|
||||
.take(20)
|
||||
{
|
||||
println!(
|
||||
"difference_xy={},{} expected={a:?} actual={b:?}",
|
||||
index % (size.width * scale).ceil() as usize,
|
||||
index / (size.width * scale).ceil() as usize
|
||||
);
|
||||
}
|
||||
let changed = expected
|
||||
.chunks_exact(4)
|
||||
.zip(actual.chunks_exact(4))
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = expected
|
||||
.iter()
|
||||
.zip(&actual)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"scale={scale} pan={},{} changed_pixels={changed} max_channel_delta={max}",
|
||||
pan.x, pan.y
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(builds.get(), 1);
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..23 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset)
|
||||
} else {
|
||||
r.draw_geometry(g)
|
||||
};
|
||||
let elapsed = black_box(now.elapsed().as_secs_f64() * 1000.);
|
||||
if i >= 3 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_and_submit changing_pan n=20 p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
ms[10], ms[19], ms[19]
|
||||
);
|
||||
}
|
||||
println!("cache_builds={}", builds.get());
|
||||
assert_eq!(builds.get(), 1);
|
||||
// Includes backend prepare/upload and readback; still not native presentation.
|
||||
if size.width == 1900. && zoom == 1. || size.width == 1440. && zoom == 0.5 {
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..10 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset);
|
||||
} else {
|
||||
r.draw_geometry(g);
|
||||
}
|
||||
let pixels = black_box(shot(&mut r, size, 1.));
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.;
|
||||
drop(pixels);
|
||||
if i >= 2 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_submit_screenshot changing_pan n=8 p50_ms={:.4} min_ms={:.4} max_ms={:.4}",
|
||||
ms[4], ms[0], ms[7]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
//! Renderer-level phase translation experiment, not application code.
|
||||
use iced::advanced::{Renderer as _, graphics::geometry::Renderer as _, renderer::Headless};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
fn dots(f: &mut Frame, size: Size, step: f32, phase: Vector) {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(
|
||||
Point::new(phase.x + x as f32 * step, phase.y + y as f32 * step),
|
||||
0.65,
|
||||
),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn old(r: &Renderer, size: Size, step: f32, phase: Vector) -> Geometry {
|
||||
let mut f = Frame::new(r, size);
|
||||
f.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
dots(&mut f, size, step, phase);
|
||||
f.into_geometry()
|
||||
}
|
||||
fn phase(r: &Renderer, cache: &Cache, size: Size, step: f32, builds: &Cell<usize>) -> Geometry {
|
||||
cache.draw_with_bounds(
|
||||
r,
|
||||
Rectangle {
|
||||
x: -step,
|
||||
y: -step,
|
||||
width: size.width + 3. * step,
|
||||
height: size.height + 3. * step,
|
||||
},
|
||||
|f| {
|
||||
builds.set(builds.get() + 1);
|
||||
f.fill_rectangle(
|
||||
Point::new(-step, -step),
|
||||
Size::new(size.width + 3. * step, size.height + 3. * step),
|
||||
BG,
|
||||
);
|
||||
dots(f, size, step, Vector::ZERO);
|
||||
},
|
||||
)
|
||||
}
|
||||
fn submit(r: &mut Renderer, size: Size, g: Geometry, pan: Vector) {
|
||||
r.with_layer(Rectangle::with_size(size), |r| {
|
||||
r.with_translation(pan, |r| r.draw_geometry(g))
|
||||
});
|
||||
}
|
||||
fn shot(r: &mut Renderer, size: Size, scale: f32) -> Vec<u8> {
|
||||
Headless::screenshot(
|
||||
r,
|
||||
Size::new(
|
||||
(size.width * scale).ceil() as u32,
|
||||
(size.height * scale).ceil() as u32,
|
||||
),
|
||||
scale,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut r = rt
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
println!(
|
||||
"renderer={}; phase-cache prototype; NOT native FPS",
|
||||
r.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
(Size::new(301.5, 211.25), 0.73),
|
||||
] {
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
let cache = Cache::new();
|
||||
let builds = Cell::new(0);
|
||||
println!(
|
||||
"size={}x{} zoom={zoom} step={step}",
|
||||
size.width, size.height
|
||||
);
|
||||
for scale in [1., 1.25, 2.] {
|
||||
for pan in [
|
||||
Vector::ZERO,
|
||||
Vector::new(-13.25, 9.75),
|
||||
Vector::new(6.125, -8.5),
|
||||
Vector::new(step - 0.125, step - 0.25),
|
||||
Vector::new(-0.125, 0.125),
|
||||
] {
|
||||
let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step));
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let expected = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let actual = shot(&mut r, size, scale);
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
let changed = expected
|
||||
.chunks_exact(4)
|
||||
.zip(actual.chunks_exact(4))
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = expected
|
||||
.iter()
|
||||
.zip(&actual)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"scale={scale} pan={},{} changed_pixels={changed} max_channel_delta={max}",
|
||||
pan.x, pan.y
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(builds.get(), 1);
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..23 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset)
|
||||
} else {
|
||||
r.draw_geometry(g)
|
||||
};
|
||||
let elapsed = black_box(now.elapsed().as_secs_f64() * 1000.);
|
||||
if i >= 3 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_and_submit changing_pan n=20 p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
ms[10], ms[19], ms[19]
|
||||
);
|
||||
}
|
||||
println!("cache_builds={}", builds.get());
|
||||
assert_eq!(builds.get(), 1);
|
||||
// Includes backend prepare/upload and readback; still not native presentation.
|
||||
if size.width == 1900. && zoom == 1. || size.width == 1440. && zoom == 0.5 {
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..10 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset);
|
||||
} else {
|
||||
r.draw_geometry(g);
|
||||
}
|
||||
let pixels = black_box(shot(&mut r, size, 1.));
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.;
|
||||
drop(pixels);
|
||||
if i >= 2 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_submit_screenshot changing_pan n=8 p50_ms={:.4} min_ms={:.4} max_ms={:.4}",
|
||||
ms[4], ms[0], ms[7]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
//! Renderer-level phase translation experiment, not application code.
|
||||
use iced::advanced::{
|
||||
Renderer as _,
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
fn dots(f: &mut Frame, size: Size, step: f32, phase: Vector) {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(
|
||||
Point::new(phase.x + x as f32 * step, phase.y + y as f32 * step),
|
||||
0.65,
|
||||
),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn old(r: &Renderer, size: Size, step: f32, phase: Vector) -> Geometry {
|
||||
let mut f = Frame::new(r, size);
|
||||
f.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
dots(&mut f, size, step, phase);
|
||||
f.into_geometry()
|
||||
}
|
||||
fn phase(r: &Renderer, cache: &Cache, size: Size, step: f32, builds: &Cell<usize>) -> Geometry {
|
||||
cache.draw_with_bounds(
|
||||
r,
|
||||
Rectangle {
|
||||
x: -1.,
|
||||
y: -1.,
|
||||
width: size.width + step + 2.,
|
||||
height: size.height + step + 2.,
|
||||
},
|
||||
|f| {
|
||||
builds.set(builds.get() + 1);
|
||||
dots(f, size, step, Vector::ZERO);
|
||||
},
|
||||
)
|
||||
}
|
||||
fn submit(r: &mut Renderer, size: Size, g: Geometry, pan: Vector) {
|
||||
r.with_layer(Rectangle::with_size(size), |r| {
|
||||
r.with_translation(pan, |r| r.draw_geometry(g))
|
||||
});
|
||||
}
|
||||
fn shot(r: &mut Renderer, size: Size, scale: f32) -> Vec<u8> {
|
||||
Headless::screenshot(
|
||||
r,
|
||||
Size::new(
|
||||
(size.width * scale).ceil() as u32,
|
||||
(size.height * scale).ceil() as u32,
|
||||
),
|
||||
scale,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut r = rt
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
println!(
|
||||
"renderer={}; phase-cache prototype; NOT native FPS",
|
||||
r.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
(Size::new(301.5, 211.25), 0.73),
|
||||
] {
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
let cache = Cache::new();
|
||||
let builds = Cell::new(0);
|
||||
println!(
|
||||
"size={}x{} zoom={zoom} step={step}",
|
||||
size.width, size.height
|
||||
);
|
||||
for scale in [1., 1.25, 2.] {
|
||||
for pan in [
|
||||
Vector::ZERO,
|
||||
Vector::new(-13.25, 9.75),
|
||||
Vector::new(6.125, -8.5),
|
||||
Vector::new(step - 0.125, step - 0.25),
|
||||
Vector::new(-0.125, 0.125),
|
||||
] {
|
||||
let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step));
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let expected = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let actual = shot(&mut r, size, scale);
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
let changed = expected
|
||||
.chunks_exact(4)
|
||||
.zip(actual.chunks_exact(4))
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = expected
|
||||
.iter()
|
||||
.zip(&actual)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"scale={scale} pan={},{} changed_pixels={changed} max_channel_delta={max}",
|
||||
pan.x, pan.y
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(builds.get(), 1);
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..23 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset)
|
||||
} else {
|
||||
r.draw_geometry(g)
|
||||
};
|
||||
let elapsed = black_box(now.elapsed().as_secs_f64() * 1000.);
|
||||
if i >= 3 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_and_submit changing_pan n=20 p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
ms[10], ms[19], ms[19]
|
||||
);
|
||||
}
|
||||
println!("cache_builds={}", builds.get());
|
||||
assert_eq!(builds.get(), 1);
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
//! Full-sequence opaque phase replay with diagnostics and repeat controls; not application code.
|
||||
use iced::advanced::{Renderer as _, graphics::geometry::Renderer as _, renderer::Headless};
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
fn dots(f: &mut Frame, size: Size, step: f32, phase: Vector) {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
f.fill(
|
||||
&Path::circle(
|
||||
Point::new(phase.x + x as f32 * step, phase.y + y as f32 * step),
|
||||
0.65,
|
||||
),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn old(r: &Renderer, size: Size, step: f32, phase: Vector) -> Geometry {
|
||||
let mut f = Frame::new(r, size);
|
||||
f.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
dots(&mut f, size, step, phase);
|
||||
f.into_geometry()
|
||||
}
|
||||
fn phase(r: &Renderer, cache: &Cache, size: Size, step: f32, builds: &Cell<usize>) -> Geometry {
|
||||
cache.draw_with_bounds(
|
||||
r,
|
||||
Rectangle {
|
||||
x: -step,
|
||||
y: -step,
|
||||
width: size.width + 3. * step,
|
||||
height: size.height + 3. * step,
|
||||
},
|
||||
|f| {
|
||||
builds.set(builds.get() + 1);
|
||||
f.fill_rectangle(
|
||||
Point::new(-step, -step),
|
||||
Size::new(size.width + 3. * step, size.height + 3. * step),
|
||||
BG,
|
||||
);
|
||||
dots(f, size, step, Vector::ZERO);
|
||||
},
|
||||
)
|
||||
}
|
||||
fn submit(r: &mut Renderer, size: Size, g: Geometry, pan: Vector) {
|
||||
r.with_layer(Rectangle::with_size(size), |r| {
|
||||
r.with_translation(pan, |r| r.draw_geometry(g))
|
||||
});
|
||||
}
|
||||
fn shot(r: &mut Renderer, size: Size, scale: f32) -> Vec<u8> {
|
||||
Headless::screenshot(
|
||||
r,
|
||||
Size::new(
|
||||
(size.width * scale).ceil() as u32,
|
||||
(size.height * scale).ceil() as u32,
|
||||
),
|
||||
scale,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
// Diagnostics only: this performs no rendering and does not mutate the renderer.
|
||||
fn log_coordinates(expected: &[u8], actual: &[u8], size: Size, scale: f32) {
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
let physical_width = (size.width * scale).ceil() as usize;
|
||||
for (index, (a, b)) in expected
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.zip(actual.as_chunks::<4>().0)
|
||||
.enumerate()
|
||||
.filter(|(_, (a, b))| a != b)
|
||||
.take(20)
|
||||
{
|
||||
println!(
|
||||
"difference_xy={},{} expected={a:?} actual={b:?}",
|
||||
index % physical_width,
|
||||
index / physical_width
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_control(label: &str, expected: &[u8], actual: &[u8], size: Size, scale: f32) {
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
let changed = expected
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
.zip(actual.as_chunks::<4>().0)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = expected
|
||||
.iter()
|
||||
.zip(actual)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"comparison={label} size={}x{} scale={scale} pan=-13.25,9.75 changed_pixels={changed} max_channel_delta={max}",
|
||||
size.width, size.height
|
||||
);
|
||||
log_coordinates(expected, actual, size, scale);
|
||||
}
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut r = rt
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
println!(
|
||||
"renderer={}; phase-cache prototype; NOT native FPS",
|
||||
r.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
(Size::new(301.5, 211.25), 0.73),
|
||||
] {
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
let cache = Cache::new();
|
||||
let builds = Cell::new(0);
|
||||
println!(
|
||||
"size={}x{} zoom={zoom} step={step}",
|
||||
size.width, size.height
|
||||
);
|
||||
for scale in [1., 1.25, 2.] {
|
||||
for pan in [
|
||||
Vector::ZERO,
|
||||
Vector::new(-13.25, 9.75),
|
||||
Vector::new(6.125, -8.5),
|
||||
Vector::new(step - 0.125, step - 0.25),
|
||||
Vector::new(-0.125, 0.125),
|
||||
] {
|
||||
let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step));
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let expected = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let actual = shot(&mut r, size, scale);
|
||||
assert_eq!(expected.len(), actual.len());
|
||||
let changed = expected
|
||||
.chunks_exact(4)
|
||||
.zip(actual.chunks_exact(4))
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
let max = expected
|
||||
.iter()
|
||||
.zip(&actual)
|
||||
.map(|(a, b)| a.abs_diff(*b))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
println!(
|
||||
"scale={scale} pan={},{} changed_pixels={changed} max_channel_delta={max}",
|
||||
pan.x, pan.y
|
||||
);
|
||||
// Keep the original render/timing sequence; print diagnostics only.
|
||||
log_coordinates(&expected, &actual, size, scale);
|
||||
}
|
||||
}
|
||||
assert_eq!(builds.get(), 1);
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..23 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset)
|
||||
} else {
|
||||
r.draw_geometry(g)
|
||||
};
|
||||
let elapsed = black_box(now.elapsed().as_secs_f64() * 1000.);
|
||||
if i >= 3 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_and_submit changing_pan n=20 p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
ms[10], ms[19], ms[19]
|
||||
);
|
||||
}
|
||||
println!("cache_builds={}", builds.get());
|
||||
assert_eq!(builds.get(), 1);
|
||||
// Includes backend prepare/upload and readback; still not native presentation.
|
||||
if size.width == 1900. && zoom == 1. || size.width == 1440. && zoom == 0.5 {
|
||||
for cached in [false, true] {
|
||||
let mut ms = Vec::new();
|
||||
for i in 0..10 {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let offset = Vector::new((i as f32 * 0.25).rem_euclid(step), 9.75);
|
||||
let now = Instant::now();
|
||||
let g = if cached {
|
||||
phase(&r, &cache, size, step, &builds)
|
||||
} else {
|
||||
old(&r, size, step, offset)
|
||||
};
|
||||
if cached {
|
||||
submit(&mut r, size, g, offset);
|
||||
} else {
|
||||
r.draw_geometry(g);
|
||||
}
|
||||
let pixels = black_box(shot(&mut r, size, 1.));
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.;
|
||||
drop(pixels);
|
||||
if i >= 2 {
|
||||
ms.push(elapsed);
|
||||
}
|
||||
}
|
||||
ms.sort_by(f64::total_cmp);
|
||||
println!(
|
||||
"cached={cached} construct_submit_screenshot changing_pan n=8 p50_ms={:.4} min_ms={:.4} max_ms={:.4}",
|
||||
ms[4], ms[0], ms[7]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// All original cases, timings and screenshots have completed above.
|
||||
// Reuse the same renderer for controls. This cache is newly constructed,
|
||||
// then retained across all control repetitions; original per-case caches
|
||||
// have already gone out of scope, exactly as in the opaque probe.
|
||||
println!("original_sequence_complete; beginning same-renderer repeat controls");
|
||||
let size = Size::new(301.5, 211.25);
|
||||
let step = (24f32 * 0.73).max(14.);
|
||||
let pan: Vector<f32> = Vector::new(-13.25, 9.75);
|
||||
let offset = Vector::new(pan.x.rem_euclid(step), pan.y.rem_euclid(step));
|
||||
let cache = Cache::new();
|
||||
let builds = Cell::new(0);
|
||||
for repetition in 1..=3 {
|
||||
for scale in [1., 2.] {
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let first = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = old(&r, size, step, offset);
|
||||
r.draw_geometry(g);
|
||||
let second = shot(&mut r, size, scale);
|
||||
report_control(
|
||||
&format!("old/old repetition={repetition}"),
|
||||
&first,
|
||||
&second,
|
||||
size,
|
||||
scale,
|
||||
);
|
||||
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let first = shot(&mut r, size, scale);
|
||||
r.reset(Rectangle::with_size(size));
|
||||
let g = phase(&r, &cache, size, step, &builds);
|
||||
submit(&mut r, size, g, offset);
|
||||
let second = shot(&mut r, size, scale);
|
||||
report_control(
|
||||
&format!("cached/cached repetition={repetition}"),
|
||||
&first,
|
||||
&second,
|
||||
size,
|
||||
scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("control_cache_builds={}", builds.get());
|
||||
assert_eq!(builds.get(), 1);
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
//! Headless rendering experiment; creates no desktop window and sends no input.
|
||||
use iced::advanced::{
|
||||
graphics::geometry::Renderer as _,
|
||||
renderer::{Headless, Renderer as _},
|
||||
};
|
||||
use iced::widget::canvas;
|
||||
use iced::{
|
||||
Color, Point, Rectangle, Renderer, Size, Vector,
|
||||
widget::canvas::{Cache, Frame, Geometry, Path},
|
||||
};
|
||||
use std::{cell::Cell, hint::black_box, time::Instant};
|
||||
|
||||
// Compile the actual candidate cache, not a second implementation of its key.
|
||||
#[path = "../../../crates/oa-desktop/src/diagram/grid.rs"]
|
||||
mod grid;
|
||||
mod style {
|
||||
pub const LINE: iced::Color = iced::Color::from_rgb(0.86, 0.89, 0.91);
|
||||
}
|
||||
|
||||
const BG: Color = Color::from_rgb8(246, 248, 251);
|
||||
const DOT: Color = Color::from_rgb(0.86, 0.89, 0.91);
|
||||
|
||||
fn paint(frame: &mut Frame, size: Size, zoom: f32, pan: Vector) {
|
||||
frame.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
frame.fill(
|
||||
&Path::circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65),
|
||||
DOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn fresh(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
paint(&mut frame, size, zoom, pan);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn batched(renderer: &Renderer, size: Size, zoom: f32, pan: Vector) -> Geometry {
|
||||
let mut frame = Frame::new(renderer, size);
|
||||
frame.fill_rectangle(Point::ORIGIN, size, BG);
|
||||
let step = (24. * zoom).max(14.);
|
||||
let ox = pan.x.rem_euclid(step);
|
||||
let oy = pan.y.rem_euclid(step);
|
||||
let path = Path::new(|p| {
|
||||
for x in 0..(size.width / step) as i32 + 1 {
|
||||
for y in 0..(size.height / step) as i32 + 1 {
|
||||
p.circle(Point::new(ox + x as f32 * step, oy + y as f32 * step), 0.65);
|
||||
p.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.fill(&path, DOT);
|
||||
frame.into_geometry()
|
||||
}
|
||||
fn screenshot(renderer: &mut Renderer, size: Size, geometry: Geometry) -> Vec<u8> {
|
||||
renderer.reset(Rectangle::with_size(size));
|
||||
renderer.draw_geometry(geometry);
|
||||
Headless::screenshot(
|
||||
renderer,
|
||||
Size::new(size.width as u32, size.height as u32),
|
||||
1.,
|
||||
BG,
|
||||
)
|
||||
}
|
||||
fn measure<T>(label: &str, n: usize, mut f: impl FnMut() -> T) {
|
||||
for _ in 0..3 {
|
||||
black_box(f());
|
||||
}
|
||||
let mut values = Vec::new();
|
||||
for _ in 0..n {
|
||||
let now = Instant::now();
|
||||
let value = black_box(f());
|
||||
values.push(now.elapsed().as_secs_f64() * 1000.);
|
||||
drop(value);
|
||||
}
|
||||
values.sort_by(f64::total_cmp);
|
||||
let at = |p: f64| values[((n - 1) as f64 * p).ceil() as usize];
|
||||
println!(
|
||||
"{label}: n={n} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}",
|
||||
at(0.5),
|
||||
at(0.95),
|
||||
values[n - 1]
|
||||
);
|
||||
}
|
||||
fn main() {
|
||||
let n = std::env::args()
|
||||
.nth(1)
|
||||
.map(|s| s.parse::<usize>().unwrap())
|
||||
.unwrap_or(20)
|
||||
.max(1);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut renderer = runtime
|
||||
.block_on(<Renderer as Headless>::new(
|
||||
iced::Font::DEFAULT,
|
||||
16.into(),
|
||||
None,
|
||||
))
|
||||
.expect("headless production renderer");
|
||||
println!(
|
||||
"renderer={} samples={n}; screenshot includes offscreen rendering + GPU readback, NOT native frame presentation",
|
||||
renderer.name()
|
||||
);
|
||||
for (size, zoom) in [
|
||||
(Size::new(1900., 660.), 1.),
|
||||
(Size::new(1900., 660.), 0.5),
|
||||
(Size::new(1440., 900.), 1.),
|
||||
(Size::new(1440., 900.), 0.5),
|
||||
] {
|
||||
let pan = Vector::new(-13.25, 9.75);
|
||||
let cache = Cache::new();
|
||||
let calls = Cell::new(0);
|
||||
let cached = |r: &Renderer| {
|
||||
cache.draw(r, size, |f| {
|
||||
calls.set(calls.get() + 1);
|
||||
paint(f, size, zoom, pan);
|
||||
})
|
||||
};
|
||||
let step = (24f32 * zoom).max(14.);
|
||||
println!(
|
||||
"\nsize={}x{} zoom={zoom} dots={}",
|
||||
size.width,
|
||||
size.height,
|
||||
((size.width / step) as i32 + 1) * ((size.height / step) as i32 + 1)
|
||||
);
|
||||
let baseline = fresh(&renderer, size, zoom, pan);
|
||||
let baseline_pixels = screenshot(&mut renderer, size, baseline);
|
||||
let geometry = batched(&renderer, size, zoom, pan);
|
||||
let pixels = screenshot(&mut renderer, size, geometry);
|
||||
let differences = baseline_pixels
|
||||
.iter()
|
||||
.zip(&pixels)
|
||||
.filter(|(a, b)| a != b)
|
||||
.count();
|
||||
println!("batched_rgba_byte_differences={differences}");
|
||||
assert_eq!(
|
||||
differences, 0,
|
||||
"batched path must preserve exact grid pixels"
|
||||
);
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
let geometry = cached(&renderer);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, geometry));
|
||||
assert_eq!(calls.get(), 1);
|
||||
println!("pixel_parity=cold_and_warm_exact");
|
||||
measure("fresh_geometry", n, || fresh(&renderer, size, zoom, pan));
|
||||
let production = grid::Grid::default();
|
||||
let g = production.draw(&renderer, size, zoom, pan);
|
||||
assert_eq!(baseline_pixels, screenshot(&mut renderer, size, g));
|
||||
measure("production_cached_geometry", n, || {
|
||||
production.draw(&renderer, size, zoom, pan)
|
||||
});
|
||||
let mut tick = 0;
|
||||
measure("production_changing_pan_geometry", n, || {
|
||||
tick += 1;
|
||||
production.draw(
|
||||
&renderer,
|
||||
size,
|
||||
zoom,
|
||||
pan + Vector::new(tick as f32 * 0.25, 0.),
|
||||
)
|
||||
});
|
||||
measure("cached_geometry", n, || cached(&renderer));
|
||||
assert_eq!(calls.get(), 1, "identical camera must reuse geometry");
|
||||
measure("fresh_geometry_and_screenshot", n, || {
|
||||
let g = fresh(&renderer, size, zoom, pan);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cached_geometry_and_screenshot", n, || {
|
||||
let g = cached(&renderer);
|
||||
screenshot(&mut renderer, size, g)
|
||||
});
|
||||
measure("cache_forced_miss_geometry", n, || {
|
||||
cache.clear();
|
||||
cached(&renderer)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Preserve receipt and review bytes exactly as bound by the evidence indexes.
|
||||
* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
|
||||
@@ -0,0 +1,85 @@
|
||||
# State transition multiline label verification
|
||||
|
||||
Issue [#36](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/36), Codex-ytr83r/root. Tested production/test source is **70bdcdf8fec1728e971cd90ad7f79a1df99777ff**, based on released **715036e132677dd2bc2fb37ed316d93dc9987aec**. Subsequent commits containing this evidence are documentation-only. The September 24 prerelease remains unchanged. Integration ownership and status belong to the issue.
|
||||
|
||||
## Outcome and boundary
|
||||
|
||||
The original `Ready\nfor review` title no longer collides with its summary. Routing prepares a shared, measured hard-line plan used by canvas paint, hit boxes, previews, focus/Fit and SVG. Opaque State labels receive hits before covered node/row pixels, in reverse paint order; exposed action rows retain their identity. Authored geometry and all semantic/source data remain unchanged. See the [contract](../../state-label-layout-contract.md) for the retained excerpt budgets, separators, measurement and scope.
|
||||
|
||||
The bounded Windows implementation passed its focused core gates, full desktop suite, formatting, strict workspace/all-target Clippy, product builds, and maximized-window native B/W/U Save/reopen checks. A later isolated trial also passed Home Open and all three transition text-row hits at restored1442×932/displayed79%, preserving all nine source files and the entire Project. The earlier unsuccessful restored1442×842 attempt and Home-button discrepancy remain explicit in [observations](observations.md) and [independent native review](native-review.md); the later success does not establish their cause. No all-window input or universal Unicode rendering claim follows. This is a follow-up contribution, not a replacement release or complete application acceptance.
|
||||
|
||||
## Automated checks
|
||||
|
||||
Windows x86_64-pc-windows-gnullvm, Rust1.98.1, locked/offline dependencies, one build job, no incremental compilation. All completed successful gates preserve identical source inventories before/after.
|
||||
|
||||
| Gate | Source | Result |
|
||||
| --- | --- | --- |
|
||||
| `cargo test --locked --offline -p oa-core --test state_label_layout -- --test-threads=1` | 2af40c7e | 4 passed |
|
||||
| `cargo test --locked --offline -p oa-core --test group_movement --test release_routing --test release_states --test routing_geometry --test routing_labels --test state_diagrams -- --test-threads=1` | 2af40c7e | 64 passed |
|
||||
| `cargo test --locked --offline -p oa-desktop --bin arcadia-studio -- --test-threads=1` | 70bdcdf8 | 1540 passed, 0 failed, 7 unchanged existing manual/diagnostic ignores |
|
||||
| `cargo fmt --all -- --check` | 70bdcdf8 | passed |
|
||||
| `cargo clippy --locked --offline --workspace --all-targets -- -D warnings` | 70bdcdf8 | passed, no warnings |
|
||||
| `cargo build -p oa-desktop --bin arcadia-studio --locked --offline --message-format=json-render-diagnostics` | 70bdcdf8 | passed |
|
||||
| `cargo build -p oa-core --example attachment_snapshot --locked --offline --message-format=json-render-diagnostics` | 70bdcdf8 | passed |
|
||||
|
||||
Core production/tests did not change after2af40c7e. Desktop adds12 passing regressions, with no removed or changed baseline outcomes. They cover width-dominating text and Unicode measurements, raw-prefix typed-reference provenance, covered/exposed row and overlapping-label selection, Shift/mixed-group refusal, generic fallback canvas, preview cancellation, fixed(+40,+40) complete-Project group commit/one-step Undo/Redo, and SVG line positions/order. These are scoped checks; the full workspace test suite was not repeated for this follow-up.
|
||||
|
||||
Preserved failure: desktop gate03 at66278ed3 failed compilation with E0382 in a new test; no tests ran. Commit70bdcdf8 adds `row.clone()` in that test only. Its failed receipt remains failed; later success does not relabel it. The [source review](source-review.md) also preserves and closes the earlier generic-fallback hit-order finding at the source-review level.
|
||||
|
||||
## Native trial
|
||||
|
||||
The exact original nine-file fixture comes from peerbed7f825fd00ec32299032a664a5f5d753aad86d, `harness/experiments/state-multiselect-20260923/native/evidence.zip`, SHA256`1d08c1bc6af4856a569ddd320ddf5403a302b8d9437750663a498541e489d663`.
|
||||
|
||||
B is original; W changes only the transition title to40 W characters; U changes only that title to CRLF/CJK/combining/ZWJ/trailing-newline text. Independent complete-Project oracles and exact source bytes are frozen before GUI input. At the actual maximized1605×872 window, displayed68% and82% scales show separated/contained lines. Inspector aliases establish label-versus-exposed-action selection. Native group movement M is unexecuted; the fixed movement/history regression supplies that separate evidence.
|
||||
|
||||
All six pure-layout checkpoints passed: initial Save and fresh-process reopen/Save for B/W/U. Each preserves the entire Project and all nine source files byte-for-byte. Two isolated-profile processes exited0 after parent-observed Alt+F4, with empty logs and immutable binaries/source inputs unchanged. The [independent review](native-review.md) checks original images, complete models, every source-file hash and both process logs. Recorded zoom percentages are rounded; no exact100% or DPI calibration is claimed.
|
||||
|
||||
Preserved GUI:119440896 bytes, SHA256`0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a`. Runtime libunwind.dll SHA256`aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a`. The supplemental bundle reader was built from the same candidate/compiler and is separate from the Cargo product artifacts.
|
||||
|
||||
## Reusable evidence
|
||||
|
||||
### Supplemental restored-window trial, September 25
|
||||
|
||||
The [supplemental observations](restored-native-observations.md) record the same
|
||||
preserved70bdcdf8 executable, rechecked before launch, with a fresh isolated copy
|
||||
of B and a private recent-model profile. At1442×932/displayed79%, Home Open loaded
|
||||
the fixture. First title, second title, and summary clicks each selected stable
|
||||
transition alias `between`; the latter two followed independently observed
|
||||
selection of `state_b`. Pure Save preserved all nine source files byte-for-byte
|
||||
and the complete expected Project. Alt+F4 termination was observed and both GUI
|
||||
logs were empty, but the watcher returned a null exit code; this is not exit0
|
||||
evidence. The initial incorrect direct snapshot invocation rejected includes;
|
||||
the established converter-then-snapshot verification subsequently passed.
|
||||
That invocation error is preserved narratively; its original stderr was replaced
|
||||
by the successful rerun's empty log. The [independent supplemental review](restored-native-review.md)
|
||||
checks all ten original images, archive members, source/model comparisons and
|
||||
tool hashes, and retains this evidence limitation.
|
||||
|
||||
[Supplemental ZIP](restored-native-evidence.zip):888553 bytes,
|
||||
SHA256`f120509338fb0ac55480ce11929381fb1a46fecf7d3a1c8869c395335f0eba0a`.
|
||||
Its45 evidence members plus manifest include original captures, source copies,
|
||||
verification scripts/results and launch/close records. Executables/DLLs are
|
||||
excluded. The [integrity receipt](restored-native-integrity.json) records successful
|
||||
CRC and every member length/hash checks. Historical builder paths require local
|
||||
relocation. Earlier failures remain unexplained; this bounded success does not
|
||||
replace the previous evidence or establish acceptance at other window sizes.
|
||||
|
||||
A subsequent bounded attempt to obtain the original1442×842 size was
|
||||
**unexecuted**: supported UI resizing yielded932→921→732 heights and tool
|
||||
target/bounds errors. The fixture was never opened or saved. Its unchanged
|
||||
bytes/model establish copied-fixture integrity only. [Exact observations and
|
||||
errors](restored842-observations.md) preserve this outcome without diagnosing an
|
||||
application defect. [Attempt ZIP](restored842-attempt.zip):661746 bytes,
|
||||
SHA256`3efb41d79a9dab70d7026ba83a7f9ec60a319fab79a8f0a383f3ce7a71b0f42f`;
|
||||
[integrity receipt](restored842-integrity.json). The original height-specific
|
||||
acceptance gap remains open.
|
||||
|
||||
### Original trial and automated gates
|
||||
|
||||
- [Native evidence ZIP](native-evidence.zip):2886914 bytes, SHA256`039add524eb99ce855c2dc5680eec01c1d5b7f23024025c428125b9771a8e687`.
|
||||
- [Native archive receipt](native-archive.json) and [member/relocation index](native-collection.json) preserve original paths and hashes; these paths describe the builder machine and are not peer-accessible dependencies. The ZIP carries original captures, metadata, source fixtures/oracles, checkpoint/process/helper receipts and hash-bound scripts, plus unchanged parent observations and review. Binary/cache payloads are excluded with their hashes retained.
|
||||
- [Automated evidence ZIP](automated-evidence.zip):6244436 bytes, SHA256`7706302ef7ee5d160a23ff2d562bedb8911b4e6d7ad4c941544088974b539b6e`,113 verified members. Its [index](automated-index.json), [integrity receipt](automated-integrity.json), and [scope note](automated-README.md) retain both successful and failed command records, frozen build environment, exact source inventories, compiler/dependency receipts and reproducible helper sources. Consult original per-command results rather than adding counts from different scopes.
|
||||
|
||||
Native archive member bytes and exact member set were read back after creation; all six independent model comparisons and source-snapshot checks passed, both process receipts were clean, and no bound input changed. The collector establishes archive integrity, not visual correctness by itself.
|
||||
|
||||
To reproduce on another Windows machine: check out70bdcdf8, use the project Windows build instructions, run the commands above, obtain the pinned peer fixture, and use the preserved trial/launcher recipes with newly generated local build/admission paths. Never reuse the builder's historical absolute paths or claim its binaries were rebuilt locally. Linux remains excluded by the user's current instruction. External SVG viewer font substitution, authored label occlusion of nodes, dynamic State row/header sizing, and broader State/Process congestion remain limitations outside this fix.
|
||||
@@ -0,0 +1,11 @@
|
||||
# #36 automated evidence archive
|
||||
|
||||
Original gate01–09 and supplemental converter receipts/logs, preserved byte-for-byte.
|
||||
|
||||
Source attribution remains per gate:01/02 at2af40c7e,03 at66278ed3,04–09 and converter at70bdcdf8. Gate03 is the retained E0382 compile failure (exit101, no tests executed); gate04 is its later corrected run. Seven existing ignored desktop tests remain ignored. See raw command results and test-evidence note. No full-workspace, native GUI or overall acceptance claim is made here.
|
||||
|
||||
The ZIP includes source inventories, frozen environments, Cargo JSON artifact ownership, original compiler/library hash inventories, process-command logs, scripts and attributed review notes. Executable/runtime/rlib payloads are omitted; index.json records their exact hashes, sizes and original paths. Historical absolute paths inside receipts are unchanged. This archive is evidence, not a runnable distribution; reproduce against each identified source commit using the retained commands/environment/toolchain bindings.
|
||||
|
||||
Existing notes describe their own review scope/time and may identify findings resolved later; they are not silently rewritten by packaging. Parent-owned native observations and captures belong in their separate native evidence archive.
|
||||
|
||||
Packaging verifies exact ZIP member names/count and decompressed SHA256/length for every member, including this README and index. integrity.json outside ZIP identifies the final ZIP and confirms original gate/converter inventories unchanged. No build/helper/GUI was executed and no publication performed by packaging.
|
||||
Binary file not shown.
@@ -0,0 +1,866 @@
|
||||
{
|
||||
"created_utc": "2026-09-25T00:06:28.817849+00:00",
|
||||
"gates": [
|
||||
{
|
||||
"name": "01-core-layout",
|
||||
"source_commit": "2af40c7e27ed9750b9585c07369dbf17a046a92d",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-core",
|
||||
"--test",
|
||||
"state_label_layout",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\result.json",
|
||||
"bytes": 4180,
|
||||
"sha256": "902fe24f6304c830f90edb060004d9ffcc218102ee19615171aa82afd7fdfe1f"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "02-core-routing-state",
|
||||
"source_commit": "2af40c7e27ed9750b9585c07369dbf17a046a92d",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-core",
|
||||
"--test",
|
||||
"group_movement",
|
||||
"--test",
|
||||
"release_routing",
|
||||
"--test",
|
||||
"release_states",
|
||||
"--test",
|
||||
"routing_geometry",
|
||||
"--test",
|
||||
"routing_labels",
|
||||
"--test",
|
||||
"state_diagrams",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\result.json",
|
||||
"bytes": 4382,
|
||||
"sha256": "6d102bf5b2e715316233f47756cb21215209045360ae736235974651435c6652"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "03-desktop-tests",
|
||||
"source_commit": "66278ed33b95961d1c129e2ddc172c567effbf9b",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"exit_code": 101,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\result.json",
|
||||
"bytes": 4185,
|
||||
"sha256": "a8e98b66b01f9965f0d49d9f9a4b83fd25e543295c29d8cefc575e98d2346e2c"
|
||||
},
|
||||
"classification": "failed_compile_no_tests",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "04-desktop-tests",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\result.json",
|
||||
"bytes": 4187,
|
||||
"sha256": "868aeace11d5307be72c01db0bb64347aed700c70fac3efb3ae0ac92d5c32707"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "05-format",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"fmt",
|
||||
"--all",
|
||||
"--",
|
||||
"--check"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\result.json",
|
||||
"bytes": 4066,
|
||||
"sha256": "09bdbca3090cb92eab436b37f209440d394b4501a6f24ea55006fc3854dc2f07"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "06-clippy",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"--workspace",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\result.json",
|
||||
"bytes": 4141,
|
||||
"sha256": "226cf432707a66c7baf6d4b866ac101a39d00a46628b3b61162694add7d04a88"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "07-gui",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"build",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"--message-format=json-render-diagnostics"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\result.json",
|
||||
"bytes": 4171,
|
||||
"sha256": "28a5726e4001652a59f1a8aab47539cf256a48a350b9749d9d3349d0950a80a1"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "08-snapshot",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"cargo",
|
||||
"build",
|
||||
"-p",
|
||||
"oa-core",
|
||||
"--example",
|
||||
"attachment_snapshot",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"--message-format=json-render-diagnostics"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\result.json",
|
||||
"bytes": 4191,
|
||||
"sha256": "cee7277ae91a41507481755755b2aacc85e3d0696db5bb30f9800a0c4c56e180"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
},
|
||||
{
|
||||
"name": "09-converter-wrapper",
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"argv": [
|
||||
"C:/Users/User/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/python.exe",
|
||||
"C:/Users/User/Documents/Git/ARCADIA-Studio/.local/prerelease-native-preflight-20260924/build_converter-full-deps.py",
|
||||
"--repo",
|
||||
"C:/Users/User/Documents/Git/ARCADIA-Studio/.local/state-label-layout-20260924",
|
||||
"--source-commit",
|
||||
"70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"--environment",
|
||||
"C:/Users/User/Documents/Git/ARCADIA-Studio/.local/state-label-checks-20260924/07-gui/environment.json",
|
||||
"--output",
|
||||
"C:/Users/User/Documents/Git/ARCADIA-Studio/.local/state-label-converter-70bdcdf8-20260924"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"runner_failure": null,
|
||||
"inputs_unchanged": true,
|
||||
"result": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\result.json",
|
||||
"bytes": 4670,
|
||||
"sha256": "0c2a93749eb99e8c2e1456437bc65b07edf0ce20dd4731ec764711bea315fa03"
|
||||
},
|
||||
"classification": "completed_command",
|
||||
"scope": "Recorded scoped command outcome, not overall acceptance"
|
||||
}
|
||||
],
|
||||
"converter_source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"converter_summary": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\summary.json",
|
||||
"bytes": 5561,
|
||||
"sha256": "4772bd6f5025566fe41d0b03d69e9b5b9bd907e2cc72deb36bfed32d43becf8d"
|
||||
},
|
||||
"members": {
|
||||
"gates/01-core-layout/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/01-core-layout/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\orchestration.log",
|
||||
"bytes": 171,
|
||||
"sha256": "4806d5c8b5392bbea0ed7797ffb84e215cd61636a4aa95a73435a102daeb5133"
|
||||
},
|
||||
"gates/02-core-routing-state/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/02-core-routing-state/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\orchestration.log",
|
||||
"bytes": 178,
|
||||
"sha256": "8a707fb3a4e2be95f733de9d229bf12c392749dc882123b61addbe6f9ed1b59b"
|
||||
},
|
||||
"gates/03-desktop-tests/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/03-desktop-tests/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\orchestration.log",
|
||||
"bytes": 175,
|
||||
"sha256": "fd4070b4fde5bdd51a4c3d1fd3201fb6c829aab6e17a4e228287e032679caafa"
|
||||
},
|
||||
"gates/04-desktop-tests/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/04-desktop-tests/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\orchestration.log",
|
||||
"bytes": 173,
|
||||
"sha256": "81e3d7ae505b357442a6844c9158822192dff18c3dbe565d64d9bfa451f03cc2"
|
||||
},
|
||||
"gates/05-format/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/05-format/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\orchestration.log",
|
||||
"bytes": 166,
|
||||
"sha256": "ecaeb74a7d9fb5897d9745c06a8976e4d84850e8b8db3ff86cb672fca21c2256"
|
||||
},
|
||||
"gates/06-clippy/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/06-clippy/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\orchestration.log",
|
||||
"bytes": 166,
|
||||
"sha256": "ecab94bc7c7fb6c367df2a0e26ebf8136b6dc78ced18a4517208e6364a88a37f"
|
||||
},
|
||||
"gates/07-gui/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/07-gui/native-artifact-relocation.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\native-artifact-relocation.json",
|
||||
"bytes": 5301,
|
||||
"sha256": "11fde1495bcfe19d97e7e596705af09c5f9baa7e805029af0d362c68e3bcc2f3"
|
||||
},
|
||||
"gates/07-gui/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\orchestration.log",
|
||||
"bytes": 163,
|
||||
"sha256": "bcaeb79c30f2249579b9fd8b31bc4d70cb8bba29d928f885eee87d0978beceae"
|
||||
},
|
||||
"gates/07-gui/summary.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\summary.json",
|
||||
"bytes": 7825,
|
||||
"sha256": "b5b5cf0d9e5518dbe1a37bbc665bdf968e762fc47634d89b095e48563664b964"
|
||||
},
|
||||
"gates/08-snapshot/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/08-snapshot/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\orchestration.log",
|
||||
"bytes": 168,
|
||||
"sha256": "1ef9cc7cc2fd057f21ea2a169137bfdcd6799745d23c4478005b549e6eb547a0"
|
||||
},
|
||||
"gates/09-converter-wrapper/environment.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\environment.json",
|
||||
"bytes": 9477,
|
||||
"sha256": "5ade2b585de0ba1e7471948d2b9a6fb7bb43cb3fc7ae3d92fda4d5bc682a4f83"
|
||||
},
|
||||
"gates/09-converter-wrapper/orchestration.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\orchestration.log",
|
||||
"bytes": 177,
|
||||
"sha256": "a2b44604b2e86a82212032893f57e3aef0905450a6fc3c7da7c90b632d9c08ed"
|
||||
},
|
||||
"gates/01-core-layout/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/01-core-layout/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\context.json",
|
||||
"bytes": 3535,
|
||||
"sha256": "84fabf0a67ff5903fa62928b271132acae52ab11b9af46992f14fa4c1c98db6a"
|
||||
},
|
||||
"gates/01-core-layout/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\inputs-after.json",
|
||||
"bytes": 1745987,
|
||||
"sha256": "b96d7ae8c2c7dfc9533de522951ebd38b9b74ab13bfb000fdf723e52fc4ef6cb"
|
||||
},
|
||||
"gates/01-core-layout/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\inputs-before.json",
|
||||
"bytes": 1745987,
|
||||
"sha256": "b96d7ae8c2c7dfc9533de522951ebd38b9b74ab13bfb000fdf723e52fc4ef6cb"
|
||||
},
|
||||
"gates/01-core-layout/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\output.log",
|
||||
"bytes": 441,
|
||||
"sha256": "95fb11393f2d0ee4345644372501f1bb7d94059f7c7ec8c3e73f2742c5a1869f"
|
||||
},
|
||||
"gates/01-core-layout/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\result.json",
|
||||
"bytes": 4180,
|
||||
"sha256": "902fe24f6304c830f90edb060004d9ffcc218102ee19615171aa82afd7fdfe1f"
|
||||
},
|
||||
"gates/01-core-layout/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\01-core-layout\\command\\stderr.log",
|
||||
"bytes": 329,
|
||||
"sha256": "ad6e55a7f9d133c1171d59895bb0b42dd9e8db249b77c711fc8e586c960afa38"
|
||||
},
|
||||
"gates/02-core-routing-state/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/02-core-routing-state/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\context.json",
|
||||
"bytes": 3721,
|
||||
"sha256": "3a9c2164e24755a1f8e677bbbaca9efa123c01a83688f2a7a1ece480be5d9051"
|
||||
},
|
||||
"gates/02-core-routing-state/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\inputs-after.json",
|
||||
"bytes": 1745987,
|
||||
"sha256": "b96d7ae8c2c7dfc9533de522951ebd38b9b74ab13bfb000fdf723e52fc4ef6cb"
|
||||
},
|
||||
"gates/02-core-routing-state/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\inputs-before.json",
|
||||
"bytes": 1745987,
|
||||
"sha256": "b96d7ae8c2c7dfc9533de522951ebd38b9b74ab13bfb000fdf723e52fc4ef6cb"
|
||||
},
|
||||
"gates/02-core-routing-state/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\output.log",
|
||||
"bytes": 5918,
|
||||
"sha256": "e0d83aa7e2daf6a708ff05f270bfd42c62e7256c9bb021d0f82a89ce82e76a72"
|
||||
},
|
||||
"gates/02-core-routing-state/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\result.json",
|
||||
"bytes": 4382,
|
||||
"sha256": "6d102bf5b2e715316233f47756cb21215209045360ae736235974651435c6652"
|
||||
},
|
||||
"gates/02-core-routing-state/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\02-core-routing-state\\command\\stderr.log",
|
||||
"bytes": 1007,
|
||||
"sha256": "7bab937f221dbe4bf6a814a2b4b96436ae6412a4abd63e227152cd039c725ede"
|
||||
},
|
||||
"gates/03-desktop-tests/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/03-desktop-tests/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\context.json",
|
||||
"bytes": 3535,
|
||||
"sha256": "65baf5817cf901ff2346f3ef4a0edc7782a66c36ccf299699fced70aec1cad1e"
|
||||
},
|
||||
"gates/03-desktop-tests/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "927af387a86f8be0af148ea8d75a610c73d588b7056108d9b7199ae300ca977e"
|
||||
},
|
||||
"gates/03-desktop-tests/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "927af387a86f8be0af148ea8d75a610c73d588b7056108d9b7199ae300ca977e"
|
||||
},
|
||||
"gates/03-desktop-tests/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\output.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/03-desktop-tests/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\result.json",
|
||||
"bytes": 4185,
|
||||
"sha256": "a8e98b66b01f9965f0d49d9f9a4b83fd25e543295c29d8cefc575e98d2346e2c"
|
||||
},
|
||||
"gates/03-desktop-tests/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\03-desktop-tests\\command\\stderr.log",
|
||||
"bytes": 1530,
|
||||
"sha256": "a572854597b1b47c5df352f769a3c785024f836107fade2300a5e6ed1ceee733"
|
||||
},
|
||||
"gates/04-desktop-tests/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/04-desktop-tests/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\context.json",
|
||||
"bytes": 3535,
|
||||
"sha256": "78622cb7fbd144369aae5932a464a79afc8a5c038063d93b59ddc8e21e6c5627"
|
||||
},
|
||||
"gates/04-desktop-tests/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/04-desktop-tests/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/04-desktop-tests/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\output.log",
|
||||
"bytes": 179631,
|
||||
"sha256": "af4d05f303f7a68357992c7d7d893d7c8856389826c89118a0632e2a01fdf346"
|
||||
},
|
||||
"gates/04-desktop-tests/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\result.json",
|
||||
"bytes": 4187,
|
||||
"sha256": "868aeace11d5307be72c01db0bb64347aed700c70fac3efb3ae0ac92d5c32707"
|
||||
},
|
||||
"gates/04-desktop-tests/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\04-desktop-tests\\command\\stderr.log",
|
||||
"bytes": 325,
|
||||
"sha256": "7b552c022264730769bb9855e876b15b60725dcc11775a5f312c683e439f1929"
|
||||
},
|
||||
"gates/05-format/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/05-format/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\context.json",
|
||||
"bytes": 3435,
|
||||
"sha256": "9503502416d1b1592711c0ff823ad86ffad43e32a26343195164e1a04ae90188"
|
||||
},
|
||||
"gates/05-format/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/05-format/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/05-format/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\output.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/05-format/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\result.json",
|
||||
"bytes": 4066,
|
||||
"sha256": "09bdbca3090cb92eab436b37f209440d394b4501a6f24ea55006fc3854dc2f07"
|
||||
},
|
||||
"gates/05-format/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\05-format\\command\\stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/06-clippy/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/06-clippy/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\context.json",
|
||||
"bytes": 3508,
|
||||
"sha256": "b9ee7be690311f6be7d732b31a9b19842ff9611bdb02a659c16716e667ce7b0d"
|
||||
},
|
||||
"gates/06-clippy/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/06-clippy/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/06-clippy/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\output.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/06-clippy/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\result.json",
|
||||
"bytes": 4141,
|
||||
"sha256": "226cf432707a66c7baf6d4b866ac101a39d00a46628b3b61162694add7d04a88"
|
||||
},
|
||||
"gates/06-clippy/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\06-clippy\\command\\stderr.log",
|
||||
"bytes": 726,
|
||||
"sha256": "cee474909c10b670a53566563437d87cee181639e801e37b5a7b51e9692bffd3"
|
||||
},
|
||||
"gates/07-gui/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/07-gui/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\context.json",
|
||||
"bytes": 3539,
|
||||
"sha256": "8b96ad95af7fa2b35308f1aacca6342bc131aa7dfd863da5960ad7907f86abd0"
|
||||
},
|
||||
"gates/07-gui/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/07-gui/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/07-gui/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\output.log",
|
||||
"bytes": 276391,
|
||||
"sha256": "f41e61dde1466a49effc3b88370f70edf3d9bca116bc6bfc2dfcf8d1c1143138"
|
||||
},
|
||||
"gates/07-gui/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\result.json",
|
||||
"bytes": 4171,
|
||||
"sha256": "28a5726e4001652a59f1a8aab47539cf256a48a350b9749d9d3349d0950a80a1"
|
||||
},
|
||||
"gates/07-gui/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\command\\stderr.log",
|
||||
"bytes": 478,
|
||||
"sha256": "d82b0db404deaa998657bf13189afb5c9a88b29ea0fd63d3c3faa13d779b6119"
|
||||
},
|
||||
"gates/08-snapshot/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/08-snapshot/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\context.json",
|
||||
"bytes": 3550,
|
||||
"sha256": "8a69ae1c020d97a816fa6683f668850b3215fe2cfb9fb541b01a52d3367f294a"
|
||||
},
|
||||
"gates/08-snapshot/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/08-snapshot/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/08-snapshot/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\output.log",
|
||||
"bytes": 40990,
|
||||
"sha256": "12bc7478c31cfae7544f807e65e7c2154eb5784b3dc369854065b2bd99c55956"
|
||||
},
|
||||
"gates/08-snapshot/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\result.json",
|
||||
"bytes": 4191,
|
||||
"sha256": "cee7277ae91a41507481755755b2aacc85e3d0696db5bb30f9800a0c4c56e180"
|
||||
},
|
||||
"gates/08-snapshot/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\08-snapshot\\command\\stderr.log",
|
||||
"bytes": 183,
|
||||
"sha256": "89cc60ca015b395250b57c6495471245529c0f315e250841c83600b860a723b1"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/changes.patch": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\changes.patch",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\context.json",
|
||||
"bytes": 4015,
|
||||
"sha256": "9424a3d6e019730d3c3706c55dccf13a5273582a5add9d1827009fe084da86af"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/output.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\output.log",
|
||||
"bytes": 165,
|
||||
"sha256": "105cd2a14d06c216616345dc02746661cbb2261fae230c1494154126d4758ad9"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\result.json",
|
||||
"bytes": 4670,
|
||||
"sha256": "0c2a93749eb99e8c2e1456437bc65b07edf0ce20dd4731ec764711bea315fa03"
|
||||
},
|
||||
"gates/09-converter-wrapper/command/stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\09-converter-wrapper\\command\\stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"converter/cargo-artifact.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\cargo-artifact.json",
|
||||
"bytes": 1722,
|
||||
"sha256": "6a03cadc613b4d5e4276f84154cc9097883c7621999635685b53d14d136228db"
|
||||
},
|
||||
"converter/cargo-lib.result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\cargo-lib.result.json",
|
||||
"bytes": 1080,
|
||||
"sha256": "9420b2f0830fcd5f3b7aa47389113f0207df632d4c571ed513f2b9dbc5fcaf17"
|
||||
},
|
||||
"converter/cargo-lib.stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\cargo-lib.stderr.log",
|
||||
"bytes": 60,
|
||||
"sha256": "bb76d746acf8827d33e2ea465a2a7e5fa1c6a4d49d77f76f98461f0764efb480"
|
||||
},
|
||||
"converter/cargo-lib.stdout.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\cargo-lib.stdout.log",
|
||||
"bytes": 40032,
|
||||
"sha256": "877b292ea6cc5c026a7d9636d806e994f2f740c7193314e483538d4cd7fd37e0"
|
||||
},
|
||||
"converter/context.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\context.json",
|
||||
"bytes": 2799,
|
||||
"sha256": "8e86beea2fa1092c41d6037f974ad28c0aef19a9393a68bcc8962a0bea3ffcf7"
|
||||
},
|
||||
"converter/converter-build.result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\converter-build.result.json",
|
||||
"bytes": 1749,
|
||||
"sha256": "4be9fbb3ba65f771e8c31b37bae750be2bc0445ec9734a582a19f2dd3fa1a38d"
|
||||
},
|
||||
"converter/converter-build.stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\converter-build.stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"converter/converter-build.stdout.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\converter-build.stdout.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"converter/inputs-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\inputs-after.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"converter/inputs-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\inputs-before.json",
|
||||
"bytes": 1746401,
|
||||
"sha256": "3288e287198ae7fb322cfb35747506f27761119fb1f96f46ba39515a30e78c73"
|
||||
},
|
||||
"converter/libraries-after.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\libraries-after.json",
|
||||
"bytes": 939193,
|
||||
"sha256": "63019c6a891db6964ef0b4edaf3e3a6c9036b30d02693552d3a977f6cfb64913"
|
||||
},
|
||||
"converter/libraries-before.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\libraries-before.json",
|
||||
"bytes": 939193,
|
||||
"sha256": "63019c6a891db6964ef0b4edaf3e3a6c9036b30d02693552d3a977f6cfb64913"
|
||||
},
|
||||
"converter/rustc-version.result.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\rustc-version.result.json",
|
||||
"bytes": 991,
|
||||
"sha256": "a66a06c9ffa97e1563c07ffbd1a5efa4b2e1f888349bbac01f1948ab8f287471"
|
||||
},
|
||||
"converter/rustc-version.stderr.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\rustc-version.stderr.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
"converter/rustc-version.stdout.log": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\rustc-version.stdout.log",
|
||||
"bytes": 197,
|
||||
"sha256": "cdaaa400be47332d69e678836251d4635b5ff2ecd34aa962b69d71f8961d274b"
|
||||
},
|
||||
"converter/summary.json": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\summary.json",
|
||||
"bytes": 5561,
|
||||
"sha256": "4772bd6f5025566fe41d0b03d69e9b5b9bd907e2cc72deb36bfed32d43becf8d"
|
||||
},
|
||||
"scripts/run-state-label-check-20260924.py": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\run-state-label-check-20260924.py",
|
||||
"bytes": 2725,
|
||||
"sha256": "4f5fd0845813a27a84921dee6b17391cd872a48f86e0d7659f5c727eb65f821b"
|
||||
},
|
||||
"scripts/prerelease-gates-v2-20260924/run.py": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\prerelease-gates-v2-20260924\\run.py",
|
||||
"bytes": 13814,
|
||||
"sha256": "698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252"
|
||||
},
|
||||
"scripts/prerelease-native-preflight-20260924/build_converter-full-deps.py": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\prerelease-native-preflight-20260924\\build_converter-full-deps.py",
|
||||
"bytes": 12695,
|
||||
"sha256": "048441adc856ef07a0f0b1bbbe0157a959bc00cbb0838a8ed18e735082602cb4"
|
||||
},
|
||||
"scripts/prerelease-native-preflight-20260924/bundle_converter.rs": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\prerelease-native-preflight-20260924\\bundle_converter.rs",
|
||||
"bytes": 2279,
|
||||
"sha256": "acbcc955e970e47b4e825f7880af2fa70ca679410ee4d765d28623ea646c4bf6"
|
||||
},
|
||||
"scripts/state-label-native-20260924/aggregate_builds.py": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-native-20260924\\aggregate_builds.py",
|
||||
"bytes": 7965,
|
||||
"sha256": "31023f1901ab49482f14ab103708e71cd34f24c1bed9d90124e40084304fffec"
|
||||
},
|
||||
"scripts/state-label-native-20260924/BUILD-ADMISSION.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-native-20260924\\BUILD-ADMISSION.md",
|
||||
"bytes": 5697,
|
||||
"sha256": "5bd950666db498014f3c21ccada768e8fa1e7f1ff965ae1a82b7b170128a024b"
|
||||
},
|
||||
"notes/state-label-test-evidence-20260924.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-test-evidence-20260924.md",
|
||||
"bytes": 8810,
|
||||
"sha256": "4894f1d48a4c1ebd34534073b79f6749ce788bfe93ae991d2b9b9bc37b3463de"
|
||||
},
|
||||
"notes/state-label-implementation-review-20260924.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-implementation-review-20260924.md",
|
||||
"bytes": 9093,
|
||||
"sha256": "78a98849d23cb75b46147b466d1de69944ef4b5c38a90948cccf08a55b38d04d"
|
||||
},
|
||||
"notes/state-label-layout-715-contract-review-20260924.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-layout-715-contract-review-20260924.md",
|
||||
"bytes": 11671,
|
||||
"sha256": "47873b0c1ed80ec3eb8511843b6a10e2be821cf19564bc702167798d10871868"
|
||||
},
|
||||
"notes/state-label-native-tooling-review-20260924.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-native-tooling-review-20260924.md",
|
||||
"bytes": 4627,
|
||||
"sha256": "c499c04b0166ec17d863d9ea076cdef1336d3b6d93cbb38b4840ed660c29fe28"
|
||||
},
|
||||
"notes/state-label-collector-review-20260924.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-collector-review-20260924.md",
|
||||
"bytes": 2953,
|
||||
"sha256": "80c0ce488a964144db9575db0aeecc4cd660b2aec0eeb5d7821c0879a088f1b7"
|
||||
},
|
||||
"packaging/archive.py": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-automated-evidence-20260924\\archive.py",
|
||||
"bytes": 8048,
|
||||
"sha256": "83e731923a78557c421c5185fb332aee7f096d01aba78fc03fa20ccddaca8900"
|
||||
},
|
||||
"README.md": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-automated-evidence-20260924\\README.md",
|
||||
"bytes": 1598,
|
||||
"sha256": "27309073da687f0a267e2ea445365f47f95313e49170789d56fce74a508ea0a5"
|
||||
}
|
||||
},
|
||||
"omitted_binary_payloads": [
|
||||
{
|
||||
"origin": "gates/07-gui/artifacts/arcadia-studio.exe",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\artifacts\\arcadia-studio.exe",
|
||||
"bytes": 119440896,
|
||||
"sha256": "0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
},
|
||||
{
|
||||
"origin": "gates/07-gui/artifacts/attachment_snapshot.exe",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\artifacts\\attachment_snapshot.exe",
|
||||
"bytes": 19189248,
|
||||
"sha256": "08a26d43257f6d611c8efce22551262234890995dbe49c2ee49df09d62eec091"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
},
|
||||
{
|
||||
"origin": "gates/07-gui/artifacts/libunwind.dll",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-checks-20260924\\07-gui\\artifacts\\libunwind.dll",
|
||||
"bytes": 190464,
|
||||
"sha256": "aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
},
|
||||
{
|
||||
"origin": "converter/artifacts/bundle_converter.exe",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\artifacts\\bundle_converter.exe",
|
||||
"bytes": 24505856,
|
||||
"sha256": "fbdd7c5696f879eb541516982cefc04bdc7fdf3adc8059b118a7992ea50f8085"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
},
|
||||
{
|
||||
"origin": "converter/artifacts/liboa_core.rlib",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\artifacts\\liboa_core.rlib",
|
||||
"bytes": 102318950,
|
||||
"sha256": "a00033d489890443d4e5fcefb018d8b4c43d3027bc00d030bb8204fa62f3d362"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
},
|
||||
{
|
||||
"origin": "converter/artifacts/libunwind.dll",
|
||||
"record": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-converter-70bdcdf8-20260924\\artifacts\\libunwind.dll",
|
||||
"bytes": 190464,
|
||||
"sha256": "aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a"
|
||||
},
|
||||
"reason": "Binary payload omitted; exact hash/path retained"
|
||||
}
|
||||
],
|
||||
"scope": "Byte packaging and existing receipt inspection only. Multiple source pins and failed03 remain explicit. No overall acceptance or publication."
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"archive": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-automated-evidence-20260924\\evidence.zip",
|
||||
"bytes": 6244436,
|
||||
"sha256": "7706302ef7ee5d160a23ff2d562bedb8911b4e6d7ad4c941544088974b539b6e"
|
||||
},
|
||||
"index": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-automated-evidence-20260924\\index.json",
|
||||
"bytes": 45295,
|
||||
"sha256": "3c6d45fb0bffe33c35f1b46d3a2eabc2f89de18d5abc57db0a234dc34165b9f0"
|
||||
},
|
||||
"readme": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-automated-evidence-20260924\\README.md",
|
||||
"bytes": 1598,
|
||||
"sha256": "27309073da687f0a267e2ea445365f47f95313e49170789d56fce74a508ea0a5"
|
||||
},
|
||||
"members": 113,
|
||||
"omitted_binary_payloads": 6,
|
||||
"original_gate_and_converter_inventories_unchanged": true,
|
||||
"recorded_gate_source_inventories_unchanged": true,
|
||||
"scope": "Verified archive integrity and original evidence preservation only"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# #36 integration handoff review
|
||||
|
||||
Ready for the parent's documentation/evidence successor commit, within the stated limits. No actionable findings or material overclaim found. Read-only review on 2026-09-24; no compiler, GUI, model validator/helper or Gitea operation executed.
|
||||
|
||||
Reviewed the experiment README, STATE opening checkpoint, contract status and appended DECISIONS entry against packaged automated/native records, earlier test reconciliation, observations and independent native review. The documents identify tested implementation70bdcdf8fec1728e971cd90ad7f79a1df99777ff, distinguish core gates at2af40c7e27ed9750b9585c07369dbf17a046a92d, and preserve gate03's compile failure at66278ed33b95961d1c129e2ddc172c567effbf9b. Git confirms no oa-core changes between the focused core gate and tested desktop pin. Current tracked modifications are only three harness documents plus the new evidence folder; no source changes were found in this handoff.
|
||||
|
||||
Independent packaged-copy checks:
|
||||
|
||||
- native-evidence.zip:2886914 bytes; SHA256039add524eb99ce855c2dc5680eec01c1d5b7f23024025c428125b9771a8e687;293 members. ZIP CRC test, exact member-name set/no duplicates, every indexed member length/SHA256, and embedded collection.json equality with the external index all pass.
|
||||
- automated-evidence.zip:6244436 bytes; SHA2567706302ef7ee5d160a23ff2d562bedb8911b4e6d7ad4c941544088974b539b6e;113 members. The same CRC/member/hash/index checks pass; embedded README equals its external published copy. All nine archived command-result records match the index, including failed03; successful gate claims retain their individual pins and scopes.
|
||||
- All six archived B/W/U actual Projects independently equal their frozen expected Projects. All54 checkpoint source members match their recorded hashes and original variant bytes. Both archived run launch/close pairs agree on PID, record exit0/null failure/unchanged inputs, and contain empty stdout/stderr. Copied observations.md and native-review.md are exact archived payloads.
|
||||
|
||||
The earlier collector finding is closed by the executed archive: the embedded index is present, byte-identical and included in this independent full readback. Original receipt paths and hashes remain historical identities rather than silently rewritten peer-local paths.
|
||||
|
||||
Accepted limitations are visible and consistent: maximized1605×872 native observations only; unsuccessful restored-window label/Home attempts and their unproven cause retained; rounded68/82 scales; no native group M/Undo/Redo or generic-fallback GUI claim; mounted exact-group/history coverage separate; no universal Unicode/font or all-window guarantee. Linux and wider State/Process congestion remain excluded. No full workspace-test rerun, merged-main acceptance, replacement release, issue closure or broader application completion is implied.
|
||||
|
||||
This review checks handoff integrity and correspondence. It relies on the named independent visual review for screenshot interpretation rather than repeating its image inspection, and on parent observation for the specific Alt+F4 close cause. Independent-machine restored-window follow-up and integration remain with the issue owner.
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"archive": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-native-evidence-20260924\\evidence.zip",
|
||||
"bytes": 2886914,
|
||||
"sha256": "039add524eb99ce855c2dc5680eec01c1d5b7f23024025c428125b9771a8e687"
|
||||
},
|
||||
"index": {
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-label-native-evidence-20260924\\collection.json",
|
||||
"bytes": 154086,
|
||||
"sha256": "5a5428a00919d9018123ccfd0644085f733a944545e912fee3bfa8b55371dd0d"
|
||||
},
|
||||
"source_commit": "70bdcdf8fec1728e971cd90ad7f79a1df99777ff",
|
||||
"original_trial_unchanged": true,
|
||||
"scope": "Archive integrity only; consult separate mechanical receipt findings and parent observations"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
# #36 native visual review: 70bdcdf8
|
||||
|
||||
**Final scope update:** the addendum below completes the fresh-process reopen and process-exit review. The original snapshot's pending statements are retained as history; the unmaximized-input limitation remains open.
|
||||
|
||||
Independent read-only review of original JPG/JSON captures01–15 under `.local/state-label-trial-70bdcdf8-20260924/raw`, the frozen B/W/U inputs, completed save checkpoints and `harness/state-label-layout-contract.md`. All15 full application images were inspected at original resolution; the16×16 secondary captures are not useful application evidence. No GUI input, product/helper execution, build, source/tooling edit or Gitea operation was performed. Fresh-process reopen and final process closure are outside this review snapshot.
|
||||
|
||||
The recorded run01 launcher identifies source `70bdcdf8fec1728e971cd90ad7f79a1df99777ff`, PID5328, preserved GUI SHA256 `0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a`. All15 capture metadata records identify that executable path. Independently reread manifest SHA256 is `7f1b6cfa8f6ca0c36b0b867f8a2ccf555cea292e8df878af629dac5b63d36ee6`. This is evidence attribution, not a newly rerun executable/build check.
|
||||
|
||||
## Observed result
|
||||
|
||||
No clear title/summary self-overlap, wrapping or ink clipping in the required B/W/U labels was visible in the maximized captures at displayed68% and82%. The text is small at those scales; these screenshots support the concrete cases, not universal glyph containment, perfect legibility at every scale, or pixel-exact font metrics.
|
||||
|
||||
- **B, captures03–07:** `Ready` and `for review` occupy separate rows, followed by the `External · Completion [request_ready]` summary. Captures04 and06 show State transition properties with stable alias `between`; capture05 shows State action properties with alias `action_a` and the exposed `entry / Record request` row selected. Thus the recorded sequence demonstrates distinct observable targets for the covered transition label and exposed row. Capture07 shows `All source files saved`. The broader authored diagram remains crowded and overlaps node territory; this is not evidence of global collision avoidance.
|
||||
- **W, captures08–11:** the input is exactly40 `W` characters, verified from the pinned expected Project. The title occupies one visibly wider line at both scales, with the summary on the next line. Its rightmost ink appears within the visible white label extent; it is not shortened to the old scalar-width box. Capture09 shows `between` selected after the parent's described far-right/padding interaction. A static after-capture confirms the selected object, not the exact preceding pointer trajectory. Capture11 shows saved status.
|
||||
- **U, captures12–15:** the input is exactly `Ready\r\n漢字 é 👩🔧\n` (combining acute, ZWJ sequence and terminal LF verified from the expected Project). The canvas shows two inked title rows and an extra blank cell before the summary, consistent with three title cells plus one summary cell. The second row's marks appear contained; capture13 shows `between` after the described blank-cell interaction, and15 shows `between` in the summary-selection sequence. At this screenshot size I do not claim independently verified shaping of every emoji/code point, nor infer preservation of CRLF from pixels.
|
||||
|
||||
**Preserved ambiguous observation:** captures01/02 are unmaximized1442×842 at displayed79%. Capture02 clearly has `state_a` selected after the parent's attempted click at(660,534). The coordinate/geometry cause has not been resolved by this review. This attempt must remain recorded as unresolved, not silently reclassified as a successful label hit or generalized into a demonstrated product defect. Successful maximized observations do not establish an all-window-size hit pass. Displayed percentages are rounded; no exact world-space movement oracle is inferred from them.
|
||||
|
||||
## Save evidence checked separately
|
||||
|
||||
At inspection, checkpoints `01-B-saved`, `02-W-saved`, and `03-U-saved` each have accepted receipts. I independently hashed the checkpoint `actual.json` and pinned expected file: each full Project file is byte-identical to its expected B/W/U file and the manifest/receipt hashes match. Each checkpoint has nine source files, matching the nine baseline members; every saved file's bytes match both its corresponding immutable baseline and its receipt SHA256. Consequently these pure saves preserve complete Project values and authored source bytes, including title separators and authored label geometry. This conclusion uses saved artifacts, not the screenshots' saved-status text. No claim is made for the unused M movement oracle.
|
||||
|
||||
## Limits and retained history
|
||||
|
||||
Parent reports core68 passes, desktop1540 passes/seven existing ignored, formatting and strict Clippy on the candidate; this visual review does not independently rerun or audit all gate logs. Preserve the earlier desktop gate03 E0382 failure: the initial Shift/mixed regression moved `row` into its selection and reused it. Candidate70bdcdf8 fixes that test-only ownership issue with `row.clone()`; the failed attempt is not a runtime failure and is not erased by the later pass.
|
||||
|
||||
The bounded maximized visual observations and three exact pure-save checkpoints support this label repair. Fresh-process reopen, final close/log evidence and any generic-canvas native claims require their own remaining evidence. Group(+40,+40), preview/settled equality and one-step history are assigned to the fixed-oracle production-input regression; no native group displacement is inferred post hoc. The unmaximized79% target ambiguity remains an explicit limitation unless independently resolved.
|
||||
|
||||
## Final addendum: fresh process, reopen and closure
|
||||
|
||||
Inspected all seven original full-size captures18–24 without repeating the earlier15-image review. Their metadata consistently identifies the pinned GUI path, window263106 and maximized1605×872 dimensions. Run02 launch records identify a fresh PID6100 with the same70bdcdf8 source and GUI hash as run01 PID5328.
|
||||
|
||||
Reopened B at displayed68% retains its separate two-line title and following summary in18;19 shows State transition properties with alias `between` after the parent's recorded summary click(737,518), and20 shows saved status. Reopened U in21/22 retains its two inked title rows and blank-cell separation before summary. Reopened W in23/24 retains the single wide title line and separate summary. No new visible required-case clipping or title/summary self-overlap was found. These are reopen observations in the second process; W/U are not additional fresh-process label-hit trials.
|
||||
|
||||
Independently reread and hashed checkpoints `04-B-reopened`, `05-U-reopened`, `06-W-reopened`: all accepted; each `actual.json` is byte-identical to its corresponding manifest-pinned expected full Project and receipt hash. Each saved source directory again contains nine files, equal in count to baseline and receipt; every file matches its baseline and receipt SHA256. Across the six reviewed save/reopen checkpoints, all complete Projects and source bytes remain exact. No semantic, authored route or title normalization change is inferred or permitted by these comparisons.
|
||||
|
||||
Both close receipts match their launch PID and source pin, report observed exit0, null failure, and `immutable_inputs_unchanged: true`. I independently checked all four stdout/stderr files are zero bytes; their recorded hashes are the empty-file SHA256. These receipts establish successful observed process exit and the launcher's unchanged-input check. The specific normal Alt+F4 close action is parent-observed evidence, not something this read-only reviewer performed or derives from exit0 alone.
|
||||
|
||||
The parent additionally reports that restored run02 Home Recent attempts at(1060,744) and the offset-hypothesis point(1052,713) did not open B; after maximizing, the actual Open point(1147,736) did. This broadens the unresolved observation beyond a State-label target, but does not prove a coordinate-mapping cause. Preserve all attempts. Native hit claims remain limited to the successful maximized trials; the initial79% attempt is still not passed or retrospectively diagnosed.
|
||||
|
||||
**Final bounded verdict:** no new actionable defect found in the reviewed maximized B/W/U presentation, six exact pure-save/reopen checkpoints, or recorded exits/logs. Fresh-process persistence evidence is now complete for these variants. Keep the restored-window input uncertainty, rounded-scale limitation, synthetic movement/history test scope, and absence of universal Unicode/all-window hit guarantees in the published evidence. This addendum does not certify a broader archive/collector or generic-canvas native trial.
|
||||
@@ -0,0 +1,25 @@
|
||||
# State label native observations
|
||||
|
||||
Parent observer: Codex-ytr83r/root, Windows native host, 2026-09-24. Candidate source `70bdcdf8fec1728e971cd90ad7f79a1df99777ff`; isolated profile and unrelated cwd per process. These are parent observations, separate from the machine-readable source/model checks. Original images and their bounds/timestamps are in `trial/raw/` in the collected archive.
|
||||
|
||||
## Inputs and method
|
||||
|
||||
The unchanged original peer fixture B has title `Ready\nfor review` and summary `External · Completion [request_ready]`. W changes only the title to 40 W characters. U changes only the title to `Ready\r\n漢字 é 👩🔧\n`. The stage independently validates their complete Projects and freezes their nine-file source closures. No model, route, or title editing occurred in the GUI. Save used Ctrl+S in place. Native movement M was not executed; exact movement/history remains covered by the mounted production-input regression.
|
||||
|
||||
Native trial used the actual public Fit and + controls. Displayed percentages are rounded, not exact scale measurements. Initial restored-window capture was 1442×842 at displayed79%; accepted coordinate-based checks used the maximized1605×872 window at displayed68% Fit and82% after one + click. No exact100% or DPI calibration claim is made.
|
||||
|
||||
## Observed outcomes
|
||||
|
||||
- B: captures01/03/06 show two title lines followed by a separate summary, without the reported title/summary collision. In the maximized window, click(737,518) at68% selected State transition alias `between` (04); exposed entry-row click(582,519) selected State action alias `action_a` (05). After +, summary click(732,499) at82% selected `between` (06). These targets were read from the actual inspector, not inferred from route color alone.
|
||||
- W: captures08–10 show the long single-line title within the enlarged white label extent and a separate summary. Click(900,514) in the far-right lower label area over State B at68% selected `between` (09), demonstrating the width-dominating title's hit area. Capture10 records82% appearance. Authored labels may still cover node content; this fix preserves their authored centers and is not an automatic collision-free layout claim.
|
||||
- U: captures12–15 show `Ready`, the Unicode line, a blank trailing title cell, and the summary below it. The visible ink fits the label extent at68/82 without a clear self-overlap. Blank-cell click(737,514) at68% selected `between` (13); summary click(731,504) at82% retained the transition (15). These small-font witnesses do not establish universal font/shaping correctness or arbitrary Unicode ink containment.
|
||||
- Ctrl+S completed with `All source files saved` for B/W/U (07/11/16). Checkpoints01-B-saved,02-W-saved,03-U-saved each report accepted=true/failure=null, full Project equality, and byte-exact preservation of all nine original source files for that variant.
|
||||
- Parent closed run01 using Alt+F4. The launcher recorded PID5328 exit0, empty stdout/stderr, and immutable inputs unchanged.
|
||||
- Fresh run02 PID6100 opened with Recent models listing the three exact working paths (17). After maximizing, B reopened through Recent (18); click(737,518) selected `between` again (19), then Save (20). U and W reopened through Recent in this fresh process (21/23) and were saved (22/24). They retained the visible label presentation. Checkpoints04-B-reopened,05-U-reopened,06-W-reopened each report accepted=true/failure=null and the same complete-model/source conservation.
|
||||
- Parent closed run02 using Alt+F4. Its launcher also recorded exit0, empty stdout/stderr, and immutable inputs unchanged. Neither process was terminated programmatically.
|
||||
|
||||
## Unsuccessful input attempts and limits
|
||||
|
||||
The initial restored-window click at requested(660,534), intended for B's summary, selected `state_a` instead (02). This attempt is not a passed label-hit check. On fresh run02, the restored-window Recent B Open button also did not respond to its screenshot-derived(1060,744) click. A one-off frame-offset hypothesis using(1052,713) likewise did not establish a successful Open. Maximizing through the observed accessibility control and clicking the newly observed Recent Open(1147,736) succeeded. This suggests a restored-window input-coordinate issue, but the cause is not proven; no product fix or universal restored-window hit correctness is claimed. The failed attempts are retained rather than recast as success.
|
||||
|
||||
Maximized checks, independent mounted hit regressions at multiple pan/zoom values, and complete Save/reopen comparisons form the scoped evidence. No native group drag, native Undo/Redo, exact1.0 scale, native generic-fallback-canvas interaction, Linux execution, or full-project usability acceptance is claimed here. The automated regressions separately exercise generic State targeting, fixed(+40,+40) group commit, exact Project history, and shared SVG positions. Wider State/Process congestion and dynamic row/header layout remain outside this bounded change.
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"archive": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-restored-native-20260925.zip",
|
||||
"bytes": 888553,
|
||||
"sha256": "f120509338fb0ac55480ce11929381fb1a46fecf7d3a1c8869c395335f0eba0a",
|
||||
"crc_ok": true,
|
||||
"all_member_lengths_and_sha256_verified": true,
|
||||
"payload_member_count": 45,
|
||||
"total_member_count": 46,
|
||||
"manifest_sha256": "f9a6997aa4e3b6358814d57b0da60f5d235ce81ac638e139a25bc7664b341624"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# #36 restored-window native observation
|
||||
|
||||
Local-only evidence, 2026-09-25 UTC. Parent owns publication/integration. No source code edits, commits, Gitea posts, model content edits, movement, deletion, or renaming were performed.
|
||||
|
||||
## Identity and isolation
|
||||
|
||||
Preserved source commit `70bdcdf8fec1728e971cd90ad7f79a1df99777ff`, earlier evidence commit `57b3fe4bff43a7df602f1567b8867bb1d31051d0`. Before launch rechecked GUI SHA256 `0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a`; confirmed adjacent libunwind.dll. `launch.json` records executable, child-only LOCALAPPDATA profile, unrelated working directory and PID2892. Copied original immutable baseline B into this evidence directory's `working/B`; original inputs were never opened in the GUI. Seeded private recent-model list with only this copy. `original-source-hashes.json` was recorded before launch.
|
||||
|
||||
All UI input used `node_repl` plus `@oai/sky`, with one action then refresh. Raw original screenshot payloads were saved once under `raw/`, accompanied by metadata without payload duplication. Screenshots were inspected through the tool's original display, not re-emitted or transformed. No capture/input helper errors occurred.
|
||||
|
||||
## Observed sequence
|
||||
|
||||
The restored window remained 1442×932 throughout (screenshot dimensions), with displayed State zoom79%. No maximize, resize, pan or zoom was performed. The fixture opens directly to Behavior & scenarios, Request lifecycle, State multiselect - main.
|
||||
|
||||
| Capture | Action and observed outcome |
|
||||
| --- | --- |
|
||||
| 01-home | Home, private B recent-model card visible. |
|
||||
| 02-open-attempt | Current observed Open clicked at(1061,574); title became State group native and Working status appeared. |
|
||||
| 03-opened | Refreshed without another input; fixture loaded, 0 selected. Ready and for review have separate title rows followed by External · Completion [request_ready]. No observed self-overlap or clipping within this title/summary. |
|
||||
| 04-title-hit | First title row clicked(652,515); inspector State transition, stable alias between. |
|
||||
| 05-clear | Blank canvas click(990,630) did **not** clear selection. Retained as an observation; not counted as reset or transition hit. |
|
||||
| 06-state-b | Click(798,596) selected State, stable alias state_b. |
|
||||
| 07-second-title | Second title row clicked(656,525); inspector State transition, stable alias between. |
|
||||
| 08-state-b | Click(798,596) again selected State, alias state_b. |
|
||||
| 09-summary | Summary clicked(661,534); inspector State transition, stable alias between. |
|
||||
| 10-save | Ctrl+S; bottom status All source files saved. |
|
||||
| 11-close-windows.json | Alt+F4, then fresh list_windows returned no Arcadia windows. |
|
||||
|
||||
Text is small at this zoom. The authored diagram remains crowded, and this is not global collision-avoidance evidence. This new run establishes bounded successful restored-window Home Open and first-title/second-title/summary hits at1442×932/79%, not all-window acceptance. Earlier1442×842/79% and restored Home failures remain recorded in the prior review; their cause is not established or reinterpreted by this success. No exact-world-space movement claim is made.
|
||||
|
||||
## Save and close verification
|
||||
|
||||
`verification.json` and reproducible `verify.py` record all nine source members byte-identical to the immutable original baseline and full Project JSON byte/value-identical to pinned expected B, SHA256 `bf04d72167e8779940dec040f47c7ac0ca69cb7feea35272f2a382c3e73dceb7`. The preserved bundle_converter.exe and attachment_snapshot.exe both returned0 with their previously recorded hashes; their stderr logs are empty. GUI stdout/stderr are both zero bytes.
|
||||
|
||||
Preserved failed verification attempt: attachment_snapshot.exe was first invoked directly on project.arcadia and rejected includes with `Error: Text("4:5: An include requires a source bundle; single-string parsing never reads files.")`; no actual.json was created then. The correct established converter-then-snapshot flow above subsequently passed. This was a verification invocation error, not a GUI/model defect.
|
||||
|
||||
Normal Alt+F4 close was directly performed and disappearance observed. The attached PowerShell process watcher records has_exited=true in `close.json`, but ExitCode is null. Therefore this run claims observed process termination, **not** observed exit0. No warning or crash appeared; GUI logs remain empty.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Independent review: #36 restored-window observation
|
||||
|
||||
Accept the bounded successful observation at 1442 x 932 / 79% as additional evidence. It does not resolve the earlier 1442 x 842 failures, establish their cause, certify arbitrary restored sizes, or establish GUI exit code 0. No material contradiction found in the bounded outcome.
|
||||
|
||||
Reviewed `.local/state-restored-native-20260925` and its sibling ZIP against original `.local/state-label-trial-70bdcdf8-20260924/baseline/B` and `expected/B.json`. Read REPORT, verification/launch/close receipts, package manifest, verification/package scripts, and raw screenshot metadata. No UI input, builds, model/source edits, publication or Gitea actions. This review file is the only write.
|
||||
|
||||
## Independently checked
|
||||
|
||||
- ZIP CRC test passed. Exact member set: 45 payload files plus PACKAGE-MANIFEST.json. Every payload length/SHA256 matched the manifest and current loose file bytes. Manifest bytes matched the ZIP copy and external receipt hash. ZIP is 888553 bytes; SHA256 `f120509338fb0ac55480ce11929381fb1a46fecf7d3a1c8869c395335f0eba0a`. Manifest SHA256 `f9a6997aa4e3b6358814d57b0da60f5d235ce81ac638e139a25bc7664b341624`.
|
||||
- All nine saved source members, including their relative-path set, are byte-identical to the original trial's immutable baseline B. Captured actual.json is byte-identical and JSON-value-identical to original expected/B.json; SHA256 `bf04d72167e8779940dec040f47c7ac0ca69cb7feea35272f2a382c3e73dceb7`.
|
||||
- Preserved converter and snapshot executable hashes match verification.json. GUI executable independently rehashed as `0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a`, matching launch.json. No helpers were rerun; converter/snapshot exit results remain recorded receipts. Source-byte identity and full snapshot identity were independently recomputed.
|
||||
- All ten raw JPEGs were independently viewed without editing/cropping. Their decoded dimensions are 1442 x 932, consistent with metadata. State captures display 79% and the restored-window titlebar control. Home Open progresses from the copied B recent entry, through Working, to the loaded fixture.
|
||||
- Capture03 has no selection. Capture04 shows State transition / stable alias `between` after the first-title action. Capture05 retains that selection following the blank-canvas action, as the report explicitly states. Captures06/08 show State / `state_b`; captures07/09 return to State transition / `between`, making the second-title and summary observations nonvacuous. Capture10 shows All source files saved.
|
||||
- The transition's two title rows and following summary are vertically distinct. Text is small and the diagram remains crowded; this is not a global readability or collision-avoidance pass. Screenshot state supports reported action outcomes, while exact input coordinates/action chronology come from the operator report, not independent replay.
|
||||
- close.json says has_exited=true with exit_code=null; final window inventory is empty. GUI stdout/stderr are empty. This supports observed termination only. Alt+F4 cause is the operator's recorded action; empty logs do not prove exit0.
|
||||
|
||||
## Scope and handoff qualifications
|
||||
|
||||
The original source pin is reported as `70bdcdf8fec1728e971cd90ad7f79a1df99777ff`; executable-to-source provenance inherits the original trial's build/staging evidence. This review independently binds the executable bytes, not a new build.
|
||||
|
||||
This success at 1442 x 932 cannot falsify a failure at 1442 x 842 or explain prior restored Home failures. Preserve both observations and keep #36's wider acceptance open. Source save with no model content changes proves preservation in this run, not changed-content persistence or a fresh-process reopen.
|
||||
|
||||
The failed direct snapshot invocation is preserved narratively in REPORT; the packaged snapshot stderr is the successful rerun's empty log. Do not describe the package as containing an independently reviewed raw log of that failed attempt.
|
||||
|
||||
The package omits executables and the original baseline/expected inputs. Publication should retain explicit links to the original trial evidence/source pin for reproducible comparison. This review makes no publication or integration claim.
|
||||
|
||||
Audit method: read-only Python standard-library ZIP CRC/member checks, SHA256 and complete byte/JSON equality against original trial files; Pillow image dimension decoding; view_image inspection of all ten raw images; PowerShell file hashing. No production decoder or GUI rerun.
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"archive": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\state-restored842-native-20260925.zip",
|
||||
"bytes": 661746,
|
||||
"sha256": "3efb41d79a9dab70d7026ba83a7f9ec60a319fab79a8f0a383f3ce7a71b0f42f",
|
||||
"crc_ok": true,
|
||||
"all_member_lengths_and_sha256_verified": true,
|
||||
"payload_member_count": 43,
|
||||
"total_member_count": 44,
|
||||
"manifest_sha256": "d51dc48095ba881b99ab745267830402fe4a164a680d85415c7b5f7383f6a15d"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# #36 exact1442x842 trial: unexecuted after bounded resize failure
|
||||
|
||||
Local-only 2026-09-25 evidence; parent owns publication/integration. No production source changes, compiler runs, git, Gitea, model Open/Save, content changes, or diagram movement.
|
||||
|
||||
The requested1442x842 window was not obtained. Home recent Open and79% title/second-title/summary trials at that size are **unexecuted**, not passed. Earlier restored-window uncertainty remains open. These resize/tool outcomes are not attributed to an application defect.
|
||||
|
||||
## Identity and isolation
|
||||
|
||||
Same preserved70bdcdf8fec1728e971cd90ad7f79a1df99777ff GUI, SHA256 rechecked before launch: `0401341388025544185455ad5520aa2c1b79ca1387d1a57c2bb0bddb8b23754a`. Adjacent libunwind.dll confirmed. launch.json records PID7500, executable, private child-only LOCALAPPDATA and cwd. Immutable original B copied to working/B and private recent-model list seeded; original-source-hashes.json captured before launch.
|
||||
|
||||
UI used only supported @oai/sky through node_repl. Original screenshots saved once; no payload re-emission or transformation.
|
||||
|
||||
## Every action and observation
|
||||
|
||||
| Capture | Action / result |
|
||||
| --- | --- |
|
||||
| 01-initial | Home at1442x932; private recent B visible. |
|
||||
| 02-resize | Bottom-area drag from(720,930) to(720,840); still1442x932. |
|
||||
| 03-system-menu | Alt+Space; visible system menu with Size. |
|
||||
| 04-size-mode | Press s; menu closes;1442x932. |
|
||||
| 05-bottom-size-mode | Down;1442x932. |
|
||||
| 06-size-up | Up; observed1442x921. |
|
||||
| no07 capture | Attempted click(720,841) to set edge; tool rejected with exact error below. No claim click delivered. |
|
||||
| 08-resize-recovery | Per skill, activate target and refresh; observed1442x732. This is observation, not diagnosis of why height changed. |
|
||||
| 09-finish-size | Return; remains1442x732. |
|
||||
| no10 capture | Final bounded drag from observed lower border(720,731) to(720,841) rejected with bounds error below. |
|
||||
| 11-before-close | Fresh observation1442x732, still Untitled mission Home. |
|
||||
| 12-close-windows.json | Normal Alt+F4, then list_windows returned no Arcadia windows. |
|
||||
|
||||
Exact first rejection:
|
||||
`point (779, 893) is over ChatGPT.exe "Chrome Legacy Window", not target window arcadia-studio.exe "Untitled mission — Arcadia Studio"; activate the target or take a fresh screenshot before retrying`
|
||||
|
||||
Exact final rejection:
|
||||
`point (720, 841) is outside window bounds { originX: 0, originY: 0, width: 1442, height: 732 }`
|
||||
|
||||
After both errors prior coordinates were discarded; final fresh observation preceded normal close. No other app was automated or intentionally targeted.
|
||||
|
||||
## Integrity and closure boundaries
|
||||
|
||||
verify.py/verification.json use the preserved converter and snapshot helpers to compare the **unopened, unsaved copy** to original immutable B. All9 source bytes and full Project bytes/values match. This is fixture integrity only, not native save evidence. Helpers both returned0; GUI stdout/stderr empty. Normal Alt+F4 and window disappearance directly observed; exit code was not captured. No exit0 claim.
|
||||
|
||||
The archive contains report, script/results, source copy/hashes, original screenshots/metadata, launch/profile and logs. Executables and DLLs excluded. No claim of842 interaction, reopened persistence, or universal window-size correctness.
|
||||
@@ -0,0 +1,36 @@
|
||||
# #36 implementation review at 2af40c7
|
||||
|
||||
Read-only source review of `2af40c7e27ed9750b9585c07369dbf17a046a92d` in `.local/state-label-layout-20260924`, confirmed clean after writers settled. This is a preliminary implementation review, not final acceptance. No independent compilation, GUI or runtime tests; parent owns execution. I authored the separate State canvas regression include earlier, so review of those tests is a consistency check, not independent authorship review.
|
||||
|
||||
**Follow-up status:** the P2 below is closed at source-review level by `1f471288284102f46f67075765568dbf5856474b`, inspected in clean `.local/state-label-hit-review-20260924` with exact parent `2af40c7e27ed9750b9585c07369dbf17a046a92d`. Integration and execution remain parent-owned; no native acceptance is claimed. No new actionable finding in this bounded follow-up.
|
||||
|
||||
## Actionable finding
|
||||
|
||||
**P2 — Generic diagram surface paints State labels above nodes but still selects the covered node.** This is a reachable surface: `views.rs:403` includes dedicated State diagrams in the saved-view picker (only Scenario is excluded), constructs `DiagramCanvas` at470, and `app.rs:3697` sends non-Scenario choices to `Page::Diagram`. The new generic painter defers State label rectangles/text until after nodes (`diagram.rs:1491`), matching their visible foreground position. However `diagram.rs:331` context targeting, `diagram.rs:999` ordinary press, and `diagram/multiselect.rs:206` production group/Shift targeting still resolve nodes before labels. Additionally `DiagramCanvas::hit_label` at428 chooses nearest center rather than the last painted overlapping State label. The same authored label over a State therefore looks selectable but returns the obscured placement in this supported view; two overlapping State labels can choose the obscured one.
|
||||
|
||||
Use one dedicated-State-plan background hit helper in the relevant generic target paths, before ordinary node targets but after existing explicit selected-handle decisions, with reverse actual label paint order. Keep non-State label/node priority unchanged. Cover generic ordinary click, context target and Shift target at a fixed label-over-node point; cover overlapping State labels with the lower label selected. The existing generic `detached_label_selects_connection_and_occluding_card_owns_its_hit` test describes non-State behavior and should continue to pass. Alternatively explicitly remove/restrict this supported surface, but that would be a larger product change than the local target fix.
|
||||
|
||||
**Repair constraint:** an unconditional non-Shift handle `return None` in generic `group_input` would bypass mixed-selection refusal: the ordinary route-edit path has no StateCanvas-style singleton gate. Instead resolve a dedicated-State selected attachment/bend to `Target::Edge(selected)` before the State-label prepass, then use existing Shift/group admission. The existing singleton/nonmember fallthrough already reaches ordinary handle editing where allowed. A selected Edge+Placement group must remain captured/refused, and Shift on a handle must toggle that handle's exact Edge even when another label overlaps it. This challenges the proposed follow-up design; it is not an additional defect introduced in the reviewed commit.
|
||||
|
||||
## Reviewed coherent behavior
|
||||
|
||||
- Core builds `StateLabelLayout` only for a StateTransition in a dedicated State projection. The raw global48 summary remains `EdgeLabelGeometry.text`; fixed title budget is computed from that raw excerpt before width measurement. LF/CRLF/CR and declared Unicode separators become explicit cells; blank/trailing cells survive; tab expansion is display-only. Title and summary positions share 12-unit cells, minimum24 height and five-unit horizontal padding.
|
||||
- `state_label::measure_line` matches installed `iced_graphics-0.14.0/src/geometry/text.rs::Text::draw_with`: same Paragraph implementation, global font system, font/shaping/alignment defaults, font10, absolute12 and infinite bounds. Helper wrappingNone versus painter default wrapping cannot introduce width wrapping at infinite width for these already split lines. Paragraph advance plus padding still needs the declared native ink-containment checks; no universal Unicode claim follows from the source review.
|
||||
- Production desktop routing consumers found by source search all call the measured wrapper, including ordinary/preview scene, Fit, State Fit, usage/focus, State bend setup, generic export and work/allocation geometry. Remaining direct core routing calls are tests. Measurement occurs before collision placement; core remains font-independent and derived plans are not serialized.
|
||||
- Dedicated State hit order matches its opaque backgrounds and reverse stable edge paint order. Endpoint/bend checks and group admission remain at their existing input sites. Raw summary prefix, not normalized display lines, feeds typed-reference visibility, preventing CRLF byte-offset corruption. The State projection admits only non-Internal StateTransition edges, so the painter's plan-only branch does not suppress another supported label kind there.
|
||||
- Settled SVG now emits the planned escaped lines and opaque backgrounds after nodes; the actual export test exercises this ordering, beyond the isolated string helper test. Non-State SVG label placement is preserved.
|
||||
- The diff contains no semantic model/schema/serialization change and does not author node sizes, route fraction/offset or waypoints. Automatic label centers may change from corrected bounds as intended. Existing scene caching still avoids recomputation on unchanged hover/selection; shaping adds bounded work per retained line when routing actually occurs. No concrete performance blocker found in this bounded static review.
|
||||
|
||||
## Test assumptions and remaining evidence
|
||||
|
||||
The new State canvas fixed-point fixture is defensible from source, rather than adjusted to returned geometry: B at(760,180),224x94 has Bottom0.5 attachment(872,274); the shape inset is zero at the side midpoint. Fraction0 and offset(-432,-14) give center(440,260). A's single entry row occupies y244..270. Thus (440,260) exercises covered row ownership, (440,272) the summary over the State header, and (200,260) the exposed row. Moving both B/C by(0,320) moves the anchor/label center by the same amount with the original authored offset; no waypoints require translation. The assertions deliberately fail if these declared assumptions cease to hold.
|
||||
|
||||
Those tests cover synthetic dedicated-canvas interactions and cancelled group preview; they do not establish native glyph containment, generic-canvas targeting, actual peer-fixture pixels, settled-move equality, or save/reopen/Undo. Parent's focused/full tests and native acceptance still need to supply their assigned evidence. The transient syntax/duplicate-tail states observed while writers were active were corrected before this pinned review and are not open findings.
|
||||
|
||||
## Generic hit repair follow-up at 1f471288
|
||||
|
||||
Inspected the exact three-file commit diff and all230 lines of its new `diagram/state_label_input_tests.rs`; no source writes, compiler or product execution. `hit_state_label` now resolves only actually painted, nonempty State-plan rectangles in reverse stable paint order. Generic context targeting and ordinary click consult it before ports/nodes, while selected attachment/bend controls remain earlier. Selected label dragging requires that selected label to be the frontmost one; the lower selected label cannot steal the upper label's click. The existing nearest-center/non-State path stays intact.
|
||||
|
||||
Generic group input resolves a selected State attachment/bend to the exact Edge target before the label target and then passes through the existing Shift, singleton/nonmember, editability and mixed-group checks. It does not introduce the previously challenged unconditional handle fallthrough. For a selected Edge+Placement set, a handle press creates the existing unsupported group lifecycle and threshold release refuses; a singleton still falls through to the ordinary handle edit. Shift on the overlapped selected handle toggles that handle's Edge, while Shift away from handles toggles the front label. No global edge-first policy or non-State ordering change was introduced.
|
||||
|
||||
The added tests exercise the real generic Program path at three zooms/nonzero pan, plain/context selection above a node, lower-selected overlap, front-label drag, read-only selection, Shift label-versus-handle identity, selected bend continuation and mixed refusal. At half zoom the pointer is deliberately17 world units below the central bend, beyond its16-unit radius and still within the18-unit half-height; this is a valid independent separation of label and handle hit regions. Fixture setup uses routing only to author an offset placing both labels at the separately declared(200,150) target, not to choose the expected winning Edge. These tests remain unexecuted by this reviewer. Existing attachment tests still need their scheduled run; the new focused handle regression directly covers a bend rather than an attachment.
|
||||
@@ -204,3 +204,8 @@ are recorded in release-routing-verification.md. The measured release full solve
|
||||
is 119 ms, so the initial settled target is not claimed as passed. Current Linux
|
||||
verification and broader release acceptance remain separate from the completed
|
||||
routing workflows.
|
||||
|
||||
Dedicated State transition title/summary layout, measured desktop label bounds
|
||||
and painted-label hit ownership are specified in
|
||||
[the State label layout contract](state-label-layout-contract.md). This derived
|
||||
presentation follow-up does not change authored routing metadata.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# State transition label layout
|
||||
|
||||
Derived presentation contract for [issue #36](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/36), based on source `715036e132677dd2bc2fb37ed316d93dc9987aec`. This follow-up is separate from the frozen September 24 prerelease. Implementation at `70bdcdf8fec1728e971cd90ad7f79a1df99777ff` has [scoped Windows test/build and native Save/reopen evidence](experiments/state-label-layout-20260924/README.md), including an explicit restored-window input limitation. Integration status belongs to the issue.
|
||||
|
||||
## Scope and decision
|
||||
|
||||
The reproduced external transition title `Ready\nfor review` occupies two lines, while the old painter reserved only one line before its summary `External · Completion [request_ready]`. Routing sized the box from the summary alone. The correction is one derived title/summary layout on routed labels in dedicated State projections. Other projections, State headers, action rows and internal-transition rows retain their existing layout.
|
||||
|
||||
The layout is prepared before label placement, so painting, collision avoidance, hit bounds, focus, previews and SVG share it. Core accepts a line-width measurement callback without a font dependency. Desktop consumers use the same Iced Paragraph font, size, shaping and line-height configuration as their canvas text. Core-only callers retain an explicitly approximate deterministic width fallback. No semantic model or serialized layout field is introduced.
|
||||
|
||||
Flattening line breaks would lose the authored presentation. Enlarging only the paint background would leave focus and selection inconsistent. A scalar-width estimate is insufficient for a width-dominating title such as 40 `W` characters. Dynamic node/row layout is outside this reproduced defect and would change unrelated State placement and group-editing contracts.
|
||||
|
||||
## Excerpts and display lines
|
||||
|
||||
Keep the existing summary excerpt: at most 48 Unicode scalars of the original summary, taking 47 and an ellipsis when longer. `EdgeLabelGeometry.text` retains this raw excerpt. Typed usage visibility compares this original prefix with the original summary; normalization must never change the field offsets or expose text after the cutoff.
|
||||
|
||||
For compatibility, the title budget is fixed from the old summary width, before new measurement: `floor((7*N+6)/6)`, where `N` is the retained raw summary's scalar count. Apply the existing whole-prefix-and-ellipsis rule to the raw title. This is a bounded excerpt, not a promise to retain breaks beyond its cutoff.
|
||||
|
||||
In each retained excerpt, CRLF is one display break; LF, bare CR, NEL, Unicode line separator and paragraph separator are also breaks. Tabs display as four spaces. These transformations do not modify source or raw usage provenance. Empty blocks have zero lines. Nonempty trailing and blank lines are preserved: `A\n` and `\n` each have two cells. A CR at an excerpt boundary is a break even if its LF was truncated.
|
||||
|
||||
Each line uses font size 10 and an explicit 12-unit line cell, without wrapping. The summary begins after all title cells. Width is the maximum measured displayed-line advance plus five units of padding on each side. Height is at least 24 units and otherwise 12 times the total line count. Lines start at `(5, 12*index)` relative to the shared box. Authored fraction, offset, center, attachments and waypoints remain unchanged. Automatic label positions may change because their obstacles are now accurate.
|
||||
|
||||
## Hit ownership and consumers
|
||||
|
||||
The dedicated State canvas paints opaque label backgrounds after nodes and rows. Its ordinary hit test therefore checks those same rectangles before nodes/rows, in reverse label paint order. Padding and blank cells belong to the rectangle. Existing selected endpoint/bend handling remains earlier; path-distance ties, group admission and `(placement, relation)` row identities remain unchanged. Exposed adjacent row pixels still select that exact row. This deliberately does not model every crossing route-stroke pixel.
|
||||
|
||||
Scene routing, edge/group previews, usage/focus queries, generic canvas paths receiving a State plan and SVG all consume the measured plan. SVG emits explicit positions for each line and the same font-size declaration; exact appearance under an external viewer's substituted font is not guaranteed.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Exact peer fixture: two title cells followed by one summary cell, readable and selectable at its last line, with full Project/source conservation.
|
||||
- Bounds falsifiers: 40 `W` title characters and a fallback/combining-text case, plus blank lines and separators. Inspect actual native glyph containment; Paragraph advance alone is not a universal ink-overhang proof.
|
||||
- Raw-prefix provenance across CRLF and multibyte text, typed fields at/after the cutoff, and duplicate names in unrelated prose.
|
||||
- Label over node selects the Edge; adjacent exposed row keeps its StateRow identity; overlapping labels choose the last painted. Shift selection, mixed-group refusal and existing handles keep their contracts.
|
||||
- Preview and settled scene bounds agree after movement; nonzero pan/zoom has no stale hit rectangle. Save/reopen and Undo/Redo preserve the independently declared source/model oracles.
|
||||
|
||||
Use meaningful automated checks and the actual Windows GUI. Linux is excluded by the user's current instructions. Publish verification and limitations with the implementation before closing #36.
|
||||
Reference in New Issue
Block a user