about summary refs log blame commit diff stats
path: root/shell/trace.mu
blob: e4c9ec5e45d32a09783ff1735429e1a81f0f0078 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                 







                                                                        

            
                


                                                                      
                                                  
 



                                                      
                             

                                                                 
                                  
                              


                                                                         
                                          
                                    
                           
                                           
                                      
                                                                                    

                                                                                                   
                                                       





                            
                   

 










                                                                                     

                    
                                                                                               
                                          
                 



                      



                                                 
                                          

                                                                    

                                                                         
                       



                                          
                 



                      

                                                             

                                                 
                                           

 
                                                      
                                          
                 
   
               
                      
   




                                                                    
   



                                                                  

                                                       
     
                 



                   
   


                

















                                                        
                                                                                     
                                          
                 









                                                      


                                                                   




                                     



                                                                   
                       
   
                                     
              





                                                                                 
                                                                               



                                                         

                                                    

 
                                                                                  
                 



                      





                                                          


                                                          



                      


                                                          
                                   
                                   

 













                                                                  











                                                                                                                  


                                    
                                          
                 



                      

                                                   


                                     
                                          
                 



                      

                                                   

 

                  














                                                                                                                           
                                                                                        




















                                                                                                                           
                                                                                        










                                                                                                         



































                                                                                                                         






























                                                                                      
                                   




                                          
                      












                                                                    
                                                                                              






                                                                        




                                          
                      






                                                                    
                                

                  
                                  

                                                                    

                                                                        


                                                               
                                                                                              





                  



                                          
                                                            




                                          
                                                            


                        
                                                                                                                                             
                                    
                                          


                 
                      
   
                             




                                                                        
                

                                                    




                                   


                                    



                                                                    

                                                      















                                                                                                                        

                                                               
                       

                  

                   
                         

                                                                    

                                                                        



                                 


                                   


                                                          

                                                  

                                                                            

                             
       


                                                         
                                                                                                


                                              
                              
                                                      


                                 
                                                                           
                                                                                             



                                              



                                            
                                                                                                      


                                             
     
                  

        
                                          
                                                                
          
 
 
                                                                       
                                                            

















                                                                                                                                                                    


                                               


                                                
   
              

                                
                                                                                                                
                                                                                                  
                                                   






                                                             









                                                                                                 



                








                                                              
                                          




                                                                    
   






                                                                  
   



                                                                       
















                                                                              
            






                                                    

 








                                                     
                                                                                     
                                             







                                                                                                                           




                                                    
                      











                                                                                       
                                                         


                             
                                                                                                                             




                                                                                                                                   

 


                                              
                                                 


                                                          
                                                                  
   
                                                                                       

                                                             






                                                                                                         
                                                 


                                                          
                                                                  










                                                                                                               
                                                 


                                                          
                                                                  







                                                                                                                                 




                                              
                                                 
                           


                                                          
                                                                  
   
                                                                                       







                                                                                    
                                                 



                                                          
                                                                    
   
                                                                                         







                                                                      
                                                 
   
                  
                           


                                                          
                                                                    
   
                                                                                         








                                                                                 
                                                 

                           



                                                          
                                                                    
   
                                                                                         








                                                                               
                                                 

                             
                  
                             


                                                          
                                                                    
   
                                                                                         





                                                                                      
 


                                              
                                                 

                             
                  
                             


                                                          
                                                                    










                                                                                                                                 





                                            


                                                           
                                    
                                                                                     
                                                                                     
                                                                                     
                                                                                  
                                                                            
                                                                                        
                                                                                             
                                                                                            
                                                                                     
                                                                                            

 
                                                         


                                          



                                                      

                                                                                

          




                                                      

                                                                                

          

             



                                                      

                                                                                

          




                                                      

                                                                                

          
                  





                            






                            







                                                                                













                         











                                                                                            

                                                                 
                                                 


                                        
                                                                                                     
          






















                                                                                 

















                                                                                            
                                                    

                                                      
                    


                                                                            
                                                                         




                                                                 






                                                                           




                                                 
                    







                                                                            








                                                                           



























                                                                         

 





















                                                                         




























                                                                                            


                                              
                                                 

                             
                  
                             


                                                          
                                                                    









                                                                                                                                  
                      








                                                                                                                                   
                      







                                                                                                                                 
 



                                              
                                                 

                             
                  
                             


                                                          
                                                                    









                                                                                                                                   




                      
                                                                                                   
                                                                                           
                           




                                                                                                                                    
                                                                                                                                    
 
 
                             

                                              
                                                 





                                                          
                                                                     


                                                                                            



                                                                                                                         



                                                                                            
                                                                                                                     
                                                                                                                            
                                                                                                                     


                                                                                                                            

 
                                        

                                              
                                                 






                                                          
                                                                     


                                                                                            



                                                                                                                                    



                                                                                            
                                                                                                                                




                                                                                                                                       

 
                                                 

                                              
                                                 








                                                          
                                                                     


                                                                                            



                                                                                                                                             



                                                                                            
                                                                                                                                         
                                                                                                                                                
                                                                          

                                                                                                                                                
                                                                                                                                         
                                                                                                                                                

 
                                            

                                              
                                                 
   
               











                                                          
                                                                     


                                                                                            



                                                                                                                                          



                                                                                            
                                                                                                                                      


                                                                                                                                             
                                                                                                                                      




                                                                                                                                             

 
                            

                                              
                                                 








                                                          
                                                                     


                                                                                            



                                                                                                                          



                                                                                            
                                                                                                                      


                                                                                                                             
                                                                                                                      
                                                                                                                             
               
                      


                                                                                                     
                                                                                                                    


                                                                                                                           
                                                                                                                    
                                                                                                                           



                                                                                            
                                                                                                                       
                                                                                                                              
                                                                                                                       
                                                                                                                              
                                                                                                                       
                                                                                                                              

 
                              

                                              
                                                 






                                                          
                                                                     


                                                                                            



                                                                                                                            



                                                                                            
                                                                                                                        
                                                                                                                               
                                                                                                                        
                                                                                                                               
                                                                                                                        
                                                                                                                               
               

                      

                                                                                            
                                                                                                                      
                                                                                                                             
                                                                                                                      
                                                                                                                             
                                                                                                                      
                                                                                                                             






                                                                                            
                                                                                                                         
                                                                                                                                
                                                                                                                         
                                                                                                                                
                                                                                                                         
                                                                                                                                

 
                                          

                                              
                                                 






                                                          
                                                                     


                                                                                            



                                                                                                                                        



                                                                                            
                                                                                                                                    
                                                                                                                                           
                                                                                                                                    
                                                                                                                                           
                                                                                                                                    
                                                                                                                                              
               

                      

                                                                                            
                                                                                                                                  
                                                                                                                                         
                                                                                                                                  
                                                                                                                                         
                                                                                                                                  
                                                                                                                                         






                                                                                            
                                                                                                                                     
                                                                                                                                            
                                                                                                                                     
                                                                                                                                            
                                                                                                                                     
                                                                                                                                            
 
 


                                              
                                                 






                                                          
                                                                     










                                                                                                                                            
                                                                                                                                        
                                                                                                                                               
                                                                                                                                        
                                                                                                                                               
                                                                                                                                        

                                                                                                                                                  

                      

                                                                                            
                                                                                                                                      
                                                                                                                                             
                                                                                                                                      
                                                                                                                                             
                                                                                                                                      

















                                                                                                                                                


                                              
                                                 








                                                          
                                                                     


                                                                                            



                                                                                                                             



                                                                                            
                                                                                                                         


                                                                                                                                
                                                                                                                         
                                                                                                                                





                                                                                                     
                                                                  



                                                                                                                              
 



                                              
                                                 





                                                          
                                                                     


                                                                                            



                                                                                                                      



                                                                                            
                                                                                                                  
                                                                                                                         
                                                                                                                  
                                                                                                                         
               
                      





                                                                                            
                                                           



                                                                                                                       




                                              
                                                 








                                                          
                                                                     


                                                                                            



                                                                                                                                            



                                                                                            
                                                                                                                                        


                                                                                                                                               
                                                                                                                                        
                                                                                                                                               
                                      
                      
                                                                                            
                      





                                                                                            


                                                                                                



                                                                                                                                             
 



                                              
                                                 








                                                          
                                                                     










                                                                                                                                 
                                                                                                                             


                                                                                                                                    
                                                                                                                             

                                                                                                                                    
                      




                                                                                            
                                                                                                                              
                                                                                                                                     
                                                                                                                              
                                                                                                                                     
                                                                                                                              

                                                                                                                                     
                      













                                                                                                                                  



                                              
                                                 












                                                          
                                                                     










                                                                                                                                   
                                                                                                                               


                                                                                                                                      
                                                                                                                               



                                                                                                                                      
                      
                                                                                            
                      
                                                                                            
                      




                                                                                            
                                                                                                                                


                                                                                                                                       
                                                                                                                                
                                                                                                                                       
                                                                                                                                
                                                                                                                                       
                                                                                                                                








                                                                                                                                       
                                                                                                                             


                                                                                                                                    
                                                                                                                             



                                                                                                                                    

                                    
                                          

                                                                                                        

                                                             




                                                                                      










                                                              



                                                                            
                                




                   
                           


                                  
                                          



                                                                                                        


                                                                                      
                    


                                   



                                    



                                                                                       
                                
     
                            

        

































                                                                                                          

































                                                                                                                    
                     












                                                                                                                       
                     











                                                                                                                    
                     








                                                                                                                      
























                                                                                                                      

                        
                     
                                                                                            
                                                                                                             
                                                                                                                    
                                                                                                             
                                                                                                                    
                                                                                                             
                                                                                                                    
                                                                                                             
                                                                                                                    











                                                                                                                    
 




























































































































                                                                                                                    
# A trace records the evolution of a computation.
# Traces are useful for:
#   error-handling
#   testing
#   auditing
#   debugging
#   learning
#
# An integral part of the Mu computer is facilities for browsing traces.

type trace {
  max-depth: int
  curr-depth: int  # depth that will be assigned to next line appended
  data: (handle array trace-line)
  first-free: int
  first-full: int  # used only by check-trace-scan

  # steady-state life cycle of a trace:
  #   reload loop:
  #     there are already some visible lines
  #     append a bunch of new trace lines to the trace
  #     recreate trace caches
  #     render loop:
  #       rendering displays trace lines that match visible lines
  #         (caching in each line)
  #         (caching top-line)
  #       rendering computes cursor-line based on the cursor-y coordinate
  #       edit-trace updates cursor-y coordinate
  #       edit-trace might add/remove lines to visible
  #       edit-trace might update top-line
  visible: (handle array trace-line)
  recreate-caches?: boolean
  cursor-line-index: int  # index into data
  cursor-y: int  # row index on screen
  unclip-cursor-line?: boolean  # extremely short-lived; reset any time cursor moves
  top-line-index: int  # start rendering trace past this index into data (updated on re-evaluation)
  top-line-y: int  # trace starts rendering at this row index on screen (updated on re-evaluation)
  screen-height: int  # initialized during render-trace
}

type trace-line {
  depth: int
  label: (handle array byte)
  data: (handle array byte)
  visible?: boolean
}

# when we recreate the trace this data structure will help stabilize our view into it
# we can shallowly copy handles because lines are not reused across reruns
type trace-index-stash {
  cursor-line-depth: int
  cursor-line-label: (handle array byte)
  cursor-line-data: (handle array byte)
  top-line-depth: int
  top-line-label: (handle array byte)
  top-line-data: (handle array byte)
}

## generating traces

fn initialize-trace _self: (addr trace), max-depth: int, capacity: int, visible-capacity: int {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var src/ecx: int <- copy max-depth
  var dest/eax: (addr int) <- get self, max-depth
  copy-to *dest, src
  dest <- get self, curr-depth
  copy-to *dest, 1  # 0 is the error depth
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  populate trace-ah, capacity
  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
  populate visible-ah, visible-capacity
  mark-lines-dirty self
}

fn clear-trace _self: (addr trace) {
  var self/eax: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var curr-depth-addr/ecx: (addr int) <- get self, curr-depth
  copy-to *curr-depth-addr, 1
  var len/edx: (addr int) <- get self, first-free
  copy-to *len, 0
  # leak: nested handles within trace-lines
}

fn has-errors? _self: (addr trace) -> _/eax: boolean {
  var self/eax: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var max/edx: (addr int) <- get self, first-free
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/esi: (addr array trace-line) <- copy _trace
  var i/ecx: int <- copy 0
  {
    compare i, *max
    break-if->=
    var offset/eax: (offset trace-line) <- compute-offset trace, i
    var curr/eax: (addr trace-line) <- index trace, offset
    var curr-depth-a/eax: (addr int) <- get curr, depth
    compare *curr-depth-a, 0/error
    {
      break-if-!=
      return 1/true
    }
    i <- increment
    loop
  }
  return 0/false
}

fn should-trace? _self: (addr trace) -> _/eax: boolean {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var depth-a/ecx: (addr int) <- get self, curr-depth
  var depth/ecx: int <- copy *depth-a
  var max-depth-a/eax: (addr int) <- get self, max-depth
  compare depth, *max-depth-a
  {
    break-if->=
    return 1/true
  }
  return 0/false
}

fn trace _self: (addr trace), label: (addr array byte), message: (addr stream byte) {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var should-trace?/eax: boolean <- should-trace? self
  compare should-trace?, 0/false
  {
    break-if-!=
    return
  }
  var data-ah/eax: (addr handle array trace-line) <- get self, data
  var data/eax: (addr array trace-line) <- lookup *data-ah
  var index-addr/edi: (addr int) <- get self, first-free
  {
    compare *index-addr, 0x8000/lines
    break-if-<
    return
  }
  var index/ecx: int <- copy *index-addr
  var offset/ecx: (offset trace-line) <- compute-offset data, index
  var dest/eax: (addr trace-line) <- index data, offset
  var depth/ecx: (addr int) <- get self, curr-depth
  rewind-stream message
  {
    compare *index-addr, 0x7fff/lines
    break-if-<
    clear-stream message
    write message, "No space left in trace\n"
    write message, "Please either:\n"
    write message, "  - find a smaller sub-computation to test,\n"
    write message, "  - allocate more space to the trace in initialize-sandbox\n"
    write message, "    (shell/sandbox.mu), or\n"
    write message, "  - move the computation to 'main' and run it using ctrl-r"
    initialize-trace-line 0/depth, "error", message, dest
    increment *index-addr
    return
  }
  initialize-trace-line *depth, label, message, dest
  increment *index-addr
}

fn trace-text self: (addr trace), label: (addr array byte), s: (addr array byte) {
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var data-storage: (stream byte 0x100)
  var data/eax: (addr stream byte) <- address data-storage
  write data, s
  trace self, label, data
}

fn error _self: (addr trace), message: (addr array byte) {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var curr-depth-a/eax: (addr int) <- get self, curr-depth
  var save-depth/ecx: int <- copy *curr-depth-a
  copy-to *curr-depth-a, 0/error
  trace-text self, "error", message
  copy-to *curr-depth-a, save-depth
}

fn error-stream _self: (addr trace), message: (addr stream byte) {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var curr-depth-a/eax: (addr int) <- get self, curr-depth
  var save-depth/ecx: int <- copy *curr-depth-a
  copy-to *curr-depth-a, 0/error
  trace self, "error", message
  copy-to *curr-depth-a, save-depth
}

fn initialize-trace-line depth: int, label: (addr array byte), data: (addr stream byte), _out: (addr trace-line) {
  var out/edi: (addr trace-line) <- copy _out
  # depth
  var src/eax: int <- copy depth
  var dest/ecx: (addr int) <- get out, depth
  copy-to *dest, src
  # label
  var dest/eax: (addr handle array byte) <- get out, label
  copy-array-object label, dest
  # data
  var dest/eax: (addr handle array byte) <- get out, data
  stream-to-array data, dest
}

fn trace-lower _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var depth/eax: (addr int) <- get self, curr-depth
  increment *depth
}

fn trace-higher _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var depth/eax: (addr int) <- get self, curr-depth
  decrement *depth
}

## checking traces

fn check-trace-scans-to self: (addr trace), label: (addr array byte), data: (addr array byte), message: (addr array byte) {
  var tmp/eax: boolean <- trace-scans-to? self, label, data
  check tmp, message
}

fn trace-scans-to? _self: (addr trace), label: (addr array byte), data: (addr array byte) -> _/eax: boolean {
  var self/esi: (addr trace) <- copy _self
  var start/eax: (addr int) <- get self, first-full
  var result/eax: boolean <- trace-contains? self, label, data, *start
  return result
}

fn test-trace-scans-to {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10/capacity, 0/visible  # we don't use trace UI
  #
  trace-text t, "label", "line 1"
  trace-text t, "label", "line 2"
  check-trace-scans-to t, "label", "line 1", "F - test-trace-scans-to/0"
  check-trace-scans-to t, "label", "line 2", "F - test-trace-scans-to/1"
  var tmp/eax: boolean <- trace-scans-to? t, "label", "line 1"
  check-not tmp, "F - test-trace-scans-to: fail on previously encountered lines"
  var tmp/eax: boolean <- trace-scans-to? t, "label", "line 3"
  check-not tmp, "F - test-trace-scans-to: fail on missing"
}

# scan trace from start
# resets previous scans
fn check-trace-contains self: (addr trace), label: (addr array byte), data: (addr array byte), message: (addr array byte) {
  var tmp/eax: boolean <- trace-contains? self, label, data, 0
  check tmp, message
}

fn test-trace-contains {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10/capacity, 0/visible  # we don't use trace UI
  #
  trace-text t, "label", "line 1"
  trace-text t, "label", "line 2"
  check-trace-contains t, "label", "line 1", "F - test-trace-contains/0"
  check-trace-contains t, "label", "line 2", "F - test-trace-contains/1"
  check-trace-contains t, "label", "line 1", "F - test-trace-contains: find previously encountered lines"
  var tmp/eax: boolean <- trace-contains? t, "label", "line 3", 0/start
  check-not tmp, "F - test-trace-contains: fail on missing"
}

# this is super-inefficient, string comparing every trace line
fn trace-contains? _self: (addr trace), label: (addr array byte), data: (addr array byte), start: int -> _/eax: boolean {
  var self/esi: (addr trace) <- copy _self
  var candidates-ah/eax: (addr handle array trace-line) <- get self, data
  var candidates/eax: (addr array trace-line) <- lookup *candidates-ah
  var i/ecx: int <- copy start
  var max/edx: (addr int) <- get self, first-free
  {
    compare i, *max
    break-if->=
    {
      var read-until-index/eax: (addr int) <- get self, first-full
      copy-to *read-until-index, i
    }
    {
      var curr-offset/ecx: (offset trace-line) <- compute-offset candidates, i
      var curr/ecx: (addr trace-line) <- index candidates, curr-offset
      # if curr->label does not match, return false
      var curr-label-ah/eax: (addr handle array byte) <- get curr, label
      var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
      var match?/eax: boolean <- string-equal? curr-label, label
      compare match?, 0/false
      break-if-=
      # if curr->data does not match, return false
      var curr-data-ah/eax: (addr handle array byte) <- get curr, data
      var curr-data/eax: (addr array byte) <- lookup *curr-data-ah
      var match?/eax: boolean <- string-equal? curr-data, data
      compare match?, 0/false
      break-if-=
      return 1/true
    }
    i <- increment
    loop
  }
  return 0/false
}

fn trace-lines-equal? _a: (addr trace-line), _b: (addr trace-line) -> _/eax: boolean {
  var a/esi: (addr trace-line) <- copy _a
  var b/edi: (addr trace-line) <- copy _b
  var a-depth/ecx: (addr int) <- get a, depth
  var b-depth/edx: (addr int) <- get b, depth
  var benchmark/eax: int <- copy *b-depth
  compare *a-depth, benchmark
  {
    break-if-=
    return 0/false
  }
  var a-label-ah/eax: (addr handle array byte) <- get a, label
  var _a-label/eax: (addr array byte) <- lookup *a-label-ah
  var a-label/ecx: (addr array byte) <- copy _a-label
  var b-label-ah/ebx: (addr handle array byte) <- get b, label
  var b-label/eax: (addr array byte) <- lookup *b-label-ah
  var label-match?/eax: boolean <- string-equal? a-label, b-label
  {
    compare label-match?, 0/false
    break-if-!=
    return 0/false
  }
  var a-data-ah/eax: (addr handle array byte) <- get a, data
  var _a-data/eax: (addr array byte) <- lookup *a-data-ah
  var a-data/ecx: (addr array byte) <- copy _a-data
  var b-data-ah/ebx: (addr handle array byte) <- get b, data
  var b-data/eax: (addr array byte) <- lookup *b-data-ah
  var data-match?/eax: boolean <- string-equal? a-data, b-data
  return data-match?
}

fn dump-trace _self: (addr trace) {
  var y/ecx: int <- copy 0
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var i/edx: int <- copy 0
  var max-addr/ebx: (addr int) <- get self, first-free
  var max/ebx: int <- copy *max-addr
  $dump-trace:loop: {
    compare i, max
    break-if->=
    $dump-trace:iter: {
      var offset/ebx: (offset trace-line) <- compute-offset trace, i
      var curr/ebx: (addr trace-line) <- index trace, offset
      y <- render-trace-line 0/screen, curr, 0, y, 0x80/width, 0x30/height, 7/fg, 0/bg, 0/clip
    }
    i <- increment
    loop
  }
}

fn dump-trace-with-label _self: (addr trace), label: (addr array byte) {
  var y/ecx: int <- copy 0
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var i/edx: int <- copy 0
  var max-addr/ebx: (addr int) <- get self, first-free
  var max/ebx: int <- copy *max-addr
  $dump-trace-with-label:loop: {
    compare i, max
    break-if->=
    $dump-trace-with-label:iter: {
      var offset/ebx: (offset trace-line) <- compute-offset trace, i
      var curr/ebx: (addr trace-line) <- index trace, offset
      var curr-label-ah/eax: (addr handle array byte) <- get curr, label
      var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
      var show?/eax: boolean <- string-equal? curr-label, label
      compare show?, 0/false
      break-if-=
      y <- render-trace-line 0/screen, curr, 0, y, 0x80/width, 0x30/height, 7/fg, 0/bg, 0/clip
    }
    i <- increment
    loop
  }
}

## UI stuff

fn mark-lines-dirty _self: (addr trace) {
  var self/eax: (addr trace) <- copy _self
  var dest/edx: (addr boolean) <- get self, recreate-caches?
  copy-to *dest, 1/true
}

fn mark-lines-clean _self: (addr trace) {
  var self/eax: (addr trace) <- copy _self
  var dest/edx: (addr boolean) <- get self, recreate-caches?
  copy-to *dest, 0/false
}

fn render-trace screen: (addr screen), _self: (addr trace), xmin: int, ymin: int, xmax: int, ymax: int, show-cursor?: boolean -> _/ecx: int {
  var already-hiding-lines?: boolean
  var self/esi: (addr trace) <- copy _self
  compare self, 0
  {
    break-if-!=
    abort "null trace"
  }
  var y/ecx: int <- copy ymin
  # recreate caches if necessary
  var recreate-caches?/eax: (addr boolean) <- get self, recreate-caches?
  compare *recreate-caches?, 0/false
  {
    break-if-=
    # cache ymin
    var dest/eax: (addr int) <- get self, top-line-y
    copy-to *dest, y
    # cache ymax
    var ymax/ecx: int <- copy ymax
    dest <- get self, screen-height
    copy-to *dest, ymax
    #
    recompute-all-visible-lines self
    mark-lines-clean self
  }
  clamp-cursor-to-top self, y
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var max-addr/ebx: (addr int) <- get self, first-free
  var max/ebx: int <- copy *max-addr
  # display trace depth (not in tests)
  $render-trace:render-depth: {
    compare max, 0
    break-if-<=
    var max-depth/edx: (addr int) <- get self, max-depth
    {
      var width/eax: int <- copy 0
      var height/ecx: int <- copy 0
      width, height <- screen-size screen
      compare width, 0x80
      break-if-< $render-trace:render-depth
    }
    set-cursor-position screen, 0x70/x, y
    draw-text-rightward-from-cursor-over-full-screen screen, "trace depth: ", 0x17/fg, 0xc5/bg=blue-bg
    draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen screen, *max-depth, 0x7/fg, 0xc5/bg=blue-bg
  }
  var top-line-addr/edx: (addr int) <- get self, top-line-index
  var i/edx: int <- copy *top-line-addr
  $render-trace:loop: {
    compare i, max
    break-if->=
    compare y, ymax
    break-if->=
    $render-trace:iter: {
      var offset/ebx: (offset trace-line) <- compute-offset trace, i
      var curr/ebx: (addr trace-line) <- index trace, offset
      var curr-label-ah/eax: (addr handle array byte) <- get curr, label
      var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
      var bg: int
      copy-to bg, 0xc5/bg=blue-bg
      var fg: int
      copy-to fg, 0x38/fg=trace
      compare show-cursor?, 0/false
      {
        break-if-=
        var cursor-y/eax: (addr int) <- get self, cursor-y
        compare *cursor-y, y
        break-if-!=
        copy-to bg, 7/trace-cursor-line-bg
        copy-to fg, 0x68/cursor-line-fg=sober-blue
        var cursor-line-index/eax: (addr int) <- get self, cursor-line-index
        copy-to *cursor-line-index, i
      }
      # always display errors
      {
        var curr-depth/eax: (addr int) <- get curr, depth
        compare *curr-depth, 0/error
        break-if-!=
        y <- render-trace-line screen, curr, xmin, y, xmax, ymax, 0xc/fg=trace-error, bg, 0/clip
        copy-to already-hiding-lines?, 0/false
        break $render-trace:iter
      }
      # display expanded lines
      var display?/eax: boolean <- should-render? curr
      {
        compare display?, 0/false
        break-if-=
        var unclip-cursor-line?/eax: boolean <- unclip-cursor-line? self, i
        y <- render-trace-line screen, curr, xmin, y, xmax, ymax, fg, bg, unclip-cursor-line?
        copy-to already-hiding-lines?, 0/false
        break $render-trace:iter
      }
      # ignore the rest
      compare already-hiding-lines?, 0/false
      {
        break-if-!=
        var x/eax: int <- copy xmin
        x, y <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, x, y, fg, bg
        y <- increment
        copy-to already-hiding-lines?, 1/true
      }
    }
    i <- increment
    loop
  }
  # prevent cursor from going too far down
  clamp-cursor-to-bottom self, y, screen, xmin, ymin, xmax, ymax
  return y
}

fn unclip-cursor-line? _self: (addr trace), _i: int -> _/eax: boolean {
  # if unclip? and i == *cursor-line-index, render unclipped
  var self/esi: (addr trace) <- copy _self
  var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
  compare *unclip-cursor-line?, 0/false
  {
    break-if-!=
    return 0/false
  }
  var cursor-line-index/eax: (addr int) <- get self, cursor-line-index
  var i/ecx: int <- copy _i
  compare i, *cursor-line-index
  {
    break-if-=
    return 0/false
  }
  return 1/true
}

fn render-trace-line screen: (addr screen), _self: (addr trace-line), xmin: int, ymin: int, xmax: int, ymax: int, fg: int, bg: int, unclip?: boolean -> _/ecx: int {
  var self/esi: (addr trace-line) <- copy _self
  var xsave/edx: int <- copy xmin
  var y/ecx: int <- copy ymin
  # show depth for non-errors
  var depth-a/ebx: (addr int) <- get self, depth
  compare *depth-a, 0/error
  {
    break-if-=
    var x/eax: int <- copy xsave
    {
      x, y <- draw-int32-decimal-wrapping-right-then-down screen, *depth-a, xmin, ymin, xmax, ymax, x, y, fg, bg
      x, y <- draw-text-wrapping-right-then-down screen, " ", xmin, ymin, xmax, ymax, x, y, fg, bg
      # don't show label in UI; it's just for tests
    }
    xsave <- copy x
  }
  var data-ah/eax: (addr handle array byte) <- get self, data
  var _data/eax: (addr array byte) <- lookup *data-ah
  var data/ebx: (addr array byte) <- copy _data
  var x/eax: int <- copy xsave
  compare unclip?, 0/false
  {
    break-if-=
    x, y <- draw-text-wrapping-right-then-down screen, data, xmin, ymin, xmax, ymax, x, y, fg, bg
  }
  compare unclip?, 0/false
  {
    break-if-!=
    x <- draw-text-rightward screen, data, x, xmax, y, fg, bg
  }
  y <- increment
  return y
}

fn should-render? _line: (addr trace-line) -> _/eax: boolean {
  var line/eax: (addr trace-line) <- copy _line
  var result/eax: (addr boolean) <- get line, visible?
  return *result
}

# This is super-inefficient, string-comparing every trace line
# against every visible line.
fn recompute-all-visible-lines _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  var max-addr/edx: (addr int) <- get self, first-free
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/esi: (addr array trace-line) <- copy _trace
  var i/ecx: int <- copy 0
  {
    compare i, *max-addr
    break-if->=
    var offset/ebx: (offset trace-line) <- compute-offset trace, i
    var curr/ebx: (addr trace-line) <- index trace, offset
    recompute-visibility _self, curr
    i <- increment
    loop
  }
}

fn recompute-visibility _self: (addr trace), _line: (addr trace-line) {
  var self/esi: (addr trace) <- copy _self
  # recompute
  var candidates-ah/eax: (addr handle array trace-line) <- get self, visible
  var candidates/eax: (addr array trace-line) <- lookup *candidates-ah
  var i/ecx: int <- copy 0
  var len/edx: int <- length candidates
  {
    compare i, len
    break-if->=
    {
      var curr-offset/ecx: (offset trace-line) <- compute-offset candidates, i
      var curr/ecx: (addr trace-line) <- index candidates, curr-offset
      var match?/eax: boolean <- trace-lines-equal? curr, _line
      compare match?, 0/false
      break-if-=
      var line/eax: (addr trace-line) <- copy _line
      var dest/eax: (addr boolean) <- get line, visible?
      copy-to *dest, 1/true
      return
    }
    i <- increment
    loop
  }
  var line/eax: (addr trace-line) <- copy _line
  var dest/eax: (addr boolean) <- get line, visible?
  copy-to *dest, 0/false
}

fn clamp-cursor-to-top _self: (addr trace), _y: int {
  var y/ecx: int <- copy _y
  var self/esi: (addr trace) <- copy _self
  var cursor-y/eax: (addr int) <- get self, cursor-y
  compare *cursor-y, y
  break-if->=
  copy-to *cursor-y, y
}

# extremely hacky; consider deleting test-render-trace-empty-3 when you clean this up
# TODO: duplicates logic for rendering a line
fn clamp-cursor-to-bottom _self: (addr trace), _y: int, screen: (addr screen), xmin: int, ymin: int, xmax: int, ymax: int {
  var y/ebx: int <- copy _y
  compare y, ymin
  {
    break-if->
    return
  }
  y <- decrement
  var self/esi: (addr trace) <- copy _self
  var cursor-y/eax: (addr int) <- get self, cursor-y
  compare *cursor-y, y
  break-if-<=
  copy-to *cursor-y, y
  # redraw cursor-line
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var trace/eax: (addr array trace-line) <- lookup *trace-ah
  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
  var first-free/edx: (addr int) <- get self, first-free
  compare cursor-line-index, *first-free
  {
    break-if-<
    return
  }
  var cursor-offset/ecx: (offset trace-line) <- compute-offset trace, cursor-line-index
  var cursor-line/ecx: (addr trace-line) <- index trace, cursor-offset
  var display?/eax: boolean <- should-render? cursor-line
  {
    compare display?, 0/false
    break-if-=
    var dummy/ecx: int <- render-trace-line screen, cursor-line, xmin, y, xmax, ymax, 0x38/fg=trace, 7/cursor-line-bg, 0/clip
    return
  }
  var dummy1/eax: int <- copy 0
  var dummy2/ecx: int <- copy 0
  dummy1, dummy2 <- draw-text-wrapping-right-then-down screen, "...", xmin, ymin, xmax, ymax, xmin, y, 9/fg=trace, 7/cursor-line-bg
}

fn test-render-trace-empty {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 5/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 0, "F - test-render-trace-empty/cursor"
  check-screen-row screen,                                  0/y, "    ", "F - test-render-trace-empty"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "    ", "F - test-render-trace-empty/bg"
}

fn test-render-trace-empty-2 {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 2/ymin, 5/xmax, 4/ymax, 0/no-cursor  # cursor below top row
  #
  check-ints-equal y, 2, "F - test-render-trace-empty-2/cursor"
  check-screen-row screen,                                  2/y, "    ", "F - test-render-trace-empty-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "    ", "F - test-render-trace-empty-2/bg"
}

fn test-render-trace-empty-3 {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 2/ymin, 5/xmax, 4/ymax, 1/show-cursor  # try show cursor
  # still no cursor to show
  check-ints-equal y, 2, "F - test-render-trace-empty-3/cursor"
  check-screen-row screen,                                  1/y, "    ", "F - test-render-trace-empty-3/line-above-cursor"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "    ", "F - test-render-trace-empty-3/bg-for-line-above-cursor"
  check-screen-row screen,                                  2/y, "    ", "F - test-render-trace-empty-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "    ", "F - test-render-trace-empty-3/bg"
}

fn test-render-trace-collapsed-by-default {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  trace-text t, "l", "data"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 5/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 1, "F - test-render-trace-collapsed-by-default/cursor"
  check-screen-row screen, 0/y, "... ", "F - test-render-trace-collapsed-by-default"
}

fn test-render-trace-error {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  error t, "error"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 1, "F - test-render-trace-error/cursor"
  check-screen-row screen, 0/y, "error", "F - test-render-trace-error"
}

fn test-render-trace-error-at-start {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  error t, "error"
  trace-text t, "l", "data"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 2, "F - test-render-trace-error-at-start/cursor"
  check-screen-row screen, 0/y, "error", "F - test-render-trace-error-at-start/0"
  check-screen-row screen, 1/y, "...  ", "F - test-render-trace-error-at-start/1"
}

fn test-render-trace-error-at-end {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "data"
  error t, "error"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 2, "F - test-render-trace-error-at-end/cursor"
  check-screen-row screen, 0/y, "...  ", "F - test-render-trace-error-at-end/0"
  check-screen-row screen, 1/y, "error", "F - test-render-trace-error-at-end/1"
}

fn test-render-trace-error-in-the-middle {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  error t, "error"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 0/no-cursor
  #
  check-ints-equal y, 3, "F - test-render-trace-error-in-the-middle/cursor"
  check-screen-row screen, 0/y, "...  ", "F - test-render-trace-error-in-the-middle/0"
  check-screen-row screen, 1/y, "error", "F - test-render-trace-error-in-the-middle/1"
  check-screen-row screen, 2/y, "...  ", "F - test-render-trace-error-in-the-middle/2"
}

fn test-render-trace-cursor-in-single-line {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  error t, "error"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...   ", "F - test-render-trace-cursor-in-single-line/0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-render-trace-cursor-in-single-line/0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-render-trace-cursor-in-single-line/1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-render-trace-cursor-in-single-line/1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-render-trace-cursor-in-single-line/2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-render-trace-cursor-in-single-line/2/cursor"
}

fn render-trace-menu screen: (addr screen) {
  var width/eax: int <- copy 0
  var height/ecx: int <- copy 0
  width, height <- screen-size screen
  var y/ecx: int <- copy height
  y <- decrement
  var height/edx: int <- copy y
  height <- increment
  clear-rect screen, 0/x, y, width, height, 0xc5/bg=blue-bg
  set-cursor-position screen, 0/x, y
  draw-text-rightward-from-cursor screen, " ^r ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " run main  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^g ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " go to  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^m ", width, 0/fg, 3/bg=keyboard
  draw-text-rightward-from-cursor screen, " to keyboard  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " enter/bksp ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " expand/collapse  ", width, 7/fg, 0xc5/bg=blue-bg
  draw-text-rightward-from-cursor screen, " ^s ", width, 0/fg, 0x5c/bg=menu-highlight
  draw-text-rightward-from-cursor screen, " show whole line  ", width, 7/fg, 0xc5/bg=blue-bg
}

fn edit-trace _self: (addr trace), key: code-point-utf8 {
  var self/esi: (addr trace) <- copy _self
  # cursor down
  {
    compare key, 0x6a/j
    break-if-!=
    var cursor-y/eax: (addr int) <- get self, cursor-y
    increment *cursor-y
    var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
    copy-to *unclip-cursor-line?, 0/false
    return
  }
  {
    compare key, 0x81/down-arrow
    break-if-!=
    var cursor-y/eax: (addr int) <- get self, cursor-y
    increment *cursor-y
    var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
    copy-to *unclip-cursor-line?, 0/false
    return
  }
  # cursor up
  {
    compare key, 0x6b/k
    break-if-!=
    var cursor-y/eax: (addr int) <- get self, cursor-y
    decrement *cursor-y
    var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
    copy-to *unclip-cursor-line?, 0/false
    return
  }
  {
    compare key, 0x82/up-arrow
    break-if-!=
    var cursor-y/eax: (addr int) <- get self, cursor-y
    decrement *cursor-y
    var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
    copy-to *unclip-cursor-line?, 0/false
    return
  }
  # enter = expand
  {
    compare key, 0xa/newline
    break-if-!=
    expand self
    return
  }
  # backspace = collapse
  {
    compare key, 8/backspace
    break-if-!=
    collapse self
    return
  }
  # ctrl-s: temporarily unclip current line
  {
    compare key, 0x13/ctrl-s
    break-if-!=
    var unclip-cursor-line?/eax: (addr boolean) <- get self, unclip-cursor-line?
    copy-to *unclip-cursor-line?, 1/true
    return
  }
  # ctrl-f: scroll down
  {
    compare key, 6/ctrl-f
    break-if-!=
    scroll-down self
    return
  }
  # ctrl-b: scroll up
  {
    compare key, 2/ctrl-b
    break-if-!=
    scroll-up self
    return
  }
}

fn expand _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
  var cursor-line-offset/eax: (offset trace-line) <- compute-offset trace, cursor-line-index
  var cursor-line/edx: (addr trace-line) <- index trace, cursor-line-offset
  var cursor-line-visible?/eax: (addr boolean) <- get cursor-line, visible?
  var cursor-line-depth/ebx: (addr int) <- get cursor-line, depth
  var target-depth/ebx: int <- copy *cursor-line-depth
  # if cursor-line is already visible, do nothing
  compare *cursor-line-visible?, 0/false
  {
    break-if-=
#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "visible", 7/fg 0/bg
    return
  }
  # reveal the run of lines starting at cursor-line-index with depth target-depth
  var i/ecx: int <- copy cursor-line-index
  var max/edx: (addr int) <- get self, first-free
  {
    compare i, *max
    break-if->=
    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
    var curr-line/edx: (addr trace-line) <- index trace, curr-line-offset
    var curr-line-depth/eax: (addr int) <- get curr-line, depth
    compare *curr-line-depth, target-depth
    break-if-<
    {
      break-if-!=
      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
      copy-to *curr-line-visible?, 1/true
      reveal-trace-line self, curr-line
    }
    i <- increment
    loop
  }
}

fn collapse _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
  var cursor-line-offset/eax: (offset trace-line) <- compute-offset trace, cursor-line-index
  var cursor-line/edx: (addr trace-line) <- index trace, cursor-line-offset
  var cursor-line-visible?/eax: (addr boolean) <- get cursor-line, visible?
  # if cursor-line is not visible, do nothing
  compare *cursor-line-visible?, 0/false
  {
    break-if-!=
    return
  }
  # hide all lines between previous and next line with a lower depth
  var cursor-line-depth/ebx: (addr int) <- get cursor-line, depth
  var cursor-y/edx: (addr int) <- get self, cursor-y
  var target-depth/ebx: int <- copy *cursor-line-depth
  var i/ecx: int <- copy cursor-line-index
  $collapse:loop1: {
    compare i, 0
    break-if-<
    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
    var curr-line/eax: (addr trace-line) <- index trace, curr-line-offset
    {
      var curr-line-depth/eax: (addr int) <- get curr-line, depth
      compare *curr-line-depth, target-depth
      break-if-< $collapse:loop1
    }
    # if cursor-line is visible, decrement cursor-y
    {
      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
      compare *curr-line-visible?, 0/false
      break-if-=
      decrement *cursor-y
    }
    i <- decrement
    loop
  }
  i <- increment
  var max/edx: (addr int) <- get self, first-free
  $collapse:loop2: {
    compare i, *max
    break-if->=
    var curr-line-offset/eax: (offset trace-line) <- compute-offset trace, i
    var curr-line/edx: (addr trace-line) <- index trace, curr-line-offset
    var curr-line-depth/eax: (addr int) <- get curr-line, depth
    compare *curr-line-depth, target-depth
    break-if-<
    {
      hide-trace-line self, curr-line
      var curr-line-visible?/eax: (addr boolean) <- get curr-line, visible?
      copy-to *curr-line-visible?, 0/false
    }
    i <- increment
    loop
  }
}

# the 'visible' array is not required to be in order
# elements can also be deleted out of order
# so it can have holes
# however, lines in it always have visible? set
# we'll use visible? being unset as a sign of emptiness
fn reveal-trace-line _self: (addr trace), line: (addr trace-line) {
  var self/esi: (addr trace) <- copy _self
  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
  var visible/eax: (addr array trace-line) <- lookup *visible-ah
  var i/ecx: int <- copy 0
  var len/edx: int <- length visible
  {
    compare i, len
    break-if->=
    var curr-offset/edx: (offset trace-line) <- compute-offset visible, i
    var curr/edx: (addr trace-line) <- index visible, curr-offset
    var curr-visible?/eax: (addr boolean) <- get curr, visible?
    compare *curr-visible?, 0/false
    {
      break-if-!=
      # empty slot found
      copy-object line, curr
      return
    }
    i <- increment
    loop
  }
  abort "too many visible lines; increase size of array trace.visible"
}

fn hide-trace-line _self: (addr trace), line: (addr trace-line) {
  var self/esi: (addr trace) <- copy _self
  var visible-ah/eax: (addr handle array trace-line) <- get self, visible
  var visible/eax: (addr array trace-line) <- lookup *visible-ah
  var i/ecx: int <- copy 0
  var len/edx: int <- length visible
  {
    compare i, len
    break-if->=
    var curr-offset/edx: (offset trace-line) <- compute-offset visible, i
    var curr/edx: (addr trace-line) <- index visible, curr-offset
    var found?/eax: boolean <- trace-lines-equal? curr, line
    compare found?, 0/false
    {
      break-if-=
      clear-object curr
    }
    i <- increment
    loop
  }
}

fn cursor-too-deep? _self: (addr trace) -> _/eax: boolean {
  var self/esi: (addr trace) <- copy _self
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var _trace/eax: (addr array trace-line) <- lookup *trace-ah
  var trace/edi: (addr array trace-line) <- copy _trace
  var cursor-line-index-addr/ecx: (addr int) <- get self, cursor-line-index
  var cursor-line-index/ecx: int <- copy *cursor-line-index-addr
  var cursor-line-offset/eax: (offset trace-line) <- compute-offset trace, cursor-line-index
  var cursor-line/edx: (addr trace-line) <- index trace, cursor-line-offset
  var cursor-line-visible?/eax: (addr boolean) <- get cursor-line, visible?
  var cursor-line-depth/ebx: (addr int) <- get cursor-line, depth
  var target-depth/ebx: int <- copy *cursor-line-depth
  # if cursor-line is visible, return false
  compare *cursor-line-visible?, 0/false
  {
    break-if-=
    return 0/false
  }
  # return cursor-line-depth >= max-depth-1
  target-depth <- increment
  var max-depth-addr/eax: (addr int) <- get self, max-depth
  compare target-depth, *max-depth-addr
  {
    break-if-<
    return 1/true
  }
  return 0/false
}

fn test-cursor-down-and-up-within-trace {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  error t, "error"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-and-up-within-trace/pre-0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-and-up-within-trace/pre-1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/pre-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/pre-2/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "      ", "F - test-cursor-down-and-up-within-trace/down-0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "||||| ", "F - test-cursor-down-and-up-within-trace/down-1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/down-2/cursor"
  # cursor up
  edit-trace t, 0x6b/k
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-and-up-within-trace/up-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-and-up-within-trace/up-0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-and-up-within-trace/up-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-and-up-within-trace/up-1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-and-up-within-trace/up-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-and-up-within-trace/up-2/cursor"
}

fn test-cursor-down-past-bottom-of-trace {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  error t, "error"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0xa/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||   ", "F - test-cursor-down-past-bottom-of-trace/pre-0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-past-bottom-of-trace/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-past-bottom-of-trace/pre-1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/pre-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "      ", "F - test-cursor-down-past-bottom-of-trace/pre-2/cursor"
  # cursor down several times
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  # hack: we do need to render to make this test pass; we're mixing state management with rendering
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0xa/xmax, 4/ymax, 1/show-cursor
  # cursor clamps at bottom
  check-screen-row screen,                                  0/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "      ", "F - test-cursor-down-past-bottom-of-trace/down-0/cursor"
  check-screen-row screen,                                  1/y, "error ", "F - test-cursor-down-past-bottom-of-trace/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "      ", "F - test-cursor-down-past-bottom-of-trace/down-1/cursor"
  check-screen-row screen,                                  2/y, "...   ", "F - test-cursor-down-past-bottom-of-trace/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "|||   ", "F - test-cursor-down-past-bottom-of-trace/down-2/cursor"
}

fn test-expand-within-trace {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...      ", "F - test-expand-within-trace/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-expand-within-trace/pre-0/cursor"
  check-screen-row screen,                                  1/y, "         ", "F - test-expand-within-trace/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-expand-within-trace/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1 ", "F - test-expand-within-trace/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-expand-within-trace/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2 ", "F - test-expand-within-trace/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-expand-within-trace/expand-1/cursor"
  check-screen-row screen,                                  2/y, "         ", "F - test-expand-within-trace/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-expand-within-trace/expand-2/cursor"
}

fn test-trace-expand-skips-lower-depth {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...      ", "F - test-trace-expand-skips-lower-depth/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-trace-expand-skips-lower-depth/pre-0/cursor"
  check-screen-row screen,                                  1/y, "         ", "F - test-trace-expand-skips-lower-depth/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-skips-lower-depth/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1 ", "F - test-trace-expand-skips-lower-depth/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-trace-expand-skips-lower-depth/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...      ", "F - test-trace-expand-skips-lower-depth/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-1/cursor"
  check-screen-row screen,                                  2/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-trace-expand-skips-lower-depth/expand-2/cursor"
}

fn test-trace-expand-continues-past-lower-depth {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...      ", "F - test-trace-expand-continues-past-lower-depth/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||      ", "F - test-trace-expand-continues-past-lower-depth/pre-0/cursor"
  check-screen-row screen,                                  1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1 ", "F - test-trace-expand-continues-past-lower-depth/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||| ", "F - test-trace-expand-continues-past-lower-depth/expand-0/cursor"
  # TODO: might be too wasteful to show every place where lines are hidden
  check-screen-row screen,                                  1/y, "...      ", "F - test-trace-expand-continues-past-lower-depth/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "         ", "F - test-trace-expand-continues-past-lower-depth/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2 ", "F - test-trace-expand-continues-past-lower-depth/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "         ", "F - test-trace-expand-continues-past-lower-depth/expand-2/cursor"
}

fn test-trace-expand-stops-at-higher-depth {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-lower t
  trace-text t, "l", "line 1.1.1"
  trace-higher t
  trace-text t, "l", "line 1.2"
  trace-higher t
  trace-text t, "l", "line 2"
  trace-lower t
  trace-text t, "l", "line 2.1"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 8/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-expand-stops-at-higher-depth/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "2 line 1.1 ", "F - test-trace-expand-stops-at-higher-depth/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||||||||| ", "F - test-trace-expand-stops-at-higher-depth/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-1/cursor"
  check-screen-row screen,                                  2/y, "2 line 1.2 ", "F - test-trace-expand-stops-at-higher-depth/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-2/cursor"
  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-expand-stops-at-higher-depth/expand-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-3/cursor"
  check-screen-row screen,                                  4/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-4"
  check-background-color-in-screen-row screen, 7/bg=cursor, 4/y, "           ", "F - test-trace-expand-stops-at-higher-depth/expand-4/cursor"
}

fn test-trace-expand-twice {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-expand-twice/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-expand-twice/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-expand-twice/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-twice/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-expand-twice/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-expand-twice/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-twice/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-expand-twice/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-expand-twice/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/expand-2/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  # hack: we need to render here to make this test pass; we're mixing state management with rendering
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-expand-twice/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-expand-twice/down-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-expand-twice/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||        ", "F - test-trace-expand-twice/down-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-expand-twice/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/down-2/cursor"
  # expand again
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-expand-twice/expand2-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-expand-twice/expand2-0/cursor"
  check-screen-row screen,                                  1/y, "2 line 1.1 ", "F - test-trace-expand-twice/expand2-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||||||||| ", "F - test-trace-expand-twice/expand2-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-expand-twice/expand2-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-expand-twice/expand2-2/cursor"
}

fn test-trace-refresh-cursor {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-refresh-cursor/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-refresh-cursor/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-refresh-cursor/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-refresh-cursor/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-refresh-cursor/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-refresh-cursor/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-refresh-cursor/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-refresh-cursor/expand-2/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-refresh-cursor/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-refresh-cursor/down-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-refresh-cursor/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/down-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-refresh-cursor/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-refresh-cursor/down-2/cursor"
  # recreate trace
  clear-trace t
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  trace-text t, "l", "line 3"
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # cursor remains unchanged
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-refresh-cursor/refresh-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-refresh-cursor/refresh-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-refresh-cursor/refresh-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-refresh-cursor/refresh-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-refresh-cursor/refresh-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-refresh-cursor/refresh-2/cursor"
}

fn test-trace-preserve-cursor-on-refresh {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-preserve-cursor-on-refresh/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-preserve-cursor-on-refresh/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-preserve-cursor-on-refresh/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-preserve-cursor-on-refresh/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-preserve-cursor-on-refresh/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "              ", "F - test-trace-preserve-cursor-on-refresh/expand-2/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-preserve-cursor-on-refresh/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-preserve-cursor-on-refresh/down-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-preserve-cursor-on-refresh/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/down-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-preserve-cursor-on-refresh/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/down-2/cursor"
  # recreate trace with slightly different lines
  clear-trace t
  trace-text t, "l", "line 4"
  trace-text t, "l", "line 5"
  trace-text t, "l", "line 3"  # cursor line is unchanged
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # cursor remains unchanged
  check-screen-row screen,                                  0/y, "1 line 4   ", "F - test-trace-preserve-cursor-on-refresh/refresh-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-preserve-cursor-on-refresh/refresh-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 5   ", "F - test-trace-preserve-cursor-on-refresh/refresh-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-preserve-cursor-on-refresh/refresh-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-preserve-cursor-on-refresh/refresh-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-preserve-cursor-on-refresh/refresh-2/cursor"
}

fn test-trace-keep-cursor-visible-on-refresh {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  trace-text t, "l", "line 3"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-keep-cursor-visible-on-refresh/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-keep-cursor-visible-on-refresh/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-keep-cursor-visible-on-refresh/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "              ", "F - test-trace-keep-cursor-visible-on-refresh/expand-2/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-keep-cursor-visible-on-refresh/down-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/down-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-keep-cursor-visible-on-refresh/down-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/down-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 3   ", "F - test-trace-keep-cursor-visible-on-refresh/down-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-keep-cursor-visible-on-refresh/down-2/cursor"
  # recreate trace with entirely different lines
  clear-trace t
  trace-text t, "l", "line 4"
  trace-text t, "l", "line 5"
  trace-text t, "l", "line 6"
  mark-lines-dirty t
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # trace collapses, and cursor bumps up
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-1/cursor"
  check-screen-row screen,                                  2/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-keep-cursor-visible-on-refresh/refresh-2/cursor"
}

fn test-trace-collapse-at-top {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-at-top/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-at-top/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-at-top/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-at-top/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-at-top/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-at-top/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-at-top/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-at-top/expand-2/cursor"
  # collapse
  edit-trace t, 8/backspace
  # hack: we need to render here to make this test pass; we're mixing state management with rendering
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-ints-equal y, 1, "F - test-trace-collapse-at-top/post-0/y"
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-at-top/post-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-at-top/post-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-at-top/post-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-at-top/post-1/cursor"
}

fn test-trace-collapse {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 2   ", "F - test-trace-collapse/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/expand-1/cursor"
  # cursor down
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # collapse
  edit-trace t, 8/backspace
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-ints-equal y, 1, "F - test-trace-collapse/post-0/y"
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse/post-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse/post-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse/post-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse/post-1/cursor"
}

fn test-trace-collapse-skips-invisible-lines {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-skips-invisible-lines/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # two visible lines with an invisible line in between
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-skips-invisible-lines/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-skips-invisible-lines/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-skips-invisible-lines/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-skips-invisible-lines/expand-2/cursor"
  # cursor down to second visible line
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # collapse
  edit-trace t, 8/backspace
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-ints-equal y, 1, "F - test-trace-collapse-skips-invisible-lines/post-0/y"
  var cursor-y/eax: (addr int) <- get t, cursor-y
  check-ints-equal *cursor-y, 0, "F - test-trace-collapse-skips-invisible-lines/post-0/cursor-y"
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-skips-invisible-lines/post-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-skips-invisible-lines/post-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/post-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-skips-invisible-lines/post-1/cursor"
}

fn test-trace-collapse-two-levels {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-two-levels/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-two-levels/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-two-levels/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # two visible lines with an invisible line in between
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-two-levels/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-two-levels/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-two-levels/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-two-levels/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-two-levels/expand-2/cursor"
  # cursor down to ellipses
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # two visible lines with an invisible line in between
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-two-levels/expand2-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-two-levels/expand2-0/cursor"
  check-screen-row screen,                                  1/y, "2 line 1.1 ", "F - test-trace-collapse-two-levels/expand2-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "|||||||||| ", "F - test-trace-collapse-two-levels/expand2-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-two-levels/expand2-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-two-levels/expand2-2/cursor"
  # cursor down to second visible line
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # collapse
  edit-trace t, 8/backspace
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-ints-equal y, 1, "F - test-trace-collapse-two-levels/post-0/y"
  var cursor-y/eax: (addr int) <- get t, cursor-y
  check-ints-equal *cursor-y, 0, "F - test-trace-collapse-two-levels/post-0/cursor-y"
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-two-levels/post-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-two-levels/post-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-two-levels/post-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-two-levels/post-1/cursor"
}

fn test-trace-collapse-nested-level {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 1"
  trace-lower t
  trace-text t, "l", "line 1.1"
  trace-higher t
  trace-text t, "l", "line 2"
  trace-lower t
  trace-text t, "l", "line 2.1"
  trace-text t, "l", "line 2.2"
  trace-higher t
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 8/height, 0/no-pixel-graphics
  #
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-collapse-nested-level/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-collapse-nested-level/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-collapse-nested-level/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/pre-1/cursor"
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  # two visible lines with an invisible line in between
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-nested-level/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-collapse-nested-level/expand-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-nested-level/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-nested-level/expand-2/cursor"
  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-collapse-nested-level/expand-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-collapse-nested-level/expand-3/cursor"
  # cursor down to bottom
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  edit-trace t, 0x6a/j
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  # expand
  edit-trace t, 0xa/enter
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  # two visible lines with an invisible line in between
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-nested-level/expand2-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-nested-level/expand2-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/expand2-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/expand2-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-nested-level/expand2-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-collapse-nested-level/expand2-2/cursor"
  check-screen-row screen,                                  3/y, "2 line 2.1 ", "F - test-trace-collapse-nested-level/expand2-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "|||||||||| ", "F - test-trace-collapse-nested-level/expand2-3/cursor"
  check-screen-row screen,                                  4/y, "2 line 2.2 ", "F - test-trace-collapse-nested-level/expand2-4"
  check-background-color-in-screen-row screen, 7/bg=cursor, 4/y, "           ", "F - test-trace-collapse-nested-level/expand2-4/cursor"
  # collapse
  edit-trace t, 8/backspace
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 8/ymax, 1/show-cursor
  #
  check-ints-equal y, 4, "F - test-trace-collapse-nested-level/post-0/y"
  var cursor-y/eax: (addr int) <- get t, cursor-y
  check-ints-equal *cursor-y, 2, "F - test-trace-collapse-nested-level/post-0/cursor-y"
  check-screen-row screen,                                  0/y, "1 line 1   ", "F - test-trace-collapse-nested-level/post-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "           ", "F - test-trace-collapse-nested-level/post-0/cursor"
  check-screen-row screen,                                  1/y, "...        ", "F - test-trace-collapse-nested-level/post-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-collapse-nested-level/post-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-collapse-nested-level/post-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "||||||||   ", "F - test-trace-collapse-nested-level/post-2/cursor"
  check-screen-row screen,                                  3/y, "...        ", "F - test-trace-collapse-nested-level/post-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-collapse-nested-level/post-3/cursor"
}

fn scroll-down _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  var screen-height-addr/ebx: (addr int) <- get self, screen-height  # only available after first render
  var lines-to-skip/ebx: int <- copy *screen-height-addr
  var top-line-y-addr/eax: (addr int) <- get self, top-line-y
  lines-to-skip <- subtract *top-line-y-addr
  var already-hiding-lines-storage: boolean
  var already-hiding-lines/edx: (addr boolean) <- address already-hiding-lines-storage
  var top-line-addr/edi: (addr int) <- get self, top-line-index
  var i/eax: int <- copy *top-line-addr
  var max-addr/ecx: (addr int) <- get self, first-free
  {
    # if we run out of trace, return without changing anything
    compare i, *max-addr
    {
      break-if-<
      return
    }
    # if we've skipped enough, break
    compare lines-to-skip, 0
    break-if-<=
    #
    {
      var display?/eax: boolean <- count-line? self, i, already-hiding-lines
      compare display?, 0/false
      break-if-=
      lines-to-skip <- decrement
    }
    i <- increment
    loop
  }
  # update top-line
  copy-to *top-line-addr, i
}

fn scroll-up _self: (addr trace) {
  var self/esi: (addr trace) <- copy _self
  var screen-height-addr/ebx: (addr int) <- get self, screen-height  # only available after first render
  var lines-to-skip/ebx: int <- copy *screen-height-addr
  var top-line-y-addr/eax: (addr int) <- get self, top-line-y
  lines-to-skip <- subtract *top-line-y-addr
  var already-hiding-lines-storage: boolean
  var already-hiding-lines/edx: (addr boolean) <- address already-hiding-lines-storage
  var top-line-addr/ecx: (addr int) <- get self, top-line-index
  $scroll-up:loop: {
    # if we run out of trace, break
    compare *top-line-addr, 0
    break-if-<=
    # if we've skipped enough, break
    compare lines-to-skip, 0
    break-if-<=
    #
    var display?/eax: boolean <- count-line? self, *top-line-addr, already-hiding-lines
    compare display?, 0/false
    {
      break-if-=
      lines-to-skip <- decrement
    }
    decrement *top-line-addr
    loop
  }
}

# TODO: duplicates logic for counting lines rendered
fn count-line? _self: (addr trace), index: int, _already-hiding-lines?: (addr boolean) -> _/eax: boolean {
  var self/esi: (addr trace) <- copy _self
  var trace-ah/eax: (addr handle array trace-line) <- get self, data
  var trace/eax: (addr array trace-line) <- lookup *trace-ah
  var offset/ecx: (offset trace-line) <- compute-offset trace, index
  var curr/eax: (addr trace-line) <- index trace, offset
  var already-hiding-lines?/ecx: (addr boolean) <- copy _already-hiding-lines?
  # count errors
  {
    var curr-depth/eax: (addr int) <- get curr, depth
    compare *curr-depth, 0/error
    break-if-!=
    copy-to *already-hiding-lines?, 0/false
    return 1/true
  }
  # count visible lines
  {
    var display?/eax: boolean <- should-render? curr
    compare display?, 0/false
    break-if-=
    copy-to *already-hiding-lines?, 0/false
    return 1/true
  }
  # count first undisplayed line after line to display
  compare *already-hiding-lines?, 0/false
  {
    break-if-!=
    copy-to *already-hiding-lines?, 1/true
    return 1/true
  }
  return 0/false
}

fn test-trace-scroll {
  var t-storage: trace
  var t/esi: (addr trace) <- address t-storage
  initialize-trace t, 0x100/max-depth, 0x10, 0x10
  #
  trace-text t, "l", "line 0"
  trace-text t, "l", "line 1"
  trace-text t, "l", "line 2"
  trace-text t, "l", "line 3"
  trace-text t, "l", "line 4"
  trace-text t, "l", "line 5"
  trace-text t, "l", "line 6"
  trace-text t, "l", "line 7"
  trace-text t, "l", "line 8"
  trace-text t, "l", "line 9"
  # setup: screen
  var screen-on-stack: screen
  var screen/edi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 0x10/width, 4/height, 0/no-pixel-graphics
  # pre-render
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "...        ", "F - test-trace-scroll/pre-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "|||        ", "F - test-trace-scroll/pre-0/cursor"
  check-screen-row screen,                                  1/y, "           ", "F - test-trace-scroll/pre-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/pre-1/cursor"
  check-screen-row screen,                                  2/y, "           ", "F - test-trace-scroll/pre-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/pre-2/cursor"
  check-screen-row screen,                                  3/y, "           ", "F - test-trace-scroll/pre-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/pre-3/cursor"
  # expand
  edit-trace t, 0xa/enter
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  #
  check-screen-row screen,                                  0/y, "1 line 0   ", "F - test-trace-scroll/expand-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/expand-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 1   ", "F - test-trace-scroll/expand-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/expand-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-scroll/expand-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/expand-2/cursor"
  check-screen-row screen,                                  3/y, "1 line 3   ", "F - test-trace-scroll/expand-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/expand-3/cursor"
  # scroll up
  # hack: we must have rendered before this point; we're mixing state management with rendering
  edit-trace t, 2/ctrl-b
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # no change since we're already at the top
  check-screen-row screen,                                  0/y, "1 line 0   ", "F - test-trace-scroll/up0-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/up0-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 1   ", "F - test-trace-scroll/up0-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/up0-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-scroll/up0-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/up0-2/cursor"
  check-screen-row screen,                                  3/y, "1 line 3   ", "F - test-trace-scroll/up0-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/up0-3/cursor"
  # scroll down
  edit-trace t, 6/ctrl-f
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  check-screen-row screen,                                  0/y, "1 line 4   ", "F - test-trace-scroll/down1-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/down1-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 5   ", "F - test-trace-scroll/down1-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/down1-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 6   ", "F - test-trace-scroll/down1-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/down1-2/cursor"
  check-screen-row screen,                                  3/y, "1 line 7   ", "F - test-trace-scroll/down1-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/down1-3/cursor"
  # scroll down
  edit-trace t, 6/ctrl-f
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  check-screen-row screen,                                  0/y, "1 line 8   ", "F - test-trace-scroll/down2-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/down2-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 9   ", "F - test-trace-scroll/down2-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/down2-1/cursor"
  check-screen-row screen,                                  2/y, "           ", "F - test-trace-scroll/down2-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/down2-2/cursor"
  check-screen-row screen,                                  3/y, "           ", "F - test-trace-scroll/down2-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/down2-3/cursor"
  # scroll down
  edit-trace t, 6/ctrl-f
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  # no change since we're already at the bottom
  check-screen-row screen,                                  0/y, "1 line 8   ", "F - test-trace-scroll/down3-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/down3-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 9   ", "F - test-trace-scroll/down3-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/down3-1/cursor"
  check-screen-row screen,                                  2/y, "           ", "F - test-trace-scroll/down3-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/down3-2/cursor"
  check-screen-row screen,                                  3/y, "           ", "F - test-trace-scroll/down3-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/down3-3/cursor"
  # scroll up
  edit-trace t, 2/ctrl-b
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  check-screen-row screen,                                  0/y, "1 line 4   ", "F - test-trace-scroll/up1-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/up1-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 5   ", "F - test-trace-scroll/up1-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/up1-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 6   ", "F - test-trace-scroll/up1-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/up1-2/cursor"
  check-screen-row screen,                                  3/y, "1 line 7   ", "F - test-trace-scroll/up1-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/up1-3/cursor"
  # scroll up
  edit-trace t, 2/ctrl-b
  clear-screen screen
  var y/ecx: int <- render-trace screen, t, 0/xmin, 0/ymin, 0x10/xmax, 4/ymax, 1/show-cursor
  check-screen-row screen,                                  0/y, "1 line 0   ", "F - test-trace-scroll/up2-0"
  check-background-color-in-screen-row screen, 7/bg=cursor, 0/y, "||||||||   ", "F - test-trace-scroll/up2-0/cursor"
  check-screen-row screen,                                  1/y, "1 line 1   ", "F - test-trace-scroll/up2-1"
  check-background-color-in-screen-row screen, 7/bg=cursor, 1/y, "           ", "F - test-trace-scroll/up2-1/cursor"
  check-screen-row screen,                                  2/y, "1 line 2   ", "F - test-trace-scroll/up2-2"
  check-background-color-in-screen-row screen, 7/bg=cursor, 2/y, "           ", "F - test-trace-scroll/up2-2/cursor"
  check-screen-row screen,                                  3/y, "1 line 3   ", "F - test-trace-scroll/up2-3"
  check-background-color-in-screen-row screen, 7/bg=cursor, 3/y, "           ", "F - test-trace-scroll/up2-3/cursor"
}

# saving and restoring trace indices

fn save-indices _self: (addr trace), _out: (addr trace-index-stash) {
  var self/esi: (addr trace) <- copy _self
  var out/edi: (addr trace-index-stash) <- copy _out
  var data-ah/eax: (addr handle array trace-line) <- get self, data
  var _data/eax: (addr array trace-line) <- lookup *data-ah
  var data/ebx: (addr array trace-line) <- copy _data
  # cursor
  var cursor-line-index-addr/eax: (addr int) <- get self, cursor-line-index
  var cursor-line-index/eax: int <- copy *cursor-line-index-addr
#?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, cursor-line-index, 2/fg 0/bg
  var offset/eax: (offset trace-line) <- compute-offset data, cursor-line-index
  var cursor-line/ecx: (addr trace-line) <- index data, offset
  var src/eax: (addr int) <- get cursor-line, depth
  var dest/edx: (addr int) <- get out, cursor-line-depth
  copy-object src, dest
  var src/eax: (addr handle array byte) <- get cursor-line, label
  var dest/edx: (addr handle array byte) <- get out, cursor-line-label
  copy-object src, dest
  src <- get cursor-line, data
#?   {
#?     var foo/eax: (addr array byte) <- lookup *src
#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 7/fg 0/bg
#?     var cursor-line-visible-addr/eax: (addr boolean) <- get cursor-line, visible?
#?     var cursor-line-visible?/eax: boolean <- copy *cursor-line-visible-addr
#?     var foo/eax: int <- copy cursor-line-visible?
#?     draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 5/fg 0/bg
#?   }
  dest <- get out, cursor-line-data
  copy-object src, dest
  # top of screen
  var top-line-index-addr/eax: (addr int) <- get self, top-line-index
  var top-line-index/eax: int <- copy *top-line-index-addr
#?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, top-line-index, 2/fg 0/bg
  var offset/eax: (offset trace-line) <- compute-offset data, top-line-index
  var top-line/ecx: (addr trace-line) <- index data, offset
  var src/eax: (addr int) <- get top-line, depth
  var dest/edx: (addr int) <- get out, top-line-depth
  copy-object src, dest
  var src/eax: (addr handle array byte) <- get top-line, label
  var dest/edx: (addr handle array byte) <- get out, top-line-label
  copy-object src, dest
  src <- get top-line, data
  dest <- get out, top-line-data
  copy-object src, dest
}

fn restore-indices _self: (addr trace), _in: (addr trace-index-stash) {
  var self/edi: (addr trace) <- copy _self
  var in/esi: (addr trace-index-stash) <- copy _in
  var data-ah/eax: (addr handle array trace-line) <- get self, data
  var _data/eax: (addr array trace-line) <- lookup *data-ah
  var data/ebx: (addr array trace-line) <- copy _data
  # cursor
  var cursor-depth/edx: (addr int) <- get in, cursor-line-depth
  var cursor-line-label-ah/eax: (addr handle array byte) <- get in, cursor-line-label
  var _cursor-line-label/eax: (addr array byte) <- lookup *cursor-line-label-ah
  var cursor-line-label/ecx: (addr array byte) <- copy _cursor-line-label
  var cursor-line-data-ah/eax: (addr handle array byte) <- get in, cursor-line-data
  var cursor-line-data/eax: (addr array byte) <- lookup *cursor-line-data-ah
  var new-cursor-line-index/eax: int <- find-in-trace self, *cursor-depth, cursor-line-label, cursor-line-data
  var dest/edx: (addr int) <- get self, cursor-line-index
  copy-to *dest, new-cursor-line-index
  # top of screen
  var top-depth/edx: (addr int) <- get in, top-line-depth
  var top-line-label-ah/eax: (addr handle array byte) <- get in, top-line-label
  var _top-line-label/eax: (addr array byte) <- lookup *top-line-label-ah
  var top-line-label/ecx: (addr array byte) <- copy _top-line-label
  var top-line-data-ah/eax: (addr handle array byte) <- get in, top-line-data
  var top-line-data/eax: (addr array byte) <- lookup *top-line-data-ah
  var new-top-line-index/eax: int <- find-in-trace self, *top-depth, top-line-label, top-line-data
  var dest/edx: (addr int) <- get self, top-line-index
  copy-to *dest, new-top-line-index
}

# like trace-contains? but stateless
# this is super-inefficient, string comparing every trace line
fn find-in-trace _self: (addr trace), depth: int, label: (addr array byte), data: (addr array byte) -> _/eax: int {
  var self/esi: (addr trace) <- copy _self
  var candidates-ah/eax: (addr handle array trace-line) <- get self, data
  var candidates/eax: (addr array trace-line) <- lookup *candidates-ah
  var i/ecx: int <- copy 0
  var max/edx: (addr int) <- get self, first-free
  {
    compare i, *max
    break-if->=
    {
      var curr-offset/edx: (offset trace-line) <- compute-offset candidates, i
      var curr/edx: (addr trace-line) <- index candidates, curr-offset
      # if curr->depth does not match, continue
      var curr-depth-addr/eax: (addr int) <- get curr, depth
      var curr-depth/eax: int <- copy *curr-depth-addr
      compare curr-depth, depth
      break-if-!=
      # if curr->label does not match, continue
      var curr-label-ah/eax: (addr handle array byte) <- get curr, label
      var curr-label/eax: (addr array byte) <- lookup *curr-label-ah
      var match?/eax: boolean <- string-equal? curr-label, label
      compare match?, 0/false
      break-if-=
      # if curr->data does not match, continue
      var curr-data-ah/eax: (addr handle array byte) <- get curr, data
      var curr-data/eax: (addr array byte) <- lookup *curr-data-ah
      {
        var match?/eax: boolean <- string-equal? curr-data, data
        compare match?, 0/false
      }
      break-if-=
#?       draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, " => ", 7/fg 0/bg
#? #?       draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, i, 4/fg 0/bg
#?       draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, curr-data, 7/fg 0/bg
#?       var curr-visible-addr/eax: (addr boolean) <- get curr, visible?
#?       var curr-visible?/eax: boolean <- copy *curr-visible-addr
#?       var foo/eax: int <- copy curr-visible?
#?       draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 2/fg 0/bg
      return i
    }
    i <- increment
    loop
  }
  abort "not in trace"
  return -1
}