Address review: qRound-consistent guards, regression test

Addresses scorpio810's review of PR #890 (bugtracker #734):

- The four cas "3"/"4" bridge-skip guards now compare qRound()ed
  coordinates, matching how the bridge coordinate itself is computed --
  an exact != would miss a pair already grid-equal after rounding but
  off by a sub-pixel remainder, and still route a degenerate bridge for
  it. Verified no behavior change on the shipped corpus: per-file
  self-retrace counts are identical before/after (every coordinate
  there already lands exactly on-grid).
- Added tests/qttest/tst_conductorselfretrace.cpp, fixture
  qet_bug_repro_resaved.qet (the report's own canonical reproduction):
  exports it via the built binary's --export-svg and asserts no
  conductor path is self-retracing. Confirmed it actually catches the
  regression, not just passes vacuously -- reverted conductor.cpp to
  master and reran: fails, 1 self-retracing path found.

Not changed in code: the cas "4" descending-branch dead-code note (kept
for symmetry, as already agreed) and the schema_unifilaire_voltaique2.qet
trade-off (flagged for the reviewer's own visual check).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-17 05:08:14 +12:00
parent abd5e8978a
commit 2ed85f6fc7
4 changed files with 1145 additions and 6 deletions
+11 -6
View File
@@ -426,8 +426,12 @@ void Conductor::generateConductorPath(const QPointF &p1, Qet::Orientation o1, co
if ((ori_depart == Qet::North && (ori_arrivee == Qet::South || ori_arrivee == Qet::West)) || (ori_depart == Qet::East && ori_arrivee == Qet::West)) {
// cas "3": bridge is vertical, at a shared x between depart.y()
// and arrivee.y() -- unnecessary exactly when depart and
// arrivee already share an x.
if (depart.x() != arrivee.x()) {
// arrivee already share an x. Compared qRound()ed, the same
// rounding the bridge coordinate itself is computed with below:
// an exact != would miss a pair that's already grid-equal after
// rounding but off by a sub-pixel remainder, and still route a
// degenerate bridge for it.
if (qRound(depart.x()) != qRound(arrivee.x())) {
int ligne_inter_x = qRound(depart.x() + arrivee.x()) / 2;
while (ligne_inter_x % Diagram::xGrid) -- ligne_inter_x;
points << QPointF(ligne_inter_x, depart.y());
@@ -439,7 +443,7 @@ void Conductor::generateConductorPath(const QPointF &p1, Qet::Orientation o1, co
// and arrivee already share a y. (Always true in this branch,
// since "descendant" requires depart.y() < arrivee.y() strictly
// -- kept for symmetry with the "montant" branch below.)
if (depart.y() != arrivee.y()) {
if (qRound(depart.y()) != qRound(arrivee.y())) {
int ligne_inter_y = qRound(depart.y() + arrivee.y()) / 2;
while (ligne_inter_y % Diagram::yGrid) -- ligne_inter_y;
points << QPointF(depart.x(), ligne_inter_y);
@@ -457,8 +461,9 @@ void Conductor::generateConductorPath(const QPointF &p1, Qet::Orientation o1, co
// depart.x() and arrivee.x() -- unnecessary exactly when depart
// and arrivee already share a y (the West->East case
// diagnosed for #734: two stubs extended onto the same y run
// straight into each other, no bridge needed).
if (depart.y() != arrivee.y()) {
// straight into each other, no bridge needed). Compared
// qRound()ed, same as the other three guards.
if (qRound(depart.y()) != qRound(arrivee.y())) {
int ligne_inter_y = qRound(depart.y() + arrivee.y()) / 2;
while (ligne_inter_y % Diagram::yGrid) -- ligne_inter_y;
points << QPointF(depart.x(), ligne_inter_y);
@@ -468,7 +473,7 @@ void Conductor::generateConductorPath(const QPointF &p1, Qet::Orientation o1, co
// cas "4": bridge is vertical, at a shared x between depart.y()
// and arrivee.y() -- unnecessary exactly when depart and
// arrivee already share an x.
if (depart.x() != arrivee.x()) {
if (qRound(depart.x()) != qRound(arrivee.x())) {
int ligne_inter_x = qRound(depart.x() + arrivee.x()) / 2;
while (ligne_inter_x % Diagram::xGrid) -- ligne_inter_x;
points << QPointF(ligne_inter_x, depart.y());