The Q^3 and Mini-Q^3

The Q^3 Snippet

As you have seen q^2-scans have improved quite a lot over the time, but are other possibilites. In May 1999 Leonardo Humberto and John Metcalf published a new type of quickscanner - q^3 (Puddleglum). It no longer decodes the correct position with a set of additions, but with some multiplications:
...
;;
;; quickscanner
;;

        start   EQU     boot    ; first instruction of this warrior
        qStep   EQU     5400    ; distance between two scans
        qHop    EQU     1300    ; scan distance within a scan

for 25
        dat.f   0,              0
rof

;; decoding table

        dat.f   15,             10      ; A, D
qTab    dat.f    7,              4      ; B, E
        dat.f   13,             11      ; C, F

qGo     ...

;; found nothing -> boot paper

        ...

;; decoder

attack2 mul.b   qTab,           found
attack1 mul.ab  qTab,           @ attack2

;; choose between the two possible positions

qSelect sne.i   (start - 1),    @ found ; use 1st position?
        add.ab  # qHop,         found   ; no, use 2nd!

;; prepare bombing

adjust  add.ba  found,          found

;; bombing engine IV

        qTimes  EQU     20         ; number of bombs to throw
        qStep2  EQU     4          ; distance between bombs

throw   mov.i   qBomb,          @ found
        mov.i   qBomb,          * found
found   mov.i   -qStep2,        @ qStart ; <-- CHANGED AGAIN
        add.f   qIncr,          found
        djn.b   throw,          # 20

        jmp     boot               ; start paper

qBomb   dat.f   # 0,            # qStep2
qIncr   dat.f   # -qStep2,      # 2*qStep2
...
I've taken the liberty to use the bombing engine IV, that we have used along with the last versions of YAP. Puddleglum uses a 0.5c negative bomber, which doesn't need the extra line at "adjust". This means, that the average number of cycles before an attack in Puddleglum starts is 1 cycle lower than in the version, that I present here.

Let's look at the scans (without 1-cycle-attacks):
...
qGo     seq     found+1*qStep,          found+1*qStep+qHop  ; 1
        jmp     qSelect
...
This is the usual scan without decoding. During the following scans there are a lot gaps, which the decoder cannot handle. I will not mention this anymore, because you should be quite familar with the problem now.
...
        seq     found+3*qStep,          found+3*qStep+qHop  ; E-1
        jmp     > attack1,           < qTab

        seq     found+4*qStep,          found+4*qStep+qHop  ; E
        jmp     > attack1

        seq     found+5*qStep,          found+5*qStep+qHop  ; E+1
        jmp     > attack1,           > qTab

        seq     found+6*qStep,          found+6*qStep+qHop  ; B-1
        jmp     attack1,                { qTab

        seq     found+7*qStep,          found+7*qStep+qHop  ; B
        jmp     attack1

        seq     found+8*qStep,          found+8*qStep+qHop  ; B+1
        jmp     attack1,                } qTab

        seq     found+9*qStep,          found+9*qStep+qHop  ; D-1
        djn.b   > attack1,           { attack2

        seq     found+10*qStep,         found+10*qStep+qHop ; D
        jmp     > attack1,           { attack2

        seq     found+11*qStep,         found+11*qStep+qHop ; F
        jmp     > attack1,           } attack2

        seq     found+13*qStep,         found+13*qStep+qHop ; C
        jmp     attack1,                } attack1

        seq     found+14*qStep,         found+14*qStep+qHop ; A-1
        djn.a   attack1,                { attack1

        seq     found+15*qStep,         found+15*qStep+qHop ; A
        jmp     attack1,                { attack1

        seq     found+18*qStep,         found+18*qStep+qHop ; B*E+1-B-E
        djn.f   attack2,                qTab

        seq     found+21*qStep,         found+21*qStep+qHop ; B*E-B
        jmp     attack2,                < qTab

        seq     found+24*qStep,         found+24*qStep+qHop ; B*E-E
        jmp     attack2,                { qTab

        seq     found+32*qStep,         found+32*qStep+qHop ; B*E+E
        jmp     attack2,                } qTab

        seq     found+35*qStep,         found+35*qStep+qHop ; B*E+B
        jmp     attack2,                > qTab

        seq     found+39*qStep,         found+39*qStep+qHop ; C*E-C
        djn.b   attack2,                } attack1

        seq     found+52*qStep,         found+52*qStep+qHop ; C*E
        jmp     attack2,                } attack1

        seq     found+56*qStep,         found+56*qStep+qHop ; A*E-E
        djn.a   attack2,                { attack1

        seq     found+60*qStep,         found+60*qStep+qHop ; A*E
        jmp     attack2,                { attack1

        seq     found+63*qStep,         found+63*qStep+qHop ; B*D-B
        djn.b   attack2,                { attack2

        seq     found+66*qStep,         found+66*qStep+qHop ; B*F-F
        djn.a   attack2,                } attack2

        seq     found+70*qStep,         found+70*qStep+qHop ; B*D
        jmp     attack2,                { attack2

        seq     found+77*qStep,         found+77*qStep+qHop ; B*F
        jmp     attack2,                } attack2
...
In previous quickscanners the scanned locations were all relative to the beginning of the warrior. Because we can no longer add a value to the first scanned position, it is necessary to make the scanned position relative to the label "found".

Now we find an interesting idea:
...
        sne     found+28*qStep,         found+28*qStep+qHop ; B*E
        jmz     boot,                   found+28*qStep+qHop-10
...
Normally we would just add a "jmp boot" to the end of the scan, but if the scan is immediately followed by the decoder, we have a restricted free scan. It is restricted to a position near (found+28*qStep+qHop), but it is free.

Before I start to benchmark this quickscan, I have to address a problem when creating quickscanners. As you have seen they become more and more complex and it is therefore quite probable, that they contain bugs. If your quickscanner bombs the found position and not only near it, there is a simple test for your quickscanner (CoreWarrior #84).

Append your quickscanner to a "jmp 0" and let it fight against a simple "jmp 0, 1" with "pmars -b -P -c 500 qscan.red jmp0.red". If your quickscanner works correctly it should win exactly twice the number of scanned locations.

Another problem of this quickscanner is, that it is no longer that easy to assure, that all scanned positions are inside [200,7900], but for example with (qStep EQU 5400) and (qHop EQU 1300) that is the case.

Puddleglum's quickscan looks at 27*2+1 locations. This might be a bit much. It scores as follows against Wilkies and WilFiz (the free scan is always used and scans are removed from the end, i.e. beginning with 77, 70, ...):
scaned pos.| Wilkies
-----------+------------------------------------------------------
2*27+1     | 131.955839 (W 28.590352%, T 46.184784%, L 25.224864%)
2*26+1     | 132.661945 (W 28.678481%, T 46.626501%, L 24.695018%)
2*25+1     | 132.218092 (W 28.442935%, T 46.889288%, L 24.667778%)
2*24+1     | 131.983613 (W 28.305132%, T 47.068218%, L 24.626650%)
2*23+1     | 131.836730 (W 28.228219%, T 47.152075%, L 24.619707%)

scaned pos.| WilFiz
-----------+------------------------------------------------------
2*27+1     | 113.860937 (W 25.200295%, T 38.260052%, L 36.539653%)
2*26+1     | 115.350062 (W 25.310858%, T 39.417489%, L 35.271653%)
2*25+1     | 116.271418 (W 25.300709%, T 40.369290%, L 34.330000%)
2*24+1     | 116.930522 (W 25.277208%, T 41.098898%, L 33.623894%)
2*23+1     | 117.677221 (W 25.262253%, T 41.890463%, L 32.847285%)
2*22+1     | 118.061787 (W 25.071572%, T 42.847071%, L 32.081357%)
2*21+1     | 118.561723 (W 25.011751%, T 43.526471%, L 31.461778%)
2*20+1     | 118.797804 (W 24.870743%, T 44.185575%, L 30.943682%)
2*19+1     | 118.991155 (W 24.692347%, T 44.914114%, L 30.393539%)
2*18+1     | 119.000235 (W 24.447186%, T 45.658676%, L 29.894138%)
2*17+1     | 118.976734 (W 24.239414%, T 46.258493%, L 29.502094%)
The full quickscan has 1 jump to qSelect, 6 jumps to attack1 and 20 jumps (19 jumps + 1 fall-through) to attack2. When we use the bombing engine IV, it takes an average of 4.20 instructions before the first bomb is thrown. The original quickscan needs only an average of 3.20 instructions, because it doesn't need to prepare the bombing (label "adjust").

This is the (more or less ;-) original bombing engine:
...
;; decoder

attack2 mul.b   qTab,           found
attack1 mul.ab  qTab,           @ attack2

;; choose between the two possible positions

qSelect sne.i   (start - 1),    @ found ; use 1st position?
        add.ab  # qHop,         found   ; no, use 2nd!

;; bombing engine V

        qTimes  EQU     20         ; number of bombs to throw
        qStep2  EQU     4          ; distance between bombs
        qDist   EQU     (qTimes*qStep2 - 10)

qLoop   mov     qBomb,          @ found
found   mov     qBomb,          * qStep
        sub     # qStep2,       found
        djn     qLoop,          # qTimes

        jmp     boot            ; start paper
qBomb   dat.f   { qDist,        { 1
...
It scores as follows:
scaned pos.| Wilkies
-----------+------------------------------------------------------
2*27+1     | 131.728304 (W 28.069585%, T 47.519549%, L 24.410866%)
2*26+1     | 131.540828 (W 27.952079%, T 47.684592%, L 24.363330%)
2*25+1     | 131.337863 (W 27.827629%, T 47.854976%, L 24.317395%)

scaned pos.| WilFiz
-----------+------------------------------------------------------
2*27+1     | 112.777208 (W 24.472824%, T 39.358736%, L 36.168440%)
2*26+1     | 113.414413 (W 24.459471%, T 40.036000%, L 35.504529%)
2*25+1     | 114.162714 (W 24.455732%, T 40.795518%, L 34.748750%)
2*24+1     | 114.982588 (W 24.463210%, T 41.592958%, L 33.943832%)
2*23+1     | (to be calculated ...)
2*22+1     | (to be calculated ...)
2*21+1     | (to be calculated ...)
At the point some of you may have noticed, that the q^2-scanner of Innocuous scores against WilFiz better than this q^3-scanner.
The Mini-Q^3

As you have seen a complete q^3-scan may not result in the best score. Fortunatly John Metcalf was so kind to trim the q^3-scan to fewer locations (CoreWarrior #75). His Mini-Q^3 scans 2*16+1 locations and has the advantage, that it no longer needs an extra table for its decoder. All necessary values are nicely hidden inside the quickscan:
...
;; quickscanner

        start   EQU     boot    ; first instruction of this warrior
        qStep   EQU     5400    ; distance between two scans
        qHop    EQU     1300    ; scan distance within a scan

for 52
        dat.f   0,              0
rof

qGo     seq     found+1*qStep,          found+1*qStep+qHop      ; 1
        jmp     qSelect

        seq     found+2*qStep,          found+2*qStep+qHop      ; C-1
        jmp     > attack1,           { attack2

        seq     found+3*qStep,          found+3*qStep+qHop      ; C
        jmp     > attack1

        seq     found+4*qStep,          found+4*qStep+qHop      ; C+1
        jmp     > attack1,           } attack2

        seq     found+5*qStep,          found+5*qStep+qHop      ; B-1
        jmp     attack1,                < qBomb

        seq     found+6*qStep,          found+6*qStep+qHop      ; B
        jmp     attack1

        seq     found+7*qStep,          found+7*qStep+qHop      ; B+1
        jmp     attack1,                > qBomb

        seq     found+9*qStep,          found+9*qStep+qHop      ; A-1
        djn.b   attack1,                { attack1

        seq     found+10*qStep,         found+10*qStep+qHop     ; B
        jmp     attack1,                { attack1

        seq     found+12*qStep,         found+12*qStep+qHop     ; B*C-B
        jmp     attack2,                { attack2

        seq     found+15*qStep,         found+15*qStep+qHop     ; B*C-C
        jmp     attack2,                < qBomb

        seq     found+21*qStep,         found+21*qStep+qHop     ; B*C+C
        jmp     attack2,                > qBomb

        seq     found+24*qStep,         found+24*qStep+qHop     ; B*C+B
        jmp     attack2,                } attack2

        seq     found+27*qStep,         found+27*qStep+qHop     ; A*C-C
        djn.b   attack2,                { attack1

        seq     found+30*qStep,         found+30*qStep+qHop     ; A*C
        jmp     attack2,                { attack1

;; free scan + boot paper, if found nothing

        sne     found+18*qStep,         found+18*qStep+qHop     ; B*C
        jmz.f   boot,                   found+18*qStep+qHop-10

;; decoder

attack2 mul.ab  # 3,            found           ; C=3
attack1 mul.b   qBomb,          @ attack2

;; choose between the two possible positions

qSelect sne.i   (start-1),      @ found
        add.ab  # qHop,         found

;; bombing engine V

        qTimes  EQU     20         ; number of bombs to throw
        qStep2  EQU     4          ; distance between bombs
        qDist   EQU     (qTimes*qStep2 - 10)

qLoop   mov     qBomb,          @ found
found   mov     qBomb,          * qStep
        sub     # qStep2,       found
        djn     qLoop,          # qTimes

        jmp     boot,           > 10         ; A=10

qBomb   dat.f   { qDist,        { 6             ; B=6
...
YAP with the original MiniQ^3 (and original bombing engine) scores as follows:
Wilkies: 129.334380 (W 26.811199%, T 48.900782%, L 24.288019%)
WilFiz : 118.624749 (W 23.864462%, T 47.031364%, L 29.104175%)
Yet Another Paper III

Just to have another reference point, here is YAP III. Is uses a q^3-scan (2*18+1 locations) together with bombing engine IV and 1-cycle-attacks. I've already sent it to Koenigstuhl. At the moment of writing it was 340th with a score of 126.11. It scores as follows against Wilkies and Wilfiz:
Wilkies: 130.187369 (W 27.325022%, T 48.212302%, L 24.462676%)
WilFiz : 119.077682 (W 24.473358%, T 45.657608%, L 29.869034%)
The only difference to the version in chapter 5 is, that this version uses three 1-cycle-attacks. This little change results in an improvement of about 0.077 points against Wilfiz.
;redcode-94nop
;name Yet Another Paper III
;author Jens Gutzeit
;strategy q^3 -> paper
;assert CORESIZE == 8000

        ORG     qGo

        pStep1  EQU     3913
        pStep2  EQU     3035

boot    spl     1
        spl     1

silk1   spl     @ silk1,        < pStep1
        mov.i   } silk1,        > silk1

        mov.i   { silk1,        < silk2
silk2   djn.f   @ silk2,        < pStep2

;;
;; quickscanner
;;

        start   EQU     boot    ; first instruction of this warrior
        qStep   EQU     5400    ; distance between two scans
        qHop    EQU     1300    ; scan distance within a scan

for 42
        dat.f   0,              0
rof

;; decoding table

        dat.f   15,             10      ; A, D
qTab    dat.f    7,              4      ; B, E
        dat.f   13,             11      ; C, F

qGo     seq     found+1*qStep,          found+1*qStep+qHop  ; 1
        jmp     qSelect,                < found+1*qStep+qHop/2

        seq     found+3*qStep,          found+3*qStep+qHop  ; E-1
        jmp     > attack1,           < qTab

        seq     found+4*qStep,          found+4*qStep+qHop  ; E
        jmp     > attack1,           < found+4*qStep+qHop/2

        seq     found+5*qStep,          found+5*qStep+qHop  ; E+1
        jmp     > attack1,           > qTab

        seq     found+6*qStep,          found+6*qStep+qHop  ; B-1
        jmp     attack1,                { qTab

        seq     found+7*qStep,          found+7*qStep+qHop  ; B
        jmp     attack1,                < found+7*qStep+qHop/2

        seq     found+8*qStep,          found+8*qStep+qHop  ; B+1
        jmp     attack1,                } qTab

        seq     found+9*qStep,          found+9*qStep+qHop  ; D-1
        djn.b   > attack1,           { attack2

        seq     found+10*qStep,         found+10*qStep+qHop ; D
        jmp     > attack1,           { attack2

        seq     found+11*qStep,         found+11*qStep+qHop ; F
        jmp     > attack1,           } attack2

        seq     found+13*qStep,         found+13*qStep+qHop ; C
        jmp     attack1,                } attack1

        seq     found+14*qStep,         found+14*qStep+qHop ; A-1
        djn.a   attack1,                { attack1

        seq     found+15*qStep,         found+15*qStep+qHop ; A
        jmp     attack1,                { attack1

        seq     found+18*qStep,         found+18*qStep+qHop ; B*E+1-B-E
        djn.f   attack2,                qTab

        seq     found+21*qStep,         found+21*qStep+qHop ; B*E-B
        jmp     attack2,                < qTab

        seq     found+24*qStep,         found+24*qStep+qHop ; B*E-E
        jmp     attack2,                { qTab

        seq     found+32*qStep,         found+32*qStep+qHop ; B*E+E
        jmp     attack2,                } qTab

;; free scan + boot paper, if found nothing

        sne     found+28*qStep,         found+28*qStep+qHop     ; B*E
        jmz     boot,                   found+28*qStep+qHop-12

;; decoder

attack2 mul.b   qTab,           found
attack1 mul.ab  qTab,           @ attack2

;; choose between the two possible positions

qSelect sne.i   (start - 1),    @ found ; use 1st position?
        add.ab  # qHop,         found   ; no, use 2nd!

;; prepare bombing

adjust  add.ba  found,          found

;; bombing engine IV

        qTimes  EQU     20         ; number of bombs to throw
        qStep2  EQU     4          ; distance between bombs

throw   mov.i   qBomb,          @ found
        mov.i   qBomb,          * found
found   mov.i   -qStep2,        @ qStep
        add.f   qIncr,          found
        djn.b   throw,          # 20

        jmp     boot,           < 4000                  ; start paper

qBomb   dat.f   # 0,            # qStep2
qIncr   dat.f   # -qStep2,      # 2*qStep2

        END