544f7054d812254a0883a412b98a08dcd78f1b84
braney
  Fri Sep 4 11:39:41 2026 -0700
hgTracks: draw quickLifted psl and bigPsl tracks

pslTrack.c had no quickLift path at all.  It now reads the alignments out of the
assembly the track came from through quickLiftSql and maps them onto the
reference.  The chromFilter clause and the sort-and-filter tail moved into
helpers so the two loaders cannot drift apart.

bigBedTrack.c built the alignment out of the source interval but labelled it with
the reference sequence name and never lifted it, so a quickLifted bigPsl track
drew its items in the wrong place.  It now takes the real source name out of the
bigBed, lifts, counts a failure the way the bed path does, and rewrites the
coordinate fields so a $chromStart in a mouseOver reports where the item is drawn.

cds.c asked the assembly on screen for the CDS and for the sequence the alignment
is to.  Both belong to the assembly the alignment came from.  This was already
the wrong answer for a quickLifted track on any target, and on a hub-backed
target it fails outright, because the name of such an assembly is not a database.
The table-exists answer is now remembered per assembly rather than once for the
process, since one page can hold both native and lifted alignment tracks.

The mRNA filter reads the same source assembly, for the same reason: the
accessions and the gbCdnaInfo ids are the source assembly's.

refs #38249

diff --git src/hg/hgTracks/bigBedTrack.c src/hg/hgTracks/bigBedTrack.c
index 12d645ce302..c5cdac79997 100644
--- src/hg/hgTracks/bigBedTrack.c
+++ src/hg/hgTracks/bigBedTrack.c
@@ -12,30 +12,31 @@
 #include "hdb.h"
 #include "bedCart.h"
 #include "hgTracks.h"
 #include "hmmstats.h"
 #include "localmem.h"
 #include "wigCommon.h"
 #include "bbiFile.h"
 #include "obscure.h"
 #include "bigWig.h"
 #include "bigBed.h"
 #include "bigWarn.h"
 #include "errCatch.h"
 #include "trackHub.h"
 #include "net.h"
 #include "bigPsl.h"
+#include "bPlusTree.h"    // for the chrom-name key size of the bigBed we are reading
 #include "bigBedFilter.h"
 #include "bigBedLabel.h"
 #include "variation.h"
 #include "chromAlias.h"
 #include "quickLift.h"
 #include "hgConfig.h"
 #include "heatmap.h"
 
 static unsigned getFieldNum(struct bbiFile *bbi, char *field)
 // get field number for field name in bigBed.  errAbort if field not found.
 {
 int fieldNum =  bbFieldIndex(bbi, field);
 if (fieldNum < 0)
     fieldNum = defaultFieldLocation(field);
 if (fieldNum < 0)
@@ -786,67 +787,106 @@
     if (mouseOverPattern)
         {
         AllocArray(fieldNames, bbi->fieldCount);
         struct slName *field = NULL, *fields = bbFieldNames(bbi);
         int i =  0;
         for (field = fields; field != NULL; field = field->next)
             fieldNames[i++] = field->name;
         }
     }
 
 // a fake item that is the union of the items that span the current  window
 struct linkedFeatures *spannedLf = NULL;
 unsigned filtered = 0;
 unsigned notLifted = 0;
 struct bed *bed = NULL, *bedCopy = NULL;
+struct hash *mapPsls = NULL;     // mapping alignments quickLift reuses across items
+int lastChromId = -1;
+char otherChrom[bbi->chromBpt->keySize+1];
+char startBuf[16], endBuf[16];
 for (bb = bbList; bb != NULL; bb = bb->next)
     {
     struct linkedFeatures *lf = NULL;
     // an item that passed the filters but has no clean mapping through the chain was
     // dropped by the lift, not by a filter, and needs to be reported in its own words
     boolean liftFailed = FALSE;
     bedCopy = NULL;
     char *bedRow[bbi->fieldCount];
     if (sameString(track->tdb->type, "bigPsl"))
         {
         // fill out bedRow to support mouseOver pattern replacements
-        char startBuf[16], endBuf[16];
         bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
         char *seq, *cds;
-        struct psl *psl = pslFromBigPsl(chromName, bb, seqTypeField,  &seq, &cds);
-        int sizeMul =  pslIsProtein(psl) ? 3 : 1;
+        // Under quickLift the interval came out of the other assembly's file, so the
+        // alignment has to carry that assembly's sequence name:  chromName is the name on
+        // the reference, and the chains are looked up by the name on the other side.
+        char *pslChrom = chromName;
+        if (quickLiftFile)
+            {
+            bbiCachedChromLookup(bbi, bb->chromId, lastChromId, otherChrom, sizeof otherChrom);
+            lastChromId = bb->chromId;
+            pslChrom = otherChrom;
+            }
+        struct psl *psl = pslFromBigPsl(pslChrom, bb, seqTypeField,  &seq, &cds);
+        boolean isProt = pslIsProtein(psl);
         boolean isXeno = 0;  // just affects grayIx
         boolean nameGetsPos = FALSE; // we want the name to stay the name
 
-        if (sizeMul == 3)
+        if (isProt)
             {
             // these tags are not currently supported by the drawing engine for protein psl
             hashRemove(track->tdb->settingsHash, "showDiffBasesAllScales");
             hashRemove(track->tdb->settingsHash, "baseColorUseSequence");
             hashRemove(track->tdb->settingsHash, "baseColorDefault");
             }
 
+        if (quickLiftFile)
+            {
+            // Move the alignment's target side onto the reference.  Ask the lifted
+            // alignment about its own block sizes rather than reusing isProt:  the lift
+            // puts a protein alignment into nucleotide space on the way through.
+            struct psl *lifted = quickLiftPsl(chainHash, &mapPsls, psl);
+            pslFree(&psl);
+            psl = lifted;
+            }
+
+        if (psl == NULL)
+            liftFailed = TRUE;
+        else
+            {
+            if (quickLiftFile)
+                {
+                // bedRow still holds the pre-lift position, and a $chrom or $chromStart in
+                // a mouseOver has to report where the item is drawn.
+                safef(startBuf, sizeof startBuf, "%d", psl->tStart);
+                safef(endBuf, sizeof endBuf, "%d", psl->tEnd);
+                bedRow[0] = psl->tName;
+                bedRow[1] = startBuf;
+                bedRow[2] = endBuf;
+                }
+            int sizeMul = pslIsProtein(psl) ? 3 : 1;
             lf = lfFromPslx(psl, sizeMul, isXeno, nameGetsPos, track);
             lf->original = psl;
             if ((seq != NULL) && (lf->orientation == -1))
                 reverseComplement(seq, strlen(seq));
             lf->extra = seq;
             lf->cds = cds;
             lf->useItemRgb = useItemRgb;
             if ( lf->useItemRgb )
                 lf->filterColor = itemRgbColumn(bedRow[8]);
             }
+        }
     else if (sameString(tdb->type, "bigDbSnp"))
         {
         // bigDbSnp does not have a score field, but I want to compute the freqSourceIx from
         // trackDb and settings one time instead of for each item, so I'm overloading scoreMin.
         int freqSourceIx = scoreMin;
         lf = lfFromBigDbSnp(tdb, bb, filters, freqSourceIx, bbi, chainHash);
         }
     else
 	{
         char startBuf[16], endBuf[16];
         bigBedIntervalToRow(bb, chromName, startBuf, endBuf, bedRow, ArraySize(bedRow));
         if (bigBedFilterInterval(bbi, bedRow, filters))
             {
             if (quickLiftFile)
                 {