AMOS has an annoying bug that makes it impossible to scroll the background screen horizontally in dual playfield mode with more than 16 pixel precision. This can be seen by running the following example:
For I=0 To 1
Screen Open I,640,200,8,Lowres
Screen Display I,129,48,320,200
Flash Off : Curs Off : Cls 0
Polygon 0,199 To 80,100 To 160,170 To 240,50 To 319,199
Screen Copy I,0,0,320,200 To I,320,0
Next I
Dual Playfield 0,1
Double Buffer : Autoback 0
Do
Add SX0,2,0 To 320
Add SX1,1,0 To 320
Screen Offset 0,SX0,0
Screen Offset 1,SX1,0
Screen Swap : Wait Vbl
Loop
The dual playfield example in AmosPro slyly limits itself to vertical scrolling to avoid this problem while implying that horizonal scrolling would be just as easy! It's a real pity that this was never fixed. Not only is the classic "Shadow of the Beast" smooth hardware parallax impossible, but dual playfield mode is also very useful for speeding up bobs. For example you can have a scrolling background playfield and all the bobs blitted on the foreground playfield in quick mode so the background doesn't have to be overwritten and restored each time they move. Blitz Basic used this trick quite extensively.
In an effort to right the wrongs of history I've hacked together a program with a custom Copper list and some hardware pokes that does horizontal dual playfield scrolling properly. It might be fun to rewrite this in Assembler as a nice user-friendly extension. It wouldn't be too hard to add some AGA support while we're at it...
'--------------- hardware registers ------------------------
_COLOR0=$180
_DMACON=$DDF096
_DIWSTRT=$8E
_DIWSTOP=$90
_DDFSTRT=$92
_DDFSTOP=$94
_BPLCON0=$100 : Rem screen mode
_BPLCON1=$102 : Rem playfield horizontal scroll
_BPLCON2=$104 : Rem Playfield and sprite priorities
'bitplane pointers
_BPL1PTH=$E0
_BPL2PTH=$E4
_BPL3PTH=$E8
_BPL4PTH=$EC
_BPL5PTH=$F0
_BPL6PTH=$F4
'bitplane modulos (one for each playfield)
_BPL1MOD=$108
_BPL2MOD=$10A
_COP1LC=$DDF080 : Rem Pointer to copper list
'-------------------------------------------------------------
On Error Proc CLEAN_UP
On Break Proc CLEAN_UP
For I=0 To 1
Screen Open I,640,200,8,Lowres
Screen Display I,129,48,320,200
Flash Off : Curs Off : Cls 0
Polygon 0,199 To 80,100 To 160,170 To 240,50 To 319,199
Screen Copy I,0,0,320,200 To I,320,0
Next I
Double Buffer : Autoback 0
Copper Off
Doke _DMACON,$180 : Rem kill bitplane and copper dma before we start tinkering
Dim A(1)
'screen (x,y) position in hardware coords, width and height
DTX=129 : Rem Make sure this is an odd number
DTY=48
DW=320
DH=200
For I=0 To 1
Cop Move _BPLCON1,$0
Cop Move _BPLCON2,0
Cop Move _BPL1MOD,38
Cop Move _BPL2MOD,38
Cop Move _DIWSTRT,DTY*256+DTX
Cop Move _DIWSTOP,(DTY+DH-256)*256+DTX+DW-256
Cop Move _DDFSTRT,DTX/2-16 : Rem DDFSTRT and DDFSTOP change for hires
Cop Move _DDFSTOP,DTX/2-8+8*(DW/16-1)
'
'start+32
'first high bp pointer at start+34, low pointer at start+38
Screen 0
Cop Movel _BPL1PTH,Logbase(0)-2
Cop Movel _BPL3PTH,Logbase(1)-2
Cop Movel _BPL5PTH,Logbase(2)-2
Screen 1
Cop Movel _BPL2PTH,Logbase(0)-2
Cop Movel _BPL4PTH,Logbase(1)-2
Cop Movel _BPL6PTH,Logbase(2)-2
'Cop Move _BPLCON0,$4200 :rem 4 bitplanes, no dualplayfield
Cop Move _BPLCON0,$6600 : Rem 6 bitplanes, dual playfield
Cop Wait 0,47
For C=0 To 15
Cop Move _COLOR0+C*2,Colour(C)
Next C
Cop Wait 254,255
A(I)=Cop Logic
Cop Swap
Screen Swap
Next I
'enable copper and bitplane DMA
Doke _DMACON,$8180
'The Cop Swap command copies all the commands that have been defined
'with the Cop Move and Cop Wait instructions since the _previous_ Cop Swap
'into a list starting at Cop Logic, then swaps the physical and logical
'pointers. If there have been no Cop Move or Cop Wait instructions
'since the last Cop Swap we get garbage copied into Cop Logic before the swap.
'We just want to swap the pointers so we have to poke _COP1LC manually
CL=0
Dim SX(1),BO(1),FS(1)
For SLOOP=1 To 2000
Add SX(0),2,0 To 320 : Rem horizontal scroll offset in pixels
Add SX(1),1,0 To 320
For PF=0 To 1
BO(PF)=SX(PF)/16 : Rem scroll offset in words
R=SX(PF)-BO(PF)*16 : Rem remainder
If R
FS(PF)=16-R : Rem finescroll
Else
FS(PF)=0
BO(PF)=BO(PF)-1
End If
BO(PF)=BO(PF)*2 : Rem offset in bytes to be added to bitplane pointers
Next PF
Doke A(CL)+2,FS(0)+FS(1)*16 : Rem poke finescroll value in logical copper list
'update the bitplane pointers of screen 0
Screen 0
For BP=0 To 2
BPADD=Logbase(BP)+BO(0)
BPADDH=BPADD/$10000
BPADDL=BPADD-BPADDH*$10000
Doke A(CL)+34+BP*8,BPADDH
Doke A(CL)+38+BP*8,BPADDL
Next BP
'update the bitplane pointers of screen 1
Screen 1
For BP=3 To 5
BPADD=Logbase(BP-3)+BO(1)
BPADDH=BPADD/$10000
BPADDL=BPADD-BPADDH*$10000
Doke A(CL)+34+BP*8,BPADDH
Doke A(CL)+38+BP*8,BPADDL
Next BP
Loke _COP1LC,A(CL) : Rem Swap copper lists
Screen Swap
Wait Vbl
CL=1-CL
Next SLOOP
Wait Key
CLEAN_UP
Procedure CLEAN_UP
Copper On
End
End Proc