57f2a4a958e5a6432829e33deaaf7b0475971188
braney
  Thu Sep 17 09:52:34 2026 -0700
quickLift: keep the target strand a reverse complemented protein just got

quickLiftPslBackToProtein turns the alignment over when pslTransMap hands back
strand[0] == '-', because a protein psl is only ever "++" or "+-".  pslRc does
that and makes the target strand explicit as it goes, so the psl comes out "+-".
The assignment at the end of the function then set strand[1] to '+' regardless
and undid it, leaving "++" over blocks that are in minus strand target
coordinates.

pslIsProtein wants strand[1] plus the three to one relation between the last
block and the target end, so it answered no.  pslTrack.c then passed a size
multiplier of one to lfFromPslx and every block was drawn at a third of its
length at a mirrored position, which is the symptom the reverse complement was
added to prevent.  The assignment is now the else branch of the same test.

hg/lib/tests/quickLiftTester.c covers this.  It goes through the public
quickLiftPsl rather than the static function, builds its chains in memory in the
shape quickLiftSourceRanges leaves them, and needs no database.  Six cases: a
protein over a same strand chain and over an opposite strand chain, a chain that
gaps only the reference, a chain that drops a source base and so splits a codon,
an mRNA over both chains, and an alignment with no chain under it.  Each one runs
pslCheck2, which is what notices a strand that disagrees with the blocks.  With
this fix backed out, only the opposite strand case changes and it reports three
errors placing the blocks outside the target range.

Found in the v504 code review, refs #38349.

refs #38249

diff --git src/hg/lib/tests/expected/quickLiftTest src/hg/lib/tests/expected/quickLiftTest
new file mode 100644
index 00000000000..317c5d94a18
--- /dev/null
+++ src/hg/lib/tests/expected/quickLiftTest
@@ -0,0 +1,49 @@
+== protein, chain on the same strand
+chain: t=chrSrc:100-400 q=chrRef:100-400 qStrand=+ blocks 100-400/100-400
+before   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrSrc	1000	100	400	1	100,	0,	100,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+lifted   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrRef	1000	100	400	1	100,	0,	100,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+
+== protein, chain on the opposite strand
+chain: t=chrSrc:600-900 q=chrRef:600-900 qStrand=- blocks 600-900/600-900
+before   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrSrc	1000	600	900	1	100,	0,	600,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+lifted   100	0	0	0	0	0	0	0	+-	prot	100	0	100	chrRef	1000	100	400	1	100,	0,	600,
+         strand='+-' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+
+== protein, chain gaps the reference
+chain: t=chrSrc:100-400 q=chrRef:100-401 qStrand=+ blocks 100-250/100-250 250-400/251-401
+before   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrSrc	1000	100	400	1	100,	0,	100,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+lifted   100	0	0	0	0	0	1	1	++	prot	100	0	100	chrRef	1000	100	401	2	50,50,	0,50,	100,251,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=50 pslCheck errors=0
+
+== protein, lift splits a codon
+chain: t=chrSrc:100-401 q=chrRef:100-400 qStrand=+ blocks 100-250/100-250 251-401/250-400
+before   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrSrc	1000	100	400	1	100,	0,	100,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+lifted   299	0	0	0	1	1	0	0	+	prot	300	0	300	chrRef	1000	100	399	2	150,149,	0,151,	100,250,
+         strand='+' isProtein=0 qSize=300 blockSizes[0]=150 pslCheck errors=0
+
+== mRNA, chain on the same strand
+chain: t=chrSrc:100-400 q=chrRef:100-400 qStrand=+ blocks 100-400/100-400
+before   300	0	0	0	0	0	0	0	+	mrna	300	0	300	chrSrc	1000	100	400	1	300,	0,	100,
+         strand='+' isProtein=0 qSize=300 blockSizes[0]=300 pslCheck errors=0
+lifted   300	0	0	0	0	0	0	0	+	mrna	300	0	300	chrRef	1000	100	400	1	300,	0,	100,
+         strand='+' isProtein=0 qSize=300 blockSizes[0]=300 pslCheck errors=0
+
+== mRNA, chain on the opposite strand
+chain: t=chrSrc:600-900 q=chrRef:600-900 qStrand=- blocks 600-900/600-900
+before   300	0	0	0	0	0	0	0	+	mrna	300	0	300	chrSrc	1000	600	900	1	300,	0,	600,
+         strand='+' isProtein=0 qSize=300 blockSizes[0]=300 pslCheck errors=0
+lifted   300	0	0	0	0	0	0	0	-	mrna	300	0	300	chrRef	1000	100	400	1	300,	0,	100,
+         strand='-' isProtein=0 qSize=300 blockSizes[0]=300 pslCheck errors=0
+
+== no chain over the alignment
+chain: t=chrSrc:100-400 q=chrRef:100-400 qStrand=+ blocks 100-400/100-400
+before   100	0	0	0	0	0	0	0	++	prot	100	0	100	chrSrc	1000	700	1000	1	100,	0,	700,
+         strand='++' isProtein=1 qSize=100 blockSizes[0]=100 pslCheck errors=0
+lifted   did not lift
+
+passed