Initial revision
[ssr] / StraySrc / Libraries / Sapphire / Modules / s / constrain
1 ;
2 ; constrain.s
3 ;
4 ; Mouse movement constraining
5 ;
6 ; © 1994-1998 Straylight
7 ;
8
9 ;----- Licensing note -------------------------------------------------------
10 ;
11 ; Constrain is free software; you can redistribute it and/or modify
12 ; it under the terms of the GNU General Public License as published by
13 ; the Free Software Foundation; either version 2, or (at your option)
14 ; any later version.
15 ;
16 ; Constrain is distributed in the hope that it will be useful,
17 ; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ; GNU General Public License for more details.
20 ;
21 ; You should have received a copy of the GNU General Public License
22 ; along with Constrain. If not, write to the Free Software Foundation,
23 ; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25 ;----- Standard header ------------------------------------------------------
26
27 GET libs:header
28 GET libs:swis
29
30 IMPORT version
31
32 ;----- Module header --------------------------------------------------------
33
34 AREA |!!!Module$$Header|,CODE,READONLY
35
36 DCD 0 ;Start code
37 DCD initialise ;Initialisation routine
38 DCD finalise ;Finalisation code
39 DCD 0 ;Service call handler
40 DCD title ;Title pointer
41 DCD version ;The help string
42 DCD 0 ;No keyword table
43 DCD &4A340 ;SWI chunk number
44 DCD swi_handler ;SWI handler code
45 DCD swi_decode ;SWI decode table
46 DCD 0 ;No decoding code needed
47
48 title DCB "Constrain",0
49
50 ;----- Initialisation and finalisation --------------------------------------
51
52 initialise ROUT
53
54 STMFD R13!,{R14} ;Stack link register nicely
55
56 ; --- Get some workspace ---
57
58 MOV R0,#6 ;Allocate workspace
59 MOV R3,#ws__wSize ;Make it *this* big
60 SWI XOS_Module ;Get me memory
61 LDMVSFD R13!,{PC} ;Return if it barfed
62 STR R2,[R12] ;Stash the workspace pointer
63 MOV R12,R2 ;Move the pointer across
64
65 ; --- Do some initialising ---
66
67 MOV R14,#0 ;No current constraint
68 STR R14,ws__routine ;Store that away
69
70 ; --- Done ---
71
72 LDMFD R13!,{PC}^ ;Return to caller happy
73
74 LTORG
75
76 finalise ROUT
77
78 STMFD R13!,{R11,R14}
79 MOV R11,R12 ;Keep the private word ptr
80 LDR R12,[R12] ;Find my workspace
81
82 BL constrain_finish ;Close any constraint
83
84 ; --- Free my workspace ---
85
86 MOV R0,#7 ;Free RMA space
87 MOV R2,R12 ;Point to workspace
88 SWI XOS_Module ;Try to free the memory
89 MOV R0,#0 ;Gonna zero the private word
90 STR R0,[R11] ;Then zero it
91 LDMFD R13!,{R11,PC}^ ;A happy bunny
92
93 LTORG
94
95 ;----- SWI dispatching ------------------------------------------------------
96
97 swi_decode DCB "Constrain",0
98 DCB "Finish",0
99 DCB "MousePos",0
100 DCB "Circle",0
101 DCB "Disc",0
102 DCB 0
103
104 swi_handler LDR R12,[R12] ;Get my workspace
105 CMP R11,#(%01-%00)/4 ;Within range?
106 ADDLO PC,PC,R11,LSL#2 ;Yes -- dispatch
107 B %01 ;No -- complain
108
109 00 B constrain_finish
110 B constrain_mousePos
111 B constrain_circ
112 B constrain_disc
113
114 01 ADR R0,noSuchSWI ;Point to the error
115 ORRS PC,R14,#V_flag ;And return with error
116
117 noSuchSWI DCD &1E6
118 DCB "Unknown Constrain operation",0
119
120 ;----- Main code ------------------------------------------------------------
121
122 ; --- divide ---
123 ;
124 ; On entry: R0 == dividend
125 ; R1 == divisor
126 ;
127 ; On exit: R0 == quotient
128 ; R1 == remainder
129 ;
130 ; Use: A standard divide routine. Fairly speedy, hopefully.
131 ; The results are always such that
132 ;
133 ; |quotient| <= |(divisor/dividend)|,
134 ;
135 ; |remainder| < |divisor|
136 ;
137 ; and
138 ;
139 ; quotient * divisor + remainder == dividend
140
141 divide ROUT
142
143 STMFD R13!,{R2,R3,R14} ;Save some registers
144
145 ; --- A note about the method ---
146 ;
147 ; We use traditional long division, but unroll the loop a
148 ; lot to keep the thing ticking over at a good rate.
149
150 ; --- First, mess about with the signs ---
151
152 ANDS R14,R0,#&80000000 ;Get the dividend's sign bit
153 ORR R14,R14,R14,LSR #1 ;Copy -- this is sign of mod
154 RSBNE R0,R0,#0 ;Take absolute value of R0
155 ANDS R3,R1,#&80000000 ;Get the divisor's sign too
156 RSBNE R1,R1,#0 ;Take absolute value of R1
157 EOR R14,R14,R3 ;Calculate sign of quotient
158 MOV R3,R1 ;Look after the divisor
159
160 ; --- Shift divisor up for long division ---
161 ;
162 ; We keep shifting the divisor up until it's greater than
163 ; the dividend, and then we skip ahead to the divide section
164
165 MOV R2,#0 ;Quotient starts off at 0
166 00divide CMP R0,R3,LSL #0
167 BLS %10divide
168 CMP R0,R3,LSL #1
169 BLS %11divide
170 CMP R0,R3,LSL #2
171 BLS %12divide
172 CMP R0,R3,LSL #3
173 BLS %13divide
174 CMP R0,R3,LSL #4
175 BLS %14divide
176 CMP R0,R3,LSL #5
177 BLS %15divide
178 CMP R0,R3,LSL #6
179 BLS %16divide
180 CMP R0,R3,LSL #7
181 MOVHI R3,R3,LSL #8
182 BHI %00divide
183
184 ; --- Now we have the shift-down loop ---
185 ;
186 ; This is where the actual job of dividing is performed.
187 ; We shift the divisor back down until it's back where we
188 ; started again.
189
190 17divide CMP R0,R3,LSL #7
191 ADC R2,R2,R2
192 SUBCS R0,R0,R3,LSL #7
193 16divide CMP R0,R3,LSL #6
194 ADC R2,R2,R2
195 SUBCS R0,R0,R3,LSL #6
196 15divide CMP R0,R3,LSL #5
197 ADC R2,R2,R2
198 SUBCS R0,R0,R3,LSL #5
199 14divide CMP R0,R3,LSL #4
200 ADC R2,R2,R2
201 SUBCS R0,R0,R3,LSL #4
202 13divide CMP R0,R3,LSL #3
203 ADC R2,R2,R2
204 SUBCS R0,R0,R3,LSL #3
205 12divide CMP R0,R3,LSL #2
206 ADC R2,R2,R2
207 SUBCS R0,R0,R3,LSL #2
208 11divide CMP R0,R3,LSL #1
209 ADC R2,R2,R2
210 SUBCS R0,R0,R3,LSL #1
211 10divide CMP R0,R3,LSL #0
212 ADC R2,R2,R2
213 SUBCS R0,R0,R3,LSL #0
214
215 CMP R3,R1 ;Have we finished dividing?
216 MOVHI R3,R3,LSR #8 ;No -- shift down a byte
217 BHI %17divide ;And loop round again
218
219 ; --- Now tidy everything up ---
220
221 TST R14,#&40000000 ;Is remainder to be negative?
222 RSBNE R1,R0,#0 ;Yes -- negate it
223 MOVEQ R1,R0 ;No -- just copy it nicely
224 TST R14,#&80000000 ;Is quotient to be negative?
225 RSBNE R0,R2,#0 ;Yes -- negate it
226 MOVEQ R0,R2 ;No -- just copy it nicely
227
228 LDMFD R13!,{R2,R3,PC}^ ;Return to caller
229
230 LTORG
231
232 ; --- divRound ---
233 ;
234 ; On entry: R0 == dividend
235 ; R1 == divisor
236 ;
237 ; On exit: R0 == quotient, rounded to nearest integer
238 ; R1 == remainder
239 ;
240 ; Use: Calculates a rounded-to-nearest quotient, rather than one
241 ; rounded towards zero, which is what divide returns you.
242 ;
243 ; The remainder is fiddled during this process, so that the
244 ; properties
245 ;
246 ; quotient * divisor + remainder == dividend
247 ;
248 ; and
249 ;
250 ; |remainder| < |divisor|
251 ;
252 ; still hold (so the remainder's sign may well change).
253
254 divRound ROUT
255
256 STMFD R13!,{R2,R14} ;Save some registers
257 MOV R2,R0 ;Keep a copy of the dividend
258 CMP R1,#0 ;Is the divisor positive?
259 MOVGE R14,R1 ;Yes -- just copy it
260 RSBLT R14,R1,#0 ;No -- negate it on the way
261 CMP R0,#0 ;Is the dividend positive?
262 ADDGE R0,R0,R14,ASR #1 ;Yes -- add half the divisor
263 SUBLT R0,R0,R14,ASR #1 ;No -- subtract it
264 SUB R2,R2,R0 ;Remember this difference
265 BL divide ;Do the division
266 ADD R1,R1,R2 ;Modify remainder suitably
267 LDMFD R13!,{R2,PC}^ ;Return to caller
268
269 LTORG
270
271 ; --- sqrt ---
272 ;
273 ; On entry: R0 == value to square-root
274 ;
275 ; On exit: R0 == the result
276 ;
277 ; Use: Evaluates the square root of the number given. This routine
278 ; is constructed from the information supplied by David Seal,
279 ; and is *extremely* fast.
280
281 EXPORT sqrt
282 sqrt ROUT
283
284 STMFD R13!,{R1-R4,R14} ;Stack registers
285 MOV R1,#0 ;Result so far
286 MOV R2,#0 ;Current remainder
287 MOV R3,#1 ;A '01' pair
288 MOV R4,#3 ;A nice mask
289
290 GBLA count
291 count SETA 0 ;Start the count at 30
292
293 WHILE count<=28 ;Set up the loop condition
294
295 AND R14,R4,R0,LSR #30-count
296 ORR R2,R14,R2,LSL #2
297 ORR R14,R3,R1,LSL #2
298 CMP R2,R14
299 ADC R1,R1,R1
300 SUBCS R2,R2,R14
301
302 count SETA count+2
303
304 WEND
305
306 AND R14,R4,R0
307 ORR R2,R14,R2,LSL #2
308 ORR R14,R3,R1,LSL #2
309 CMP R2,R14
310 ADC R1,R1,R1
311 SUBCS R2,R2,R14
312
313 c MOV R0,R1 ;Put the result in R0
314 LDMFD R13!,{R1-R4,PC}^ ;Return to caller
315
316 LTORG
317
318 ; --- constrain_circ ---
319 ;
320 ; On entry: R0 == x position of circle centre
321 ; R1 == y position of circle centre
322 ; R2 == radius of circle
323 ; R3 == maximum distance below centre allowed
324 ;
325 ; On exit: --
326 ;
327 ; Use: Constrains the mouse to the given circle outline
328
329 constrain_circ ROUT
330
331 STMFD R13!,{R0-R2,R14} ;Stack some registers
332 BL constrain_finish ;Clear a current constraint
333 MUL R14,R2,R2 ;Get the radius squared
334 MOV R2,R14 ;Put in back in R2
335 STMIA R12,{R0-R3} ;Store parameter in RMA ws
336 MOV R0,#1 ;Call this often
337 ADR R1,constrain__circ ;Routine to call
338 STR R1,ws__routine ;Remember this pointer
339 MOV R2,R12 ;Pass this R12 value
340 SWI XOS_CallEvery ;Call every 50cs
341 MOV R0,#106 ;Define mouse parameters
342 MOV R1,#&81 ;Pointer 1 -- de-linked
343 SWI XOS_Byte ;Do the OS_Byte call
344 LDMFD R13!,{R0-R2,PC}^ ;Return to caller
345
346 ; --- constrain_disc ---
347 ;
348 ; On entry: R0 == x position of circle centre
349 ; R1 == y position of circle centre
350 ; R2 == radius of circle
351 ;
352 ; On exit: --
353 ;
354 ; Use: Constrains the mouse to the given circle outline
355
356 constrain_disc ROUT
357
358 STMFD R13!,{R0-R2,R14} ;Stack some registers
359 BL constrain_finish ;Clear a current constraint
360 MUL R14,R2,R2 ;Get the radius squared
361 MOV R2,R14 ;Put in back in R2
362 STMIA R12,{R0-R2} ;Store parameter in RMA ws
363 MOV R0,#1 ;Call this often
364 ADR R1,constrain__disc ;Routine to call
365 STR R1,ws__routine ;Remember this pointer
366 MOV R2,R12 ;Pass this R12 value
367 SWI XOS_CallEvery ;Call every 50cs
368 MOV R0,#106 ;Define mouse parameters
369 MOV R1,#&81 ;Pointer 1 -- de-linked
370 SWI XOS_Byte ;Do the OS_Byte call
371 LDMFD R13!,{R0-R2,PC}^ ;Return to caller
372
373 ; --- constrain__circ ---
374 ;
375 ; On entry: --
376 ;
377 ; On exit: --
378 ;
379 ; Use: Called on interrupts to constrin the mouse
380
381 constrain__circ ROUT
382 STMFD R13!,{R0-R11,R14} ;Stack some registers
383
384 SWI XOS_Mouse ;Get the current mouse pos
385 10 LDMIA R12,{R4-R6,R9} ;Load interesting values
386 MOV R10,#0 ;Temporary flags word
387
388 ; --- Set R7 == horizontal distance from centre squared ---
389
390 SUB R14,R0,R4 ;Get distance in R14
391 MUL R7,R14,R14 ;Square it and copy
392 CMP R7,#&10000 ;Is it too big?
393 MOVGT R7,#&10000 ;Yes -- reduce (stop oflow)
394 LDR R8,ws__oldX ;Get the old x position
395 SUB R8,R8,R4 ;Get the relative position
396 EOR R8,R8,R14 ;See if top bit is changed
397 TST R8,#(1<<31) ;Test the top bit
398 ORRNE R10,R10,#(1<<0) ;Set the 'side changed' bit
399
400 ; --- Set R8 == vertical distance from centre squared ---
401
402 SUB R11,R5,R9 ;Get actual maximum height
403 MUL R8,R9,R9 ;Get the y distance squared
404 CMP R1,R11 ;Is it high enough?
405 MOVLE R1,R11 ;No -- then raise it
406 SUBLE R7,R6,R8 ;Calculate the corner x pos
407 ORRLE R10,R10,#2 ;Remember we made this bodge
408 SUBS R14,R1,R5 ;Get distance in R14
409 BGE %00constrain__circ ;If in upper quadrants, skip
410 CMP R8,R6 ;Is there a sensible distance
411 BGT %00constrain__circ ;No -- skip this magic bit
412 TST R10,#1 ;Have we changed sides?
413 BEQ %00constrain__circ ;Nope -- skip ahead
414 LDR R0,ws__oldX ;Make sure were on correct sd
415 SUB R7,R6,R8 ;Calculate the corner x pos
416 ORR R10,R10,#2 ;Remember we made this bodge
417 B %01constrain__circ ;Skip next instruction
418
419 00 MUL R8,R14,R14 ;Square it and copy
420 CMP R8,#&10000 ;Is it too big?
421 MOVGT R8,#&10000 ;Yes -- reduce (stop oflow)
422
423 01 ADDS R9,R7,R8 ;Dist from centre squared
424 BEQ %90constrain__circ ;If 0, return
425
426 ; --- Don't allow a diagonal skip ---
427
428 TST R10,#1 ;Has x changed sides?
429 BEQ %02constrain__circ ;No -- forget this check
430 LDR R14,ws__oldY ;Get the old Y value
431 SUB R3,R14,R5 ;Get distance from centre
432 MOV R2,R1 ;Get the new Y value
433 SUB R2,R2,R5 ;Get the new distance
434 EOR R3,R3,R2 ;Has sign bit changed?
435 TST R3,#(1<<31) ;Test it
436 BEQ %02constrain__circ ;No -- skip ahead
437 MOV R1,R14 ;Use this Y value
438 LDR R0,ws__oldX ;And this X value
439 SUB R14,R0,R4 ;Get the hz distance
440 MUL R7,R14,R14 ;And put the square in R7
441 SUB R14,R1,R5 ;Get the vt distance
442 MUL R8,R14,R14 ;And put the square in R8
443 ORR R10,R10,#2 ;Force a *mouse* update
444 ADDS R9,R7,R8 ;Dist from centre squared
445 BEQ %90constrain__circ ;If 0, return
446
447 ; --- Calculate corrected relative positions ---
448
449 02 MOV R2,R0 ;Look after the original...
450 MOV R3,R1 ;... mouse position
451
452 MOV R1,R9 ;We'll need to divide by this
453 MUL R0,R7,R6 ;Multiply up to give thing
454 BL divRound ;Do the main division
455 BL sqrt ;And take the square root
456 CMP R2,R4 ;Which side of the centre?
457 ADDGE R2,R4,R0 ;This is the x position
458 SUBLT R2,R4,R0 ;This is the x position
459
460 MOV R1,R9 ;We'll need to divide by this
461 MUL R0,R8,R6 ;Multiply up to give thing
462 BL divRound ;Do the main division
463 BL sqrt ;And take the square root
464 CMP R3,R5 ;Which side of the centre?
465 ADDGE R3,R5,R0 ;This is the y position
466 SUBLT R3,R5,R0 ;This is the y position
467
468 ; --- Make sure y isn't too low now ---
469
470 CMP R3,R11 ;Is the y value too low?
471 MOVLT R0,R2 ;Yes -- get the new x and...
472 MOVLT R1,R3 ;... y coordinates
473 BLT %10constrain__circ ;And skip back round (yeuch)
474
475 ; --- Store the newly modified mouse positions ---
476
477 STR R2,ws__oldX ;Store the new pointer...
478 STR R3,ws__oldY ;...positions away
479
480 ; --- Now move the mouse pointer position ---
481
482 MOV R0,#5 ;OS_Word subreason code
483 ORR R0,R0,R2,LSL #8 ;Move in the x position
484 ORR R0,R0,R3,LSL #24 ;And the bottom of the y
485 MOV R1,R3,LSR #8 ;Get the top of the y
486 STMFD R13!,{R0,R1} ;Save the block on the stack
487 MOV R0,#21 ;OS_Word main reason code
488 MOV R1,R13 ;Point to the new block thing
489 SWI XOS_Word ;Position the mouse pointer
490
491 ; --- Constrain the *mouse* to the circular band ---
492
493 SUBS R14,R9,R6 ;f(distance from circumf.)
494 RSBLT R14,R14,#0 ;Make sure it's positive
495 CMP R14,#256 ;Is this within the band?
496 CMPLE R10,#1 ;Make sure we didn't bodge
497 MOVGT R14,#3 ;No -- move mouse position
498 STRGTB R14,[R13,#0] ;Store new subreason code
499 SWIGT XOS_Word ;And move the position
500
501 ADD R13,R13,#8 ;Reclaim the OS_Word block
502 90 LDMFD R13!,{R0-R11,PC}^ ;Return to caller
503
504 LTORG
505
506 ; --- constrain__disc ---
507 ;
508 ; On entry: --
509 ;
510 ; On exit: --
511 ;
512 ; Use: Called on interrupts to constrin the mouse
513
514 constrain__disc ROUT
515 STMFD R13!,{R0-R11,R14} ;Stack some registers
516 SWI XOS_Mouse ;Get the current mouse pos
517 10 LDMIA R12,{R4-R6} ;Load interesting values
518 MOV R10,#0 ;Temporary flags word
519
520 ; --- Set R7 == horizontal distance from centre squared ---
521
522 SUB R14,R0,R4 ;Get distance in R14
523 MUL R7,R14,R14 ;Square it and copy
524 CMP R7,#&10000 ;Is it too big?
525 MOVGT R7,#&10000 ;Yes -- reduce (stop oflow)
526
527 ; --- Set R8 == vertical distance from centre squared ---
528
529 SUBS R14,R1,R5 ;Get distance in R14
530 00 MUL R8,R14,R14 ;Square it and copy
531 CMP R8,#&10000 ;Is it too big?
532 MOVGT R8,#&10000 ;Yes -- reduce (stop oflow)
533
534 01 ADDS R9,R7,R8 ;Dist from centre squared
535 BEQ %90constrain__disc ;If 0, return
536
537 ; --- Calculate corrected relative positions ---
538
539
540 02 MOV R2,R0 ;Look after the original...
541 MOV R3,R1 ;... mouse position
542
543 CMP R6,R9 ;Are we in the circle?
544 BGE %03constrain__disc ;Yes -- just move it then
545
546 MOV R1,R9 ;We'll need to divide by this
547 MUL R0,R7,R6 ;Multiply up to give thing
548 BL divRound ;Do the main division
549 BL sqrt ;And take the square root
550 CMP R2,R4 ;Which side of the centre?
551 ADDGE R2,R4,R0 ;This is the x position
552 SUBLT R2,R4,R0 ;This is the x position
553
554 MOV R1,R9 ;We'll need to divide by this
555 MUL R0,R8,R6 ;Multiply up to give thing
556 BL divRound ;Do the main division
557 BL sqrt ;And take the square root
558 CMP R3,R5 ;Which side of the centre?
559 ADDGE R3,R5,R0 ;This is the y position
560 SUBLT R3,R5,R0 ;This is the y position
561
562 ; --- Store the newly modified mouse positions ---
563
564 03 STR R2,ws__oldX ;Store the new pointer...
565 STR R3,ws__oldY ;...positions away
566
567 ; --- Now move the mouse pointer position ---
568
569 MOV R0,#5 ;OS_Word subreason code
570 ORR R0,R0,R2,LSL #8 ;Move in the x position
571 ORR R0,R0,R3,LSL #24 ;And the bottom of the y
572 MOV R1,R3,LSR #8 ;Get the top of the y
573 STMFD R13!,{R0,R1} ;Save the block on the stack
574 MOV R0,#21 ;OS_Word main reason code
575 MOV R1,R13 ;Point to the new block thing
576 SWI XOS_Word ;Position the mouse pointer
577
578 ; --- Constrain the *mouse* to the circular band ---
579
580 CMP R6,R9
581 MOVLT R14,#3 ;No -- move mouse position
582 STRLTB R14,[R13,#0] ;Store new subreason code
583 SWILT XOS_Word ;And move the position
584
585 ADD R13,R13,#8 ;Reclaim the OS_Word block
586 90 LDMFD R13!,{R0-R11,PC}^ ;Return to caller
587
588 LTORG
589
590 ; --- constrain_finish ---
591 ;
592 ; On entry: --
593 ;
594 ; On exit: --
595 ;
596 ; Use: Finishes a constraining operation
597
598 constrain_finish ROUT
599
600 STMFD R13!,{R0-R2,R14} ;Stack some registers
601
602 ; --- Check whether there's a constrain on ---
603
604 LDR R0,ws__routine ;Check the routine
605 CMP R0,#0 ;Is there one going?
606 LDMEQFD R13!,{R0-R2,PC}^ ;No -- do nothing then
607
608 ; --- Remove the routine ---
609
610 MOV R1,R12 ;Pass this R12 value
611 SWI XOS_RemoveTickerEvent ;Call every 50cs
612 MOV R0,#106 ;Change mouse parameters
613 MOV R1,#&1 ;Pointer 1, linked
614 SWI XOS_Byte ;Do the thing
615
616 ; --- Clear the routine ---
617
618 MOV R14,#0 ;Clear the pointer
619 STR R14,ws__routine ;Store that away
620 LDMFD R13!,{R0-R2,PC}^ ;Return to caller
621
622 ; --- constrain_mousePos ---
623 ;
624 ; On entry: --
625 ;
626 ; On exit: R0 == x coordinate of pointer
627 ; R1 == y coordinate of pointer
628 ;
629 ; Use: Returns the current mouse position during a constrain
630 ; operation.
631
632 constrain_mousePos ROUT
633
634 ADR R0,ws__oldX ;Point to the coordinates
635 LDMIA R0,{R0,R1} ;And read them out
636 MOVS PC,R14 ;Return to caller
637
638 LTORG
639
640 ;----- Workspace layout -----------------------------------------------------
641
642 ^ 0,R12
643
644 ws__wStart # 0 ;The start of the workspace
645 ws__parameters # 16 ;Parameters for the constrain
646 ws__routine # 4 ;The routine to call
647 ws__oldX # 4 ;The old X coordinate
648 ws__oldY # 4 ;The old Y coordinate
649
650 ws__wSize EQU {VAR}-ws__wStart ;The workspace size
651
652 ;----- That's all, folks ----------------------------------------------------
653
654 END