News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

Recent posts

#1
Based on the strong contrast between arcade games and average CPC games, I always wanted to examine the relation between frame rate and sprite movement speed.

The following program moves 23 sprites, each with a different frame rate. The initial movement speed is set to 1 pixel per frame, but can be changed with left and right cursor key between 1/32 pixel per frame up to 16 pixel per frame in steps of 1/32.

While the topmost sprite with a frame rate of 50 Hz stays stable even for high movement speeds, the sprites with lower frame rates get into trouble sooner or later.

NOLIST
ORG #A000
RUN program_begin



;; This program visualizes the relation between movement speed and frame rate.
;; Higher speeds combined with small frame rates lead to undesirable effects
;; like jumping movement, trails, and ragged object borders. These effects
;; cannot be avoided by double-buffering. Only a higher frame rate will
;; reduce them.
;;
;; See:
;;   - <https://en.wikipedia.org/wiki/Stroboscopic_effect>
;;   - <https://en.wikipedia.org/wiki/Wagon-wheel_effect>



ga_port EQU #7F00
ppi_port_b EQU #F500

os__key_code__escape       EQU #FC
os__key_code__cursor_left  EQU #F2
os__key_code__cursor_right EQU #F3

number_of_sprites EQU 23



program_begin:

;; Set mode 2 and clear screen.
LD A,2 : CALL os__scr_set_mode

;; Print row and column headers.
LD HL,row_and_column_headers : CALL print_string                ;; Print headers.
LD B,1                                                          ;; Sprite counter. Count upwards.
prach__loop:                                                    ;;
    LD H,4 : LD L,B : INC L : INC L : CALL os__txt_set_cursor   ;; Set cursor to (4|counter+2).
    LD A,B : CALL pad_number_with_space : CALL print_number     ;; Print counter with padding.
    LD A,10 : CALL os__txt_set_column                           ;; Set cursor to (10|*).
    LD HL,50 : LD A,B : CALL divide                             ;; Calculate 50/counter.
    PUSH AF                                                     ;;
        LD A,L : CALL pad_number_with_space : CALL print_number ;; Print quotient with padding.
        LD A,"." : CALL os__txt_output                          ;; Add a decimal point.
    POP AF                                                      ;;
    ADD A,A : LD C,A : ADD A,A : ADD A,A : ADD A,C              ;; Calculate remainder*10. Maximum remainder is 16 for 50/17.
    LD C,A : LD A,B : SRL A : ADD A,C                           ;; Calculate remainder*10+counter/2 for rounding.
    LD L,A : LD A,B : CALL divide                               ;; Calculate (remainder*10+counter/2)/counter.
    LD A,L : CALL print_number                                  ;; Print one decimal place.
INC B : LD A,B : CP A,number_of_sprites+1 : JR NZ,prach__loop   ;; Loop while counter less or equal 23.

;; Main loop.
main_loop:

    ;; Wait for VSYNC.
    LD B, ppi_port_b / 256                                           ;;
    vsyncIsAlreadyActive: IN A,(C) : RRA : JR C,vsyncIsAlreadyActive ;; Wait for end of VSYNC.
    waitForBeginOfVSync: IN A,(C) : RRA : JR NC,waitForBeginOfVSync  ;; Now wait for begin of VSYNC.

    ;; Wait longer until screen refresh has passed drawing area.
    HALT : HALT : HALT : HALT : HALT : HALT ;; Wait for 2+5*52 = 262 pixel lines.
    LD B,144 : DJNZ $ : NOP                 ;; Wait for 144*4/64 = 576/64 = 9 more pixel lines.

    ;; For debugging: mark begin of sprite update.
    IF 1                                 ;;
        DEFS 5                           ;; Align raster to begin of non-border region.
        LD BC, ga_port + #10 : OUT (C),C ;; Select pen of border.
        LD C,#55 : OUT (C),C             ;; Set ink "bright blue".
        LD A,8 : DEC A : JR NZ,$-1 : NOP ;; In combination with other commands: Wait 40 chars.
        LD C,#44 : OUT (C),C             ;; Set ink "dark blue".
    ENDIF                                ;;

    ;; Update sprite position.
    LD HL,(position) : LD DE,(speed) : ADD HL,DE ;; Update position.
    LD A,H : AND A,#3F : LD H,A                  ;; Restrict to a maximum byte offset of 63.
    LD (position),HL                             ;; Store position.

    ;; Prepare bit pattern for new position. This is at least used by the fastest row.
    ADD HL,HL : ADD HL,HL : ADD HL,HL : LD A,H : AND A,#07 ;; Extract bit position.
    LD BC,#FF00                                            ;; Start with set block at right.
    JR Z,pbp__skip                                         ;; Skip for offset 0.
        pbp__loop:                                         ;;
            SRL B : RR C                                   ;; Shift right.
        DEC A : JR NZ,pbp__loop                            ;;
    pbp__skip:                                             ;;
    LD IYH,B : LD IYL,C                                    ;; Store bit pattern in IY.

    ;; Update sprites.
    LD B,number_of_sprites                                          ;; Counter.
    LD DE,#0800                                                     ;; Offset from one scanline to next.
    LD HL, 80*2 + 15 + #C000                                        ;; Set sprite base address on screen to third row, character 15.
    LD IX,sprite_table                                              ;; Pointer to current sprite descriptor.
    ml__sprite_loop:                                                ;;
        LD A,(IX+sprite__counter) : DEC A : JR NZ,mlsl__not_at_zero ;; Decrement sprite counter. If not zero, skip sprite processing.
            ;;                                                      ;;
            PUSH HL                                                 ;;
                LD A,(IX+sprite__position)                          ;; Get old sprite position in bytes.
                ADD A,L : LD L,A : JR NC,$+3 : INC H                ;; Add to screen address.
                XOR A,A                                             ;; Set A to eight background pixels.
                ADD HL,DE                                           ;; Delete old sprite in region 2x4 bytes, starting at scanline 2.
                REPEAT 2                                            ;;
                    ADD HL,DE : LD (HL),A : INC HL : LD (HL),A      ;;
                    ADD HL,DE : LD (HL),A : DEC HL : LD (HL),A      ;;
                REND                                                ;;
            POP HL                                                  ;;
            ;;                                                      ;;
            LD A,(position+1) : LD (IX+sprite__position),A          ;; Get new sprite position. Store in sprite descriptor.
            ;;                                                      ;;
            PUSH BC : PUSH HL                                       ;;
                ADD A,L : LD L,A : JR NC,$+3 : INC H                ;; Add nes sprite position to screen address.
                LD B,IYH : LD C,IYL                                 ;; Set BC to bit pattern.
                ADD HL,DE                                           ;; Draw new sprite in region 2x4 bytes, starting at scanline 2.
                REPEAT 2                                            ;;
                    ADD HL,DE : LD (HL),B : INC HL : LD (HL),C      ;;
                    ADD HL,DE : LD (HL),C : DEC HL : LD (HL),B      ;;
                REND                                                ;;
            POP HL : POP BC                                         ;;
            ;;                                                      ;;
            LD A,number_of_sprites+1 : SUB A,B                      ;; Reset sprite counter. Calculate number_of_sprites + 1 - loop_counter.
            ;;                                                      ;;
        mlsl__not_at_zero:                                          ;;
        LD (IX+sprite__counter),A                                   ;; Store updated or reset sprite counter.
        LD A,L : ADD A,80 : LD L,A : JR NC,$+3 : INC H              ;; Go to next row on screen.
        INC IX : INC IX                                             ;; Go to next sprite descriptor.
    DJNZ ml__sprite_loop                                            ;;

    ;; For debugging: mark end of sprite update.
    IF 1                                  ;;
        LD BC, ga_port + #10 : OUT (C),C  ;; Select pen of border.
        LD C,#55 : OUT (C),C              ;; Set ink "bright blue".
        LD A,32 : DEC A : JR NZ,$-1 : NOP ;; Wait 128 chars.
        LD C,#44 : OUT (C),C              ;; Set ink "dark blue".
    ENDIF                                 ;;

    ;; Check keys.
    LD HL, 36 * 256 + 1 : CALL os__txt_set_cursor        ;; Set cursor to position of speed.
    LD HL,(speed)                                        ;; Load speed.
    CALL os__km_read_char : JP NC,main_loop              ;; Get last pressed key. Continue loop, if nothing was pressed.
    CP A,os__key_code__escape       : JR  Z,ml__leave    ;; Leave loop, if <Esc> has been pressed.
    CP A,os__key_code__cursor_left  : JR NZ,$+3 : DEC HL ;; Left cursor key decreases speed.
    CP A,os__key_code__cursor_right : JR NZ,$+3 : INC HL ;; Right cursor key increases speed.
    LD A,H : OR A,L : JR NZ,$+3 : INC HL                 ;; Minimum speed is  0|01 =   1.
    LD A,H : CP 2   : JR  C,$+3 : DEC HL                 ;; Maximum speed is 15|31 = 511.
    LD (speed),HL                                        ;; Store updated speed.
    LD B,L                                               ;; Store fraction in B.
        ADD HL,HL : ADD HL,HL : ADD HL,HL : LD A,H       ;; Extract integer speed.
        CALL pad_number_with_space : CALL print_number   ;; Print integer speed.
        LD A,"|" : CALL os__txt_output                   ;; Print separator.
    LD A,B : AND A,#1F                                   ;; Extract fractional speed.
    CALL pad_number_with_zero : CALL print_number        ;; Print fractional speed.

JP main_loop
ml__leave:

;; Set mode 1 and clear screen.
LD A,1 : CALL os__scr_set_mode

;; Program end.
RST #00     ;; Option 1: Reset system.
;;DI : HALT ;; Option 2: Halt system.
;;RET       ;; Option 3: Return to caller.



divide:
    ;; Divide 16-bit by 8-bit number.
    ;; Source: <https://wikiti.brandonw.net/index.php?title=Z80_Routines:Math:Division>
    ;;     Input:
    ;;         HL - dividend / numerator
    ;;         A  - divisor / denominator
    ;;     Output:
    ;;         HL - quotient
    ;;         A  - remainder
    ;;         *  - Everting else preserved.
    PUSH BC                                  ;;
        LD C,A                               ;;
        XOR A,A                              ;; Collected bits
        LD B,16                              ;; Number of bits, loop counter.
        d__loop:                             ;;
            ADD HL,HL : RLA                  ;; Shift HL to the left, transfer topmost bit to A.
            JR C,d__found_set_bit            ;;
                CP A,C                       ;; If the current bit is not set, and also not enough bits have
                JR C,d__smaller_than_divisor ;;   been collected in A, go on with collecting more bits.
            d__found_set_bit:                ;;
                SUB A,C                      ;; Reduce collected bits.
                INC L                        ;; Build up quotient. This will not interfere with the use as dividend.
            d__smaller_than_divisor:         ;;
        DJNZ d__loop                         ;;
    POP BC                                   ;;
    RET                                      ;;



binary_to_bcd:
    ;; Use "double dabble" algorithm to convert a binary number to BCD format.
    ;; See <https://en.wikipedia.org/wiki/Double_dabble> for more details.
    ;;     Input:
    ;;         A - Binary number in range 0..99.
    ;;     Output:
    ;;         A - BCD number in range 0..99.
    ;;         * - Everything else preserved.
    PUSH BC : PUSH HL                         ;;
        LD H,0 : LD L,A : SLA L               ;; Store binary number (7 bits) on right/lower side.
        LD B,7                                ;; Number of bits to process; loop counter.
        btb__loop:                            ;;
            LD A,H : AND A,#0F                ;; Get lower BCD digit.
            CP A,5 : JR C,$+4 : ADD A,3       ;; If greater or equal to 5, add 3.
            LD C,A                            ;; Save in C.
            LD A,H : AND A,#F0                ;; Get higher BCD digit.
            CP A,5*16 : JR C,$+4 : ADD A,3*16 ;; If greater or equal to 5, add 3.
            ADD A,C : LD H,A                  ;; Store both BCD digits in H.
            ADD HL,HL                         ;; Shift bits.
        DJNZ btb__loop                        ;;
        LD A,H                                ;; Move result to A.
    POP HL : POP BC                           ;;
    RET                                       ;;



print_string:
    ;; Input:
    ;;     HL - Address of a zero-terminated string.
    ;; Output:
    ;;     HL - Changed.
    ;;     A  - Changed.
    ;;     *  - Everyting else preserved.
    ps__loop:               ;;
        LD A,(HL) : INC HL  ;; Read next character.
        OR A,A : RET Z      ;; If zero, leave routine.
        CALL os__txt_output ;; Print character.
    JR ps__loop             ;;



print_number:
    ;; Input:
    ;;     A - Number.
    ;; Output:
    ;;     A - Changed.
    ;;     * - Everyting else preserved.
    CALL binary_to_bcd                              ;; Convert to BCD.
    PUSH AF                                         ;; Save lower digit.
        SRL A : SRL A : SRL A : SRL A               ;; Shift upper to lower digit.
        CALL NZ,pn__x                               ;; Print out upper digit, if not zero.
    POP AF                                          ;; Restore lower digit.
    pn__x:                                          ;;
        AND A,#0F : ADD A,"0" : CALL os__txt_output ;; Mask lower digit, convert to character, and print.
        RET                                         ;; Return to above call or to caller.



pad_number_with_space:
    ;; Input:
    ;;     A - Number.
    ;; Output:
    ;;     * - Everyting preserved.
    CP A,10 : RET NC
    PUSH AF
        LD A," " : CALL os__txt_output
    POP AF
    RET



pad_number_with_zero:
    ;; Input:
    ;;     A - Number.
    ;; Output:
    ;;     * - Everyting preserved.
    CP A,10 : RET NC
    PUSH AF
        LD A,"0" : CALL os__txt_output
    POP AF
    RET



os__scr_set_mode EQU #BC0E
    ;; Input:
    ;;     A - Screen mode in range [0..2].



os__txt_set_column:
    ;; Input:
    ;;     A - Column, one-based.
    ;; Output:
    ;;     * - Everyting preserved.
    PUSH AF : PUSH HL
        CALL #BB6F
    POP HL : POP AF
    RET



os__txt_set_cursor:
    ;; Input:
    ;;     H - Row, one-based.
    ;;     L - Column, one-based.
    ;; Output:
    ;;     * - Everyting preserved.
    PUSH AF : PUSH HL
        CALL #BB75
    POP HL : POP AF
    RET



os__txt_output EQU #BB5A
    ;; Input:
    ;;     A - Character to write at current cursor location.
    ;; Output:
    ;;     * - Everyting preserved.



os__km_read_char EQU #BB09
    ;; Input:
    ;;     -
    ;; Output:
    ;;     flag.C - If C, then key is returned.
    ;;     A      - If C, this is the returned key.
    ;;     *       - Everyting else preserved.



row_and_column_headers:
    ;;    12345678901234567890123456789012345678901234567890123456789012345678901234567890
    DEFB "  delay   FPS   speed [px/frame] :  1|00 (use keys LEFT and RIGHT)", 13, 10
    DEFB "[frames] [Hz]", 0



speed:
    ;; This stores the current speed in 1/256 bytes/frame.
    ;; Range is 1..511.
    DEFW 32 ;; Start value 32 corresponds to 1 pixel/frame.



position:
    ;; This stores the current sprite position in 1/256 bytes.
    ;; Range is 0..63*256.
    DEFW 0



sprite_table:
    sprite__counter EQU 0
    sprite__position EQU 1
    REPEAT number_of_sprites
        DEFB 1 ;; Current counter. Counting downwards.
        DEFB 0 ;; Sprite position in bytes. Range 0..63.
    REND
#2
avatar_lightforce6128
Games / Re: Turrican (128K)
Last post by lightforce6128 - Yesterday at 23:49
Some more ideas to speed up sprite drawing:

  • From Jean-Marie: Use B as loop counter and remove EX AF,AF'. => 287 NOPs saved.
  • From Jean-Marie: Shorten source skip assignment from LD BC to LD B. => 24 NOPs saved.
  • Increment of screen address is guaranteed not to cross a 256 bytes border. So INC HL can be replaced with INC L. => 144 NOPs saved.
  • Not for all, but for some sprites it is guaranteed that they are stored within a 256 bytes page (approximately half of them). In this cases INC DE can be replaced with INC E. => 144/2 = 72 NOPs saved.
  • Sprites are placed on a mode 1 chars grid. Often there is no vertical clipping. Then it is known in advance when the screen address will overflow. This does not need to be checked on every line. => 120 NOPs saved.
  • The screen address is updated with INC HL at the end of each row, although no more bytes will be drawn. => 48|24 NOPs saved.
  • Adding source skip is done with a 16-bit addition. This can be shortened with an 8-bit addition, and it can be shortened even more if the sprite data is inside one 256 bytes page. => 60 NOPs saved.
    EX DE,HL : LD BC,0 :source_skip : ADD HL,BC : EX DE,HL     ;; 8
    LD A,E : ADD A,0 :source_skip : LD E,A : JR NC,$+3 : INC D ;; 7
    LD A,E : ADD A,0 :source_skip : LD E,A                     ;; 4

In sum, these things would save 731 NOPs or 24% of sprite drawing time. And I think, even more than this is possible.
#3
avatar_MiguelSky
Games / Re: Alien Escape
Last post by MiguelSky - Yesterday at 23:36
Just in case your prefer to play in Spanish:
https://amstrad.es/doku.php?id=foraneos:alien_escape
#4
That's an interesting failure mode. The system is reading each video byte twice, but the second is corrupted.

Selecting between the two video bytes is done by the CCLK signal into IC105. This is a similar failure mode to that in the video posted earlier in the thread, but on a different address line. I'd look at IC105 (possibly swap with the other multiplexer chips) and the relevant traces into and out. I could be a broken trace or a poor solder joint. Maybe rework the pins on IC105, check for any shorts between traces and, perhaps, bodge wire pin 4 to the GA (or CRTC) to bypass a bad trace.

Since the CPU side of the RAM appears to be working that suggests the signals between IC105 and RAM are good.

Note that CCLK is also the clock signal into the 6845. Since the 6845 appears to be functioning properly that would indicate that CCLK is correct (unless it is, somehow, out of phase, which is unlikely).

As to why the second video byte is corrupted, that's harder to say. It could be a symptom of the first fault, faulty RAM, or a faulty GA. My money says it's on the first of those options. I'd recommend solving that issue and see if that fixes it.
#5
avatar_HAL6128
Games / Re: Alien Escape
Last post by HAL6128 - Yesterday at 22:42
Very nice game (graphics / ambience)! Well done.
#6
Clearly, the GA is getting the same video byte twice, and with inverted color interpretation. So this error is most likely in the GA. It would be best to check this with an oscilloscope to see if the CCLK, nCAS, and CAS ADDR signals are shifted and misaligning the LS153 multiplexers.

Try replug GA
#7

There you go. With pen 2/pen 3, now the left part of the characters repeats in yellow. In mode 2, every other character is missing.
#8
C
Games / Re: How about making a "CLASSI...
Last post by cwpab - Yesterday at 16:52
Congratulations to "TACHA", I assume my fellow spaniard, for the menu music of Red Planet. It's a perfect showcase of what the AY chip can do, with a total sound invasion filling up your ears slowly with the combination of the high and low pitch sounds. An example of music setting the mood of a game.
#9
avatar_GUNHED
Demos / Re: "Sunny Day" at Shadow Part...
Last post by GUNHED - Yesterday at 15:07
Well done! Great new art!!!  :) :) :)
#10
Update for the comprehensive M4 SD card manager available:
https://www.cpcwiki.eu/forum/programming/futureos-corner/msg252354/#msg252354

Enjoy!  :) :) :)
Powered by SMFPacks Menu Editor Mod