7bed537d83b701bb4284080c2b634f1069417cae
braney
  Thu Jun 18 12:07:03 2026 -0700
fix squishyPack track stealing a neighboring track's center label when image orders collide

A squishyPack track is split into a full-height part and a squished part
that are stitched back together by smashSquish(), which assumes the squished
clone is drawn immediately after its source. flatTracksCmp() broke an
image-order tie involving a squink track purely by visibility, even against an
unrelated track sharing the same order, so a colliding track (e.g. MANE vs
GENCODE V49) could sort between the source and its squink. smashSquish() then
merged the squished slice onto the wrong row, dragging the neighbor's center
label with it. Keep a squink adjacent to its own source in flatTracksCmp(), and
guard smashSquish() so it only merges a linked neighbor cloned from that track,
refs #37785

diff --git src/hg/hgTracks/imageV2.c src/hg/hgTracks/imageV2.c
index 5f95146aeae..82ad780d229 100644
--- src/hg/hgTracks/imageV2.c
+++ src/hg/hgTracks/imageV2.c
@@ -55,32 +55,39 @@
         flatTrack->order = topOrder + index + 1;
     else
         flatTrack->order = ++lastOrder;
     }
 
 slAddHead(flatTracks,flatTrack);
 }
 
 int flatTracksCmp(const void *va, const void *vb)
 // Compare to sort on flatTrack->order
 {
 const struct flatTracks *a = *((struct flatTracks **)va);
 const struct flatTracks *b = *((struct flatTracks **)vb);
 if (a->order == b->order)
     {
-    if ((a->track->originalTrack != NULL) || (b->track->originalTrack != NULL))
-        return a->track->visibility - b->track->visibility;
+    // A squishyPack "squink" track (originalTrack set) shares its source track's order and
+    // must sort immediately after that source so the two can be smashed back together (see
+    // smashSquish).  Use visibility to order the genuine source/squink pair, but a squink
+    // compared against an unrelated track that happens to share the same order must fall
+    // through to priority - since the squink carries its source's priority, that keeps it
+    // grouped with its source instead of being separated by the other track.
+    struct track *at = a->track, *bt = b->track;
+    if (sameOk(at->originalTrack, bt->track) || sameOk(bt->originalTrack, at->track))
+        return at->visibility - bt->visibility;
     return tgCmpPriority(&(a->track),&(b->track));
     }
 return (a->order - b->order);
 }
 
 void flatTracksSort(struct flatTracks **flatTracks)
 // This routine sorts the imgTracks then forces tight ordering, so new tracks wil go to the end
 {
 // flatTracks list has 2 sets of "order": those already dragReordered (below IMG_ORDERTOP)
 // and those not yet reordered (above).  Within those not yet dragReordered are 2 sets:
 // Those that begin numbering at IMG_ORDERTOP and those that begin at IMG_ORDEREND.
 // This routine must determine if there are any already dragOrdered, and if so, position the
 // newcomers in place.  Newly appearing customTracks will appear at top, while newly appearing
 // standard tracks appear at the end of the image.
 int haveBeenOrderd = 0, imgOrdHighest=0; // Keep track of reordered count and position
@@ -2071,31 +2078,36 @@
     hPrintf("</A>");
 
 if (slice->parentImg)
     hPrintf("</div>");
 }
 
 struct imgTrack *smashSquish(struct imgTrack *imgTrackList)
 /* Javascript doesn't know about our trick to do squishyPack so we need to pass it a single div instead of two. 
  * We assume that the linked track immediately follows the original track in the sorted list. */
 {
 struct imgTrack *nextImg, *imgTrack;
 
 for (imgTrack = imgTrackList; imgTrack!=NULL;imgTrack = nextImg)
     {
     nextImg = imgTrack->next;
-    boolean joinNext =  ((nextImg != NULL)  && nextImg->linked);
+    // Only smash a squink into the track it was cloned from.  nextImg->linked is set for any
+    // squishyPack squink, but if an unrelated track sorted between a squink and its source we
+    // must not merge the squink's slice into the wrong neighbor (its pixels are elsewhere).
+    boolean joinNext =  ((nextImg != NULL)  && nextImg->linked
+                         && nextImg->tdb != NULL && imgTrack->tdb != NULL
+                         && sameOk(nextImg->tdb->originalTrack, imgTrack->tdb->track));
 
     if (joinNext)  // Smash these together
         {
         struct imgSlice *packedSlices = imgTrack->slices;
         struct imgSlice *squishSlices = imgTrack->next->slices;
         
         if ((packedSlices == NULL) || (squishSlices == NULL) ||
            (slCount(packedSlices) != slCount(squishSlices)))
            continue;
         for(; packedSlices && squishSlices; packedSlices = packedSlices->next, squishSlices = squishSlices->next)
             {
             if (packedSlices->type != stCenter)
                 {
                 if ((packedSlices->map != NULL) && (squishSlices->map != NULL))
                     packedSlices->map->items = slCat(packedSlices->map->items, squishSlices->map->items);