Mad Assembler (MADS) Documentation - Based on Drwal Game

This comprehensive guide provides an in-depth overview of key concepts, instructions, and directives for the Mad Assembler (MADS), which targets the MOS 6502 family of microprocessors used in computers like the Atari 8-bit.

This entire documentation is based on the analysis of the "Drwal" (Lumberjack) game source code - a complete 6502 assembly program written in Mad Assembler that demonstrates real-world usage of MADS features and advanced 6502 programming techniques.

What You'll Learn

  • Core 6502 processor concepts and registers
  • Essential assembly instructions and their usage
  • MADS-specific directives and features
  • Real-world programming patterns from the Drwal game
  • Advanced interrupt handling techniques
  • Graphics and sound programming on Atari 8-bit

About the Drwal Game

The Drwal (Lumberjack) game is a complete Atari 8-bit game written in 6502 assembly using MADS. It features:

  • Complex graphics with multiple character sets
  • Advanced sprite multiplexing using Display List Interrupts
  • Music and sound effects
  • Game logic with collision detection
  • Smooth animations and visual effects

Drwal.asm Code Walkthrough - Line by Line Mad Assembler Analysis

Interactive Mad Assembler Code Walkthrough

This chapter walks through the complete Drwal.asm source code line by line, with clickable links explaining each concept, instruction, and technique used. This is the best way to understand how a real Mad Assembler project is structured.

File Header and Comments

; Drwal
; skromna gra
;
; wersja: 2025.10.08

The file begins with standard comments describing the program. In Mad Assembler, comments start with ; and continue to the end of the line.

Include Files and Constants

;               etykiety
                icl 'etykiety.hea'

📖 icl directive - Includes another source file containing label definitions and constants.

;               deklaracja strony zerowej ($80 - $D3)
ZERO1           equ $0080   ; (2)
ZERO2           equ $0082   ; (2)
ZERO3           equ $0084   ; (2)
ZERO4           equ $0086   ; (2)
PAMY            equ $0088   ; (1)

📖 equ directive - Defines constants for zero-page memory locations. Zero-page addressing is faster on the 6502 processor.

Why Zero-Page Memory?

The 6502 processor can access memory locations $00-$FF (zero-page) faster than other addresses. Drwal reserves locations $80-$88 for frequently used pointers and variables. See Programming Patterns for more details on zero-page usage.

Program Origin

; ------------------------------------------------------------------------------------------------
;               początek
                org $2000                   ; początek programu w pamięci

📖 org directive - Sets the memory address where the program will be loaded. $2000 is a common starting address for Atari programs.

PMG Memory Layout

; ------------------------------------------------------------------------------------------------
;               pamięć PMG
pmg
;               obszar 3 stron (3x256B) niewykorzystanych przez PMG zagospodarowano na inne dane

📖 Label definition - Creates a label marking the start of Player/Missile Graphics memory area.

Graphics Data Definitions

;               dane PMG dla pozycji lewa góra
plr0_lg         .HE 80 c0 e4 fc fc e4 c0 c0 80 00 00 00 00 00 00 00 00 00 02 02 02 02 02 02 01 01 01 01 01 01
plr1_lg         .HE 20 60 60 60 00 60 60 60 60 60 60 60 60 60 60 60 60 60 28 28 28 68 68 68 64 04 04 04 64 64

📖 .HE directive - Defines hexadecimal sprite data for the lumberjack's axe in different positions. Each line contains 30 bytes of graphics data.

Understanding Sprite Data

The plr0_lg, plr1_lg, etc. arrays contain bitmap data for 4 hardware sprites (Player 0-3) showing the axe in "left-top" position. See Drwal Code Analysis for details on sprite multiplexing.

Color and Position Data

;               kolory i pozycja PMG toporek prawy górny
pmg_kol_pg      .HE 12 36 10 0a
pmg_poz_pg      .HE 8e 94 94 96

📖 Sprite attributes - Color values and horizontal positions for the 4 sprites when showing the axe in "right-top" position.

Game Data Structures

;               dane drzewa
drzewo          dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
                ; ?xxxx???
                ;        \\- kora: 0=66, 1=67
                ;       \\-- 1=gałąź po prawej
                ;      \\--- 1=gałąź po lewej
                ; \\-------- 1=koniec drzewa
pam_drzewa      dta 0,1,1,0,0,0,0,1,0,3,0,1,0,1,1,5,1,0,0,1

📖 dta directive - Defines the tree structure using bit-encoded data. Each byte represents one level of the tree with branches and bark type encoded in bits.

Bit-Encoded Game Data

This is a clever space-saving technique. Instead of using separate variables for each tree property, Drwal packs multiple boolean values into single bytes. See Programming Patterns for more bit manipulation techniques.

Game State Variables

;               pamięć danych joya
pam_joya        dta %00001100
;               pamięć pozycji drwala
pam_pozdrwala   dta 0,0
pozdrwala_atak  dta 0

📖 Game state storage - Variables tracking joystick state and player position. Note the binary literal %00001100 for the initial joystick state.

Lookup Tables

;               adresy rysowania pnia
lo_adr_pnia     dta <(EKRAN+(19*40)+18),<(EKRAN+(18*40)+18),<(EKRAN+(17*40)+18)...
hi_adr_pnia     dta >(EKRAN+(19*40)+18),>(EKRAN+(18*40)+18),>(EKRAN+(17*40)+18)...

📖 Lookup tables - Pre-calculated screen addresses for drawing tree trunk pieces. Uses < and > operators to extract low and high bytes.

Performance Optimization

Instead of calculating screen addresses at runtime (slow multiplication), Drwal pre-calculates all 20 addresses and stores them in tables. This is a classic 6502 optimization pattern.

PMG Graphics Data

                ; początek obszaru z faktycznymi danymi PMG
                org  pmg+$300
missile         .HE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00...
player0         .HE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00...

📖 PMG memory layout - Defines the actual Player/Missile Graphics data area. The org pmg+$300 positions this data at the correct hardware address.

Atari PMG System

The Atari has 4 "Player" sprites and 4 "Missile" sprites implemented in hardware. Drwal uses Display List Interrupts to reuse these sprites for clouds, axe, and snake graphics.

Font Data

;               wczytujemy fonty
FNTA
                ins 'grafika/fntA.fnt'
FNTB_0
                ins 'grafika/fntB_0.fnt'
FNTB_1
                ins 'grafika/fntB_1.fnt'

📖 ins directive - Inserts binary font files directly into the program. Drwal uses 3 different character sets for various display purposes.

Snake Animation Data

;               dane żmii
pzmija0_0       .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00 ; prawa
pzmija0_1       .HE 00 00 00 00 00 00 00 00 00 20 00 23 33 1c 14 08 00 ; prawa
lzmija0_0       .HE 00 00 00 00 00 00 00 00 00 00 01 02 c4 cc 38 20 00 ; lewa

📖 Animation frames - Multiple sprite patterns for snake animation. The 'p' prefix indicates right-facing, 'l' indicates left-facing movement.

Screen Memory

;               ekran
                .align $1000                ; wyrównaj kod do pełnych 4KB
EKRAN
                ins 'grafika/drwal.sved2'

📖 .align directive - Ensures screen memory starts on a 4KB boundary, required by Atari graphics hardware.

Display List

; ------------------------------------------------------------------------------------------------
;               DISPLAY LIST
DISPLAY_LIST
                dta $70
                dta %11110000                   ; +DLI-2 (DLI_2)
                dta %11110000                   ; +DLI-1 (DLI_1)
                dta %01000100,a(EKRAN)          ;  LINIA 0
                dta %10000100                   ; +DLI1

📖 Display List structure - Defines how the Atari displays graphics. Lines starting with %1 trigger Display List Interrupts for special effects.

Advanced Graphics Programming

The Display List is the heart of Atari graphics. Each byte controls one screen line, and the high bit enables interrupts. See Interrupts section for detailed explanation of how Drwal uses DLIs.

Main Program Entry

; ------------------------------------------------------------------------------------------------
;               PROGRAM
START
                ; wygaszamy ekran
                jsr OBRAZ_OFF
                ; ekran tytułowy
                jsr EKRAN_TYTULOWY
                ; inicjowanie gry
                jsr INICJOWANIE
                ; ekran gry
                jmp EKRAN_GRY

📖 Program structure - The main entry point uses JSR (Jump to Subroutine) to call initialization routines, then JMP (Jump) to start the game.

Structured Programming in Assembly

Even in assembly language, Drwal follows good programming practices by organizing code into logical subroutines. This makes the code easier to understand and maintain. See Programming Patterns for more examples.

Display List Interrupts

; ------------------------------------------------------------------------------------------------
;               PRZERWANIA DISPLAY LIST
dli
dli_2
                pha
                ; szerokość chmur
                lda #%00000001              ; szerokość graczy podwójna
                sta SIZEP0
                sta SIZEP1
                ; pozycja chmur linii 0
                lda pchmur0_0:#83
                sta HPOSP0
                pla
                rti

📖 Display List Interrupt - This routine runs when the electron beam reaches a specific screen line. It:

  • PHA - Saves the accumulator (interrupt safety)
  • Sets sprite width - Makes sprites double-width for cloud effect
  • Sets sprite positions - Positions clouds on screen
  • PLA/RTI - Restores accumulator and returns from interrupt

Interrupt Safety

Notice the PHA at the start and PLA before RTI. This is crucial for interrupt programming - always save and restore any registers you modify in an interrupt routine.

Vertical Blank Interrupt

; ------------------------------------------------------------------------------------------------
;               DVBI - przerwanie wygaszania pionowego opóźnionego
dvbi
                ; licznik chmur linia 0
                lda lch0:#0
                bne @+
                lda #2                      ; prędkość chmur
                sta lch0
                dec pchmur0_0
@               dec lch0

📖 Vertical Blank Interrupt - The game's main timing loop that runs 50/60 times per second. This section handles cloud animation using:

Key Subroutines

; ------------------------------------------------------------------------------------------------
;               RYS_DRWALA (X)
; X: 0-3 - numer pozycji drwala: 0=lewa-góra, 1=prawy-dół, 2=prawa-góra, 3=lewy-dół
RYS_DRWALA
poz_3           cpx #3
                bne poz_2
                jsr KAS_DRWALA_P
                jsr RYS_TOPOR_LD
                rts
poz_2           cpx #2
                bne poz_1
                jsr KAS_DRWALA_L
                rts

📖 Conditional logic chain - This subroutine demonstrates the assembly equivalent of if/else statements:

  • CPX - Compare X register with immediate values
  • BNE - Branch if not equal (fall-through logic)
  • JSR - Call appropriate drawing subroutines
  • RTS - Return from subroutine

Memory Copy with Indirect Addressing

; ------------------------------------------------------------------------------------------------
;               RYSUJ PIEŃ (X,Y)
RYS_PIEN
                ; ustalamy adres rysowania
                lda lo_adr_pnia,y
                sta ZERO1
                lda hi_adr_pnia,y
                sta ZERO1+1

                ; rysujemy pień
                ldy #0
@               lda pien,y
                sta (ZERO1),y
                iny
                cpy #4
                bne @-
                rts

📖 Lookup tables + Indirect addressing - This routine demonstrates advanced 6502 techniques:

  • Lookup tables - lo_adr_pnia,y and hi_adr_pnia,y get pre-calculated addresses
  • Zero-page pointers - ZERO1 holds the 16-bit target address
  • Indirect addressing - (ZERO1),y writes to the calculated address
  • Countdown loop - Standard pattern for copying 4 bytes

Advanced 6502 Programming

This routine showcases why the 6502 was so powerful for its time. The combination of lookup tables, zero-page addressing, and indirect addressing allows for very efficient graphics operations. See Programming Patterns for more examples.

Navigation Links

Continue Learning

This walkthrough covers the essential structure of the Drwal game. To dive deeper into specific topics:

Core Concepts - Mad Assembler (MADS) for 6502

Mad Assembler Documentation - Based on Drwal Game Analysis

All concepts and examples in this section are derived from the Drwal game source code, showing real-world Mad Assembler usage.

Registers

The 6502 CPU has three primary user registers:

Register Name Purpose Example Usage
A Accumulator Primary register for arithmetic and logical operations LDA #$10 - Load immediate value
X Index Register X Counter in loops, offset for memory addressing LDA address,X - Indexed addressing
Y Index Register Y Counter in loops, offset for memory addressing STA (pointer),Y - Indirect indexed

Status Flags

The Status Register holds single-bit flags that are automatically set or cleared by instructions. Branch instructions use these flags to make decisions.

Flag Name Set (1) if... Clear (0) if...
N Negative Result bit 7 is 1 (negative) Result bit 7 is 0 (positive)
Z Zero Result is exactly zero Result is not zero
C Carry Unsigned add > 255 or no borrow needed Unsigned add ≤ 255 or borrow occurred
V Overflow Signed arithmetic result is invalid Signed arithmetic result is valid

Important Note

Understanding these flags is crucial for conditional branching. Most arithmetic and logical operations affect these flags automatically.

6502 Instructions - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

All instruction examples are taken directly from the Drwal game source code, showing real-world Mad Assembler usage.

Data & Memory Operations

Load Instructions

LDA, LDX, LDY: Load a value into the A, X, or Y register.

LDA #$10    ; Load A with the immediate hexadecimal value 16
LDX myVar   ; Load X with the value from the memory address 'myVar'
LDY $D018   ; Load Y with value from hardware address $D018

Store Instructions

STA, STX, STY: Store the value from A, X, or Y into a memory address.

STA $D018   ; Store contents of A at hardware address $D018
STX ZERO1   ; Store X register at zero-page location ZERO1
STY COLBAK  ; Store Y register at background color register

Increment/Decrement Instructions

INC, INX, INY: Increment (add 1 to) a value in memory or register.

DEC, DEX, DEY: Decrement (subtract 1 from) a value in memory or register.

INX         ; Increment X register
DEY         ; Decrement Y register
INC power   ; Increment memory location 'power'
DEC $80     ; Decrement zero-page location $80

Compare Instructions

CMP, CPX, CPY: Compare register value with memory. Sets flags without changing the register.

CMP #80     ; Is the value in A equal to 80?
BEQ is_equal; Branch if it is
CPX #3      ; Compare X with 3
BNE not_three ; Branch if X is not 3

Arithmetic & Logic

Addition and Subtraction

ADC (Add with Carry): A = A + value + C. Always use CLC before starting an addition chain.

SBC (Subtract with Carry): A = A - value - (1-C). Always use SEC before starting a subtraction chain.

CLC         ; Clear carry flag
ADC #10     ; Add 10 to accumulator
ADC value   ; Add memory value to accumulator

SEC         ; Set carry flag
SBC #5      ; Subtract 5 from accumulator

Logical Operations

AND, ORA, EOR: Perform bitwise operations between accumulator and memory.

AND #%11110000  ; Mask lower 4 bits
ORA #%00001111  ; Set lower 4 bits
EOR #%10101010  ; Toggle alternating bits

Bit Shifting

ASL, LSR, ROL, ROR: Bit-shift instructions for fast multiplication/division by 2.

ASL A       ; Arithmetic Shift Left (multiply by 2)
LSR A       ; Logical Shift Right (divide by 2)
ROL value   ; Rotate Left through carry
ROR $80     ; Rotate Right through carry

Conditional Branching - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Branching examples demonstrate real conditional logic patterns used in the Drwal game.

Branches alter program flow based on the status flags. They take an address or a label as an argument.

Instruction Name Branches if... Flag Condition
BCC Branch if Carry Clear Unsigned ≥ C = 0
BCS Branch if Carry Set Unsigned < C = 1
BEQ Branch if Equal Result is Zero Z = 1
BNE Branch if Not Equal Result is Not Zero Z = 0
BMI Branch if Minus Signed is Negative N = 1
BPL Branch if Plus Signed is Positive N = 0
BVC Branch if Overflow Clear Signed math is valid V = 0
BVS Branch if Overflow Set Signed math is invalid V = 1

Practical Examples

Simple Comparison and Branch

LDA player_lives
CMP #0
BEQ game_over    ; Branch if no lives left
; Continue game logic here

game_over:
    ; Game over routine
    JSR SHOW_GAME_OVER
    RTS

Loop with Counter

LDX #10          ; Loop 10 times
loop_start:
    ; Do something here
    DEX              ; Decrement counter
    BNE loop_start   ; Branch back if not zero
; Continue after loop

Range Checking

LDA player_x
CMP #240         ; Check if at right edge
BCS at_edge      ; Branch if carry set (≥ 240)
CMP #16          ; Check if at left edge
BCC at_edge      ; Branch if carry clear (< 16)
; Player is in valid range
JMP continue

at_edge:
    ; Handle edge collision
    JSR BOUNCE_PLAYER

continue:
    ; Continue game logic

Branch Range Limitation

Branch instructions can only jump -128 to +127 bytes from the current location. For longer jumps, use JMP instruction or combine branches with jumps.

Jumps & Coroutines - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Understanding program flow control through jumps, subroutines, and coroutine-like patterns demonstrated in the Drwal game source code.

Types of Jumps in 6502 Assembly

The 6502 processor provides several ways to change program flow. Each serves different purposes and has different effects on the stack and processor state.

1. JMP (Unconditional Jump)

Purpose: Transfer control to another location without saving return address.

; Example from Drwal main program flow
START:
                jsr OBRAZ_OFF      ; Turn off display
                jsr EKRAN_TYTULOWY ; Show title screen
                jsr INICJOWANIE    ; Initialize game
                jmp EKRAN_GRY      ; Jump to main game (never returns)

; Infinite loop pattern
petla:          lda RANDOM
                sta COLOR3
                jmp petla          ; Loop forever

JMP vs JSR

JMP doesn't use the stack - it's a one-way transfer. Use it for:

  • Main program flow (like jumping to the game loop)
  • Infinite loops
  • Error handling where you don't want to return

2. JSR/RTS (Subroutine Calls)

Purpose: Call reusable code blocks that return to the caller.

; Example from Drwal drawing routines
RYS_DRWALA:
poz_3:          cpx #3
                bne poz_2
                jsr KAS_DRWALA_P    ; Clear right side
                jsr RYS_TOPOR_LD    ; Draw left-bottom axe
                jsr RYS_DRWALA_L    ; Draw left lumberjack
                jsr KAS_GALAZ_P     ; Clear right branch
                rts                 ; Return to caller

; Subroutine implementation
KAS_DRWALA_P:
                ldy #3
                lda #0
@               sta EKRAN+(17*40)+23-1,y
                sta EKRAN+(18*40)+23-1,y
                dey
                bne @-
                rts                 ; Return to RYS_DRWALA

3. Conditional Branches

Purpose: Short-range conditional jumps based on processor flags.

; Example from Drwal input handling
spr_joy:
                jsr JOYFIRKEY       ; Read joystick
                tya
                cmp pam_joya        ; Compare with previous state
                beq spr_joy         ; Branch back if no change

                ; Handle input change
                sta pam_joya
                ; ... process input ...

Branch Range Limitation

Branches can only jump -128 to +127 bytes. For longer distances, combine with JMP:

                bne skip_far
                jmp distant_label
skip_far:       ; Continue here

Coroutine-Like Patterns in Drwal

While the 6502 doesn't have built-in coroutine support, Drwal demonstrates several patterns that achieve similar cooperative multitasking effects.

1. Interrupt-Driven Coroutines

The most sophisticated example is how VBI and DLI interrupts work together:

; Main program "coroutine" - handles input
GRAMY:
spr_joy:        jsr JOYFIRKEY
                cmp pam_joya
                beq spr_joy         ; "Yield" until input changes
                ; Process input...
                jmp spr_joy         ; Return to waiting state

; VBI "coroutine" - handles timing and animation
dvbi:
                ; Cloud animation
                lda lch0
                bne @+
                lda #2
                sta lch0
                dec pchmur0_0       ; Move clouds
@               dec lch0

                ; Snake animation
                lda zmija_idzie
                bne @+
                jmp obslzmii_exit   ; "Yield" if snake not moving
@               ; Animate snake...

                jmp XITVBV          ; "Yield" back to main program

Cooperative Multitasking

This pattern creates the illusion of multiple tasks running simultaneously:

  • Main program: Handles input, "yields" when no changes
  • VBI interrupt: Handles animation, "yields" back after each frame
  • DLI interrupts: Handle graphics effects, "yield" after each scanline

2. State Machine Coroutines

Drwal uses state-based execution that resembles coroutines:

; Snake state machine acts like a coroutine
obsluga_zmii:
                lda zmija_idzie
                bne @+
                jmp obslzmii_exit   ; "Yield" - snake is idle

@               ; Snake is active - check animation state
                lda klatka_zmii
                cmp #4
                beq attack_state    ; "Yield" to attack coroutine
                cmp #5
                bne move_state      ; "Yield" to movement coroutine

attack_state:   ; Handle attack logic
                lda pozdrwala_atak
                cmp pam_pozdrwala
                bne @+
                ldx #1              ; Set poison state
                stx jad
@               lda #0
                sta klatka_zmii
                jmp animation_done  ; "Yield" back to main flow

move_state:     ; Handle movement logic
                lda kierunek_zmii
                beq move_left       ; "Yield" to left movement
                ; Move right...
                jmp animation_done

move_left:      ; Left movement "coroutine"
                dec pzmii_0
                dec pzmii_1
                ; ...

animation_done: ; "Yield" back to caller
                rts

3. Timer-Based Coroutines

Drwal implements timer-driven cooperative scheduling:

; Multiple timer "coroutines" in VBI
dvbi:
                ; Cloud coroutine 1
                lda lch0
                bne cloud1_yield
                lda #2
                sta lch0
                ; Do cloud 1 work...
cloud1_yield:   dec lch0

                ; Cloud coroutine 2
                lda lch1
                bne cloud2_yield
                lda #3
                sta lch1
                ; Do cloud 2 work...
cloud2_yield:   dec lch1

                ; Power decrease coroutine
                lda spadek_mocy
                bne power_yield
                lda power
                beq power_yield
                dec power           ; Decrease power
power_yield:    dec spadek_mocy

                ; Music coroutine
                jsr RASTERMUSICTRACKER+3  ; Always runs

                jmp XITVBV          ; "Yield" back to main

Advanced Jump Techniques

1. Jump Tables (Computed Jumps)

Drwal uses computed jumps for efficient multi-way branching:

; Position-based jump table pattern
RYS_DRWALA:
                ; X contains position (0-3)
                cpx #3
                beq pos_3_handler
                cpx #2
                beq pos_2_handler
                cpx #1
                beq pos_1_handler
                ; Fall through to pos_0_handler

pos_0_handler:  jsr KAS_DRWALA_P
                jsr RYS_TOPOR_LG
                jsr RYS_DRWALA_L
                rts

pos_1_handler:  jsr KAS_DRWALA_L
                jsr RYS_TOPOR_PD
                jsr RYS_DRWALA_P
                jsr KAS_GALAZ_L
                rts

; More efficient jump table approach:
; (Not used in Drwal but shown for completeness)
JUMP_TABLE_EXAMPLE:
                txa                 ; X = position
                asl                 ; Multiply by 2 (word addresses)
                tax
                lda jump_table,x
                sta jump_addr
                lda jump_table+1,x
                sta jump_addr+1
                jmp (jump_addr)     ; Indirect jump

jump_table:     dta pos_0_handler
                dta pos_1_handler
                dta pos_2_handler
                dta pos_3_handler

2. Indirect Jumps

Using memory locations to store jump addresses:

; Interrupt vector setup (indirect jump pattern)
EKRAN_GRY:
                ; Set up VBI vector
                ldy dvbi           ; High byte of routine address
                lda #$07
                jsr SETVBV          ; OS sets up indirect jump

                ; Set up DLI vector
                ldx dli            ; High byte
                stx VDSLST          ; Store in interrupt vector
                sty VDSLST+1        ; Hardware uses JMP (VDSLST)

Performance Considerations

Instruction Cycles Stack Effect Best Use
JMP absolute 3 None Main program flow, loops
JMP indirect 5 None Jump tables, vectors
JSR 6 Push return address Subroutine calls
RTS 6 Pull return address Return from subroutine
Branch (taken) 3 None Short conditional jumps
Branch (not taken) 2 None Fall-through logic

Stack Management

Always balance JSR with RTS. Unbalanced calls will corrupt the stack and crash the program. The 6502 stack is only 256 bytes, so avoid deep recursion.

Modern Programming Parallels

Drwal's Patterns in Modern Terms

  • VBI interrupt: Similar to setInterval() in JavaScript
  • DLI chain: Like CSS animations with keyframes
  • Input polling: Similar to event loops in Node.js
  • State machines: Like Redux reducers or state management
  • Timer coroutines: Like async/await with setTimeout

Best Practices

  • Use JSR/RTS for reusable code blocks
  • Use JMP for main program flow and infinite loops
  • Use branches for short conditional jumps
  • Keep subroutines focused - one task per routine
  • Document jump tables clearly with comments
  • Balance stack operations - every JSR needs an RTS
  • Use interrupts for time-critical or repetitive tasks
  • Implement state machines for complex game logic

Mad Assembler (MADS) Directives - Examples from Drwal Game

Mad Assembler Documentation - Based on Drwal Game Analysis

These MADS-specific directives are demonstrated through actual usage in the Drwal game source code.

Directives are commands for the assembler, not the CPU. They define data and control the assembly process.

Basic Directives

equ (Equate)

Assigns a constant value to a label, improving code readability.

ZERO1 equ $80    ; Now we can use 'ZERO1' instead of address $80
SCREEN_WIDTH equ 40
PLAYER_SPEED equ 2

org (Origin)

Sets the memory address where the following code or data will be located.

org $2000        ; Assemble the following code starting at address $2000
START:
    LDA #0       ; This instruction will be at $2000

Data Definition

dta (Define Data)

Stores one or more 8-bit values. Can define numbers, text strings, or a mix.

tekst0 dta d'ETAP',$61,d'ZALICZONY'  ; String with hex value
drzewo dta 0,1,1,0,0,1,0             ; Sequence of bytes
lives  dta 3                         ; Single byte value

.HE (Store Hex Bytes)

Stores a sequence of hex values without requiring the $ prefix.

pmg_colors .HE 12 36 10 0a    ; Stores $12, $36, $10, $0A
sprite_data .HE 00 18 3c 7e ff 7e 3c 18 00

Memory Alignment

.align

Aligns the next line of code or data to a specified memory boundary.

.align $1000     ; Ensure next data starts on 4KB boundary
SCREEN_DATA:
    ins 'graphics.dat'

File Operations

icl (Include)

Inserts another source code file at the current location.

icl 'constants.asm'    ; Include constants file
icl 'subroutines.asm'  ; Include subroutines

ins (Insert)

Inserts a raw binary file directly into the compiled output.

FONT_DATA:
    ins 'font.fnt'     ; Insert font binary data
MUSIC_DATA:
    ins 'song.rmt'     ; Insert music data

run

Sets the program's starting execution address in the file header.

run START          ; Program starts at START label

Address Manipulation

MADS uses the < and > operators to get the low and high bytes of a 16-bit address.

; Set Display List pointer (16-bit address)
ldx DISPLAY_LIST    ; Load Y with high byte
stx DLPTRS          ; Store low byte
sty DLPTRS+1        ; Store high byte

Example from Drwal Game

; Memory layout from drwal.asm
ZERO1    equ $0080    ; Zero page pointer (2 bytes)
ZERO2    equ $0082    ; Zero page pointer (2 bytes)

org $2000            ; Program starts at $2000

; Data definitions
power    dta 40      ; Player power level
drzewo   dta 0,0,0,0,0,0,0,0,0,0  ; Tree data array

; Graphics data
pmg_colors .HE 12 36 10 0a  ; PMG color data

; Font inclusion
FONT_A:
    ins 'grafika/fntA.fnt'

; Screen memory alignment
.align $1000
SCREEN:
    ins 'grafika/drwal.sved2'

Labels in Mad Assembler (MADS) - Examples from Drwal Game

Mad Assembler Documentation - Based on Drwal Game Analysis

Label usage patterns and MADS-specific anonymous label features demonstrated in the Drwal game.

Labels are human-readable names for memory addresses, making code more maintainable and readable.

Named Labels

Used for subroutines, variables, and major loop entry points. They must start in the first column.

RYS_DRWALA:          ; A label for a subroutine
    cpx #3
    bne check_pos_2
    jsr DRAW_LEFT
    rts

check_pos_2:
    cpx #2
    bne check_pos_1
    jsr DRAW_RIGHT
    rts

game_data:           ; Label for data
    dta 1,2,3,4,5

Anonymous Labels

Used for short, local jumps to avoid cluttering code with unnecessary names.

  • @: - Defines an anonymous label
  • @- - Jumps backwards to the most recent @:
  • @+ - Jumps forwards to the next @:

Example: Backward Jump (Loop)

ldy #0
@:                   ; Define anonymous label for loop start
    lda drzewo+1,y
    sta drzewo,y
    iny
    cpy #19
    bne @-           ; Jump back to '@:' above until Y is 19

Example: Forward Jump (Skip)

cpx #0
    beq @+           ; If X is 0, jump forward over next part

    lda #$FF         ; This part is skipped if X was 0
    sta SOMEWHERE
@:                   ; Execution continues here

Label Scope and Organization

Best Practices

  • Use descriptive names for major subroutines: DRAW_PLAYER, CHECK_COLLISION
  • Use anonymous labels for simple loops and short conditional jumps
  • Group related labels with prefixes: PLAYER_INIT, PLAYER_MOVE, PLAYER_DRAW
  • Use ALL_CAPS for constants and subroutines
  • Use lowercase for local variables and temporary labels

Examples from Drwal Game

; Main subroutine labels
START:               ; Program entry point
EKRAN_GRY:          ; Game screen routine
RYS_DRWALA:         ; Draw lumberjack routine
PAUSE:              ; Pause routine

; Position checking with anonymous labels
RYS_DRWALA:
poz_3:  cpx #3
        bne poz_2
        jsr KAS_DRWALA_P
        jsr RYS_TOPOR_LD
        rts
poz_2:  cpx #2
        bne poz_1
        jsr KAS_DRWALA_L
        rts

; Loop with anonymous labels
KAS_DRWALA_L:
        ldy #3
        lda #0
@:      sta EKRAN+(17*40)+14-1,y
        sta EKRAN+(18*40)+14-1,y
        dey
        bne @-
        rts

Label Naming Rules

  • Labels must start with a letter or underscore
  • Can contain letters, numbers, and underscores
  • Case sensitive
  • Cannot be the same as instruction mnemonics
  • Named labels must start in column 1

6502 Programming Patterns - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Common 6502 programming patterns and techniques demonstrated through real examples from the Drwal game source code.

This section outlines common programming patterns used in 6502 assembly, demonstrated through examples from the Drwal game.

1. The Countdown Loop

The most common way to repeat an action a fixed number of times.

How it Works:

  1. Load an index register (X or Y) with the number of iterations
  2. Perform the action inside the loop
  3. Decrement the register (DEX or DEY)
  4. Use BNE to jump back until the register becomes zero
; Example from KAS_DRWALA_L (Clear lumberjack graphics)
                ldy #3           ; Loop 4 times (3,2,1,0)
                lda #0           ; Value to store (clear)
@               sta EKRAN+(17*40)+14-1,y  ; Clear line 17
                sta EKRAN+(18*40)+14-1,y  ; Clear line 18
                sta EKRAN+(19*40)+14-1,y  ; Clear line 19
                dey              ; Decrement counter
                bne @-           ; Branch back if not zero
                rts

2. Polling for Input

6502 systems don't have event listeners. Code must actively check hardware state in a loop.

; Example from GRAMY (Main game loop)
spr_joy:
                jsr JOYFIRKEY    ; Read joystick into Y
                tya              ; Transfer to A for comparison
                cmp pam_joya     ; Compare with last known state
                beq spr_joy      ; If EQUAL, no change, loop back

                ; Change detected!
                sta pam_joya     ; Store NEW state for next frame
                ; Handle the input change here...

Key Points:

  • Store previous state to detect changes
  • Only act when state changes, not every frame
  • This prevents multiple actions from single input

3. Lookup Tables for Speed

Pre-calculate complex values and store them in tables for instant access.

; Address tables for drawing tree trunk
lo_adr_pnia  dta <(EKRAN+(19*40)+18),<(EKRAN+(18*40)+18),...
hi_adr_pnia  dta >(EKRAN+(19*40)+18),>(EKRAN+(18*40)+18),...

; Usage in RYS_PIEN
RYS_PIEN:
                lda lo_adr_pnia,y    ; Get low byte from table
                sta ZERO1            ; Store in zero-page pointer
                lda hi_adr_pnia,y    ; Get high byte from table
                sta ZERO1+1          ; Complete the pointer
                ; Now ZERO1 points to correct screen location

4. Indirect Addressing with Zero-Page Pointers

Use zero-page locations as pointers to access calculated addresses.

; ZERO1 has been set up by lookup table above
                ldy #0
@               lda pien,y           ; Get trunk character
                sta (ZERO1),y        ; Store at address in ZERO1 + Y
                iny
                cpy #4               ; 4 characters wide
                bne @-

Zero-Page Advantage

Zero-page addressing ($00-$FF) is faster and uses less memory than absolute addressing. Always use zero-page for frequently accessed pointers and variables.

5. Conditional Logic Chain (If-ElseIf-Else)

Assembly has no if/else statements. Build logic using comparisons and branches.

; Example from RYS_DRWALA (Draw lumberjack at position)
RYS_DRWALA:
poz_3:          cpx #3          ; Is position 3?
                bne poz_2       ; If not, check position 2
                ; Code for position 3
                jsr KAS_DRWALA_P
                jsr RYS_TOPOR_LD
                rts

poz_2:          cpx #2          ; Is position 2?
                bne poz_1       ; If not, check position 1
                ; Code for position 2
                jsr KAS_DRWALA_L
                jsr RYS_TOPOR_PG
                rts

poz_1:          ; Handle remaining cases...

6. State Machines

Use variables to track current state and jump tables for state transitions.

; Game state handling
game_state      dta 0    ; 0=menu, 1=playing, 2=paused, 3=game_over

MAIN_LOOP:
                lda game_state
                asl              ; Multiply by 2 for word addresses
                tax
                lda state_table,x
                sta jump_addr
                lda state_table+1,x
                sta jump_addr+1
                jmp (jump_addr)  ; Jump to current state handler

state_table:
                dta MENU_STATE
                dta PLAY_STATE
                dta PAUSE_STATE
                dta GAMEOVER_STATE

Atari 8-bit Interrupts - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Advanced interrupt programming techniques for Atari 8-bit systems, demonstrated through the sophisticated interrupt system used in the Drwal game.

Understanding Interrupts vs. Modern Computing

Why the Joystick Loop Doesn't Hang the Game

Modern computers use event-driven programming with callbacks and event listeners. When you click a button, the OS sends an event to your program.

6502 systems like the Atari don't have this luxury. Instead, they use interrupts - hardware-triggered events that can "interrupt" the main program at any time to handle time-critical tasks.

The Key Insight: While the main program loops checking the joystick, interrupts continue running in the background:

  • VBI (Vertical Blank Interrupt) runs 50/60 times per second, handling game timing, animation, and music
  • DLI (Display List Interrupts) run during screen drawing, creating visual effects
  • The main program can loop forever checking input because interrupts ensure the game keeps running smoothly

Comparison: Modern vs. 6502 Programming

Aspect Modern Programming 6502/Atari Programming
Input Handling Event listeners, callbacks Polling loops with interrupt support
Timing Timers, requestAnimationFrame VBI interrupt (50/60 Hz)
Graphics GPU, graphics APIs DLI interrupts, hardware registers
Multitasking OS scheduler, threads Interrupt-driven cooperative multitasking

The Drwal game uses two types of interrupts to achieve complex graphical effects and maintain consistent timing.

How Drwal's Input System Works

Let's examine how the Drwal game handles input without hanging, demonstrating the power of interrupt-driven programming:

; Main game loop from Drwal
GRAMY:
spr_joy:
                jsr JOYFIRKEY    ; Read joystick/keyboard into Y
                tya              ; Transfer to A for comparison
                cmp pam_joya     ; Compare with last known state
                beq spr_joy      ; If EQUAL, no change, so loop back

                ; Change detected - handle input
                sta pam_joya     ; Store NEW state for next frame
                ; ... process the input change ...

Why This Loop Doesn't Freeze the Game

At first glance, the beq spr_joy instruction creates what looks like an infinite loop when no input changes. However:

  • VBI interrupts continue firing every 1/50th or 1/60th of a second
  • DLI interrupts continue firing during screen drawing
  • These interrupts handle all game logic, animation, music, and graphics
  • The main loop only handles input changes - everything else runs via interrupts

Result: The game appears to run smoothly even when the main program is "stuck" in the input loop!

1. VBI (Vertical Blank Interrupt) - The Game's Heartbeat

The VBI runs once per frame (50/60 times per second) and handles all time-critical game logic.

Setup Code

; From EKRAN_GRY procedure
                ldy dvbi        ; Load high byte
                lda #$07         ; Deferred VBI mode
                jsr SETVBV       ; Register the interrupt

VBI Routine Structure

dvbi:
                ; Move clouds at different speeds
                lda lch0
                bne @+
                lda #2           ; Cloud speed
                sta lch0
                dec pchmur0_0    ; Move cloud positions
                dec pchmur0_1
@               dec lch0

                ; Decrease power bar over time
                lda spadek_mocy
                bne @+
                lda power
                beq @+
                dec power        ; Reduce player power
@
                ; Handle snake logic and animation
                ; ... (snake movement and collision code)

                ; Play background music
                jsr RASTERMUSICTRACKER+3

                ; Exit interrupt
                jmp XITVBV

VBI: The Secret to Smooth Gameplay

The VBI interrupt is what makes the "endless" joystick loop possible. Here's what happens:

Interrupt-Driven Multitasking in Drwal

  1. Main Program: Loops checking for joystick changes
  2. VBI Interrupt: Fires 50/60 times per second, handling:
    • Cloud animation and movement
    • Power bar decrease over time
    • Snake AI, movement, and collision detection
    • Background music playback
    • Game timing and frame counting
  3. DLI Interrupts: Fire during screen drawing for visual effects
  4. Result: Game runs smoothly regardless of main program state

This is cooperative multitasking - the main program cooperates with interrupts to share CPU time efficiently.

2. DLI (Display List Interrupt) - Special Effects Engine

DLIs trigger on specific scanlines to change graphics parameters mid-frame, enabling advanced visual effects.

DLI Chain Setup

; Enable DLI system
                ldx dli
                stx VDSLST       ; Set DLI vector
                sty VDSLST+1
                lda #%11000000   ; Enable DLI and VBL
                sta NMIEN

Display List with DLI Triggers

DISPLAY_LIST:
                dta $70
                dta %11110000    ; Triggers dli_2 (clouds setup)
                dta %11110000    ; Triggers dli_1 (sky color)
                dta %01000100,a(EKRAN)
                dta %10000100    ; Triggers dli1 (power bar colors)
                dta %11000100,a(EKR_WSKAZNIK) ; Triggers dli2
                ; ... more lines with %1xxxxxxx trigger DLIs

DLI Routine Examples

Safe Interrupt Entry/Exit

dli_1:
                pha              ; 1. Save accumulator
                lda #$88         ; 2. Do work (sky color)
                sta WSYNC        ;    Wait for horizontal sync
                sta COLBAK       ;    Change background color

                ; 3. Set up next DLI in chain
                lda dli1
                sta VDSLST+1

                pla              ; 4. Restore accumulator
                rti              ; 5. Return from interrupt

Sprite Multiplexing

; dli13 - Repurpose players for lumberjack axe
dli13:
                pha
                lda #%00100001   ; PMG priority
                sta PRIOR
                lda #%00000000   ; Single width sprites
                sta SIZEP0
                sta SIZEP1

                ; Set new colors for axe
                lda cpl0
                sta COLPM0
                lda cpl1
                sta COLPM1

                ; Set new positions for axe
                lda ppl0
                sta HPOSP0
                lda ppl1
                sta HPOSP1

                ; Chain to next DLI
                lda dli16
                sta VDSLST+1
                pla
                rti

DLI Effects in Drwal

  • Sky Gradient: Changes background color from light blue to dark blue to green
  • Power Bar: Special colors for the power indicator
  • Font Switching: Different character sets for UI vs. game area
  • Sprite Reuse: Same 4 hardware sprites show clouds, axe, and snake

Interrupt Best Practices

Critical Rules

  • Always save/restore registers (PHA/PLA)
  • Keep interrupt routines short and fast
  • Use WSYNC for precise timing
  • Set up next DLI before exiting current one
  • Use RTI (not RTS) to exit interrupts

Other Atari Interrupts

Interrupt Purpose Used in Drwal?
VBI Frame timing, game logic ✓ Yes
DLI Mid-screen graphics changes ✓ Yes
IRQ Peripheral devices, timers Music player only
SIO Disk/serial communication No
BRK Software interrupt, debugging No

Modern Programming Analogy

Think of it Like a Web Server

Imagine a web server that works like this:

  • Main Thread: Continuously polls for new HTTP requests (like the joystick loop)
  • Timer Events: Handle database cleanup, log rotation, cache updates (like VBI)
  • Render Events: Update the UI, refresh dashboards (like DLI)

The server doesn't freeze when no requests come in because timer and render events keep running. Similarly, Drwal doesn't freeze when no joystick input occurs because VBI and DLI interrupts keep the game alive.

Why This Approach Works

The interrupt-driven approach in Drwal demonstrates several key advantages:

  • Deterministic Timing: VBI ensures consistent 50/60 Hz game updates
  • Responsive Input: Main loop can focus solely on input detection
  • Efficient CPU Usage: No complex scheduling overhead
  • Hardware Integration: Interrupts are tied directly to video hardware
  • Predictable Performance: Interrupt timing is guaranteed by hardware

Key Takeaway

The "endless loop" in Drwal's input handling is actually a feature, not a bug. It demonstrates how interrupt-driven programming can create smooth, responsive games even on simple 8-bit hardware. The interrupts handle all the heavy lifting while the main program focuses on what it does best - detecting input changes.

Graphics & PMG System - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Advanced Atari 8-bit graphics programming using Player/Missile Graphics (PMG) system, demonstrated through Drwal's sophisticated sprite multiplexing and visual effects.

Understanding the Atari PMG System

The Atari 8-bit computers have a unique graphics system with hardware sprites called "Players" and "Missiles". Drwal demonstrates advanced techniques for maximizing this limited hardware.

Hardware Limitations & Solutions

Atari Graphics Hardware

  • 4 Players - Hardware sprites, 8 pixels wide each
  • 4 Missiles - Thin sprites, 2 pixels wide each
  • Limited colors - Each sprite has one color + transparency
  • Fixed positions - Horizontal position set by hardware registers

Drwal's Challenge: Display clouds, axe, and snake using only 4 sprites!

PMG Memory Layout in Drwal

; PMG memory organization
pmg:
                ; 3 pages (768 bytes) for data storage
                ; ...various game data...

                org pmg+$300           ; Start of actual PMG data
missile:        .HE 00 00 00...        ; 256 bytes - missile graphics
player0:        .HE 00 00 00...        ; 256 bytes - player 0 graphics
player1:        .HE 00 00 00...        ; 256 bytes - player 1 graphics
player2:        .HE 00 00 00...        ; 256 bytes - player 2 graphics
player3:        .HE 00 00 00...        ; 256 bytes - player 3 graphics

📖 Memory Efficiency: Drwal uses the unused PMG memory area for game data, maximizing memory usage.

Sprite Multiplexing - The Core Technique

Drwal's most impressive feature is sprite multiplexing - using the same 4 hardware sprites for different purposes at different screen positions.

1. Cloud Display (Top of Screen)

; DLI_2 - Cloud setup interrupt
dli_2:
                pha
                ; Make sprites double-width for clouds
                lda #%00000001              ; Double width
                sta SIZEP0
                sta SIZEP1
                sta SIZEP2
                sta SIZEP3

                ; Position clouds across screen
                lda pchmur0_0:#83           ; Cloud 0 position
                sta HPOSP0
                lda pchmur0_1:#128          ; Cloud 1 position
                sta HPOSP1
                lda pchmur0_2:#180          ; Cloud 2 position
                sta HPOSP2
                lda pchmur0_3:#243          ; Cloud 3 position
                sta HPOSP3

                ; Set up next interrupt
                lda dli_1
                sta VDSLST+1
                pla
                rti

2. Axe Display (Middle of Screen)

; DLI_13 - Axe setup interrupt
dli13:
                pha
                ; Reset sprite width to normal
                lda #%00000000              ; Normal width
                sta SIZEP0
                sta SIZEP1
                sta SIZEP2
                sta SIZEP3

                ; Load axe graphics into sprites
                ; (Graphics loaded by main program based on axe position)

                ; Set axe colors
                lda cpl0:#$12               ; Axe color 0
                sta COLPM0
                lda cpl1:#$36               ; Axe color 1
                sta COLPM1
                lda cpl2:#$10               ; Axe color 2
                sta COLPM2
                lda cpl3:#$0a               ; Axe color 3
                sta COLPM3

                ; Set axe positions
                lda ppl0:#$8e               ; Axe position 0
                sta HPOSP0
                lda ppl1:#$94               ; Axe position 1
                sta HPOSP1
                lda ppl2:#$94               ; Axe position 2
                sta HPOSP2
                lda ppl3:#$96               ; Axe position 3
                sta HPOSP3

                pla
                rti

3. Snake Display (Bottom of Screen)

; DLI_20 - Snake setup interrupt
dli20:
                pha
                ; Snake uses different colors and positions
                lda #$46                    ; Snake color
                sta COLPM0
                sta COLPM1
                sta COLPM2
                sta COLPM3

                ; Snake positions (animated)
                lda pzmii_0:#$60            ; Snake segment 0
                sta HPOSP0
                lda pzmii_1:#$68            ; Snake segment 1
                sta HPOSP1
                lda pzmii_2:#$70            ; Snake segment 2
                sta HPOSP2
                lda pzmii_3:#$78            ; Snake segment 3
                sta HPOSP3

                pla
                rti

Sprite Multiplexing Magic

The same 4 hardware sprites display:

  • Lines 0-2: White clouds (double-width)
  • Lines 13-16: Colored axe (4 different colors)
  • Lines 20-22: Green animated snake

This creates the illusion of having 12 sprites when only 4 exist!

Axe Animation System

Drwal implements a sophisticated 4-position axe animation system:

; Axe graphics data for different positions
; Left-top position
plr0_lg:        .HE 80 c0 e4 fc fc e4 c0 c0 80 00 00 00...
plr1_lg:        .HE 20 60 60 60 00 60 60 60 60 60 60 60...
plr2_lg:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...
plr3_lg:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...

; Right-top position
plr0_pg:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...
plr1_pg:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...
plr2_pg:        .HE 04 06 06 06 00 06 06 06 06 06 06 06...
plr3_pg:        .HE 01 03 27 3f 3f 27 03 03 01 00 00 00...

; Left-bottom position
plr0_ld:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...
; ...and right-bottom position
plr0_pd:        .HE 00 00 00 00 00 00 00 00 00 00 00 00...

Dynamic Axe Loading

; Load axe graphics based on position
RYS_TOPOR_LG:   ; Draw left-top axe
                ldy #0
@               lda plr0_lg,y               ; Load graphics data
                sta player0+160,y           ; Store in PMG memory
                lda plr1_lg,y
                sta player1+160,y
                lda plr2_lg,y
                sta player2+160,y
                lda plr3_lg,y
                sta player3+160,y
                iny
                cpy #30                     ; 30 bytes per sprite
                bne @-

                ; Set colors for this axe position
                lda pmg_kol_lg+0            ; Load color data
                sta cpl0                    ; Store in color variables
                lda pmg_kol_lg+1
                sta cpl1
                lda pmg_kol_lg+2
                sta cpl2
                lda pmg_kol_lg+3
                sta cpl3

                ; Set positions for this axe position
                lda pmg_poz_lg+0            ; Load position data
                sta ppl0                    ; Store in position variables
                lda pmg_poz_lg+1
                sta ppl1
                lda pmg_poz_lg+2
                sta ppl2
                lda pmg_poz_lg+3
                sta ppl3

                rts

Character Set Switching

Drwal uses multiple character sets for different screen areas:

; Font data
FNTA:           ins 'grafika/fntA.fnt'      ; Font A - UI elements
FNTB_0:         ins 'grafika/fntB_0.fnt'    ; Font B0 - Game graphics
FNTB_1:         ins 'grafika/fntB_1.fnt'    ; Font B1 - Alternative graphics

; Character set switching in DLI
dli3:
                pha
                lda >FNTB_0                 ; Switch to game font
                sta CHBASE                  ; Character base register
                pla
                rti

Why Multiple Character Sets?

Different screen areas need different graphics:

  • FNTA: Score display, UI text
  • FNTB_0: Tree trunk, branches, background
  • FNTB_1: Lumberjack character graphics

Display List Programming

DISPLAY_LIST:
                dta $70                     ; 8 blank lines
                dta %11110000               ; +DLI-2 (clouds)
                dta %11110000               ; +DLI-1 (sky color)

                dta %01000100,a(EKRAN)      ; Line 0 - screen data
                dta %10000100               ; +DLI1 (power bar)
                dta %11000100,a(EKR_WSKAZNIK) ; +DLI2 (power bar colors)
                dta %11000100,a(EKRAN+3*40) ; +DLI3 (font switch)
                dta %00000100               ; Normal line
                dta %10000100               ; +DLI5 (more clouds)
                ; ...more lines...
                dta %10000100               ; +DLI13 (axe setup)
                ; ...more lines...
                dta %10000100               ; +DLI20 (snake setup)

Display List Interrupt Timing

Each %1xxxxxxx byte triggers a DLI when that screen line is drawn. Drwal uses 10+ interrupts per frame for complex effects!

Sound & Music Integration - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Professional audio integration using RasterMusicTracker (RMT) system, demonstrated through Drwal's music and sound effects implementation.

RasterMusicTracker (RMT) Integration

Drwal uses the RasterMusicTracker system, a professional music player for Atari 8-bit computers that provides high-quality music and sound effects.

RMT Player Setup

; RMT Configuration
STEREOMODE      equ 0                       ; 0 = 4 channels mono
                                            ; 1 = stereo mode

; Include RMT player code
                icl 'muzyka/rmtplayr.a65'

; Music module location
MODUL_RMT       equ $8600                   ; Module load address

; Load music data
                opt h-                      ; Disable headers
                ins 'muzyka/drwal_modul.rmt' ; Insert music module
                opt h+                      ; Re-enable headers

Music Control System

; Start music at specific line
RMT_SET:        ; A = music line number
                ldx MODUL_RMT              ; Module address high
                ; A contains starting line number
                jsr RASTERMUSICTRACKER      ; Initialize player
                rts

; Stop music
RMT_OFF:
                lda #$46                    ; Line number for silence
                jsr RMT_SET                 ; Start silent section
                jsr RASTERMUSICTRACKER+9    ; Mute all channels
                rts

Sound Effects System

Drwal implements a sophisticated SFX system using RMT's instrument-based sound effects:

; Play sound effect
SFX_SET:        ; A = effect number
                asl                         ; Multiply by 2 (word table)
                tay                         ; Effect number * 2
                ldx #0                      ; Channel number (0-3)
                txa                         ; Pitch = 0 (use default)
                jsr RASTERMUSICTRACKER+15   ; Start SFX
                rts

; Example usage in game
hit_tree:
                lda #$13                    ; Effect #19 (hit sound)
                jsr SFX_SET                 ; Play hit sound
                ; ...continue game logic...

Sound Effect Types in Drwal

Effect # Description Usage Context
$13 Tree hit / Head impact When axe hits tree or player gets hit
$15 Snake attack When snake bites player
$17 Power decrease When energy bar goes down
$19 Level complete When tree is fully chopped

Music Integration with Game States

Title Screen Music

EKRAN_TYTULOWY:
                ; Start title music
                lda #$00                    ; Line 0 = title theme
                jsr RMT_SET                 ; Start music

                ; Set up VBI for music
                ldy dvbi_tytul
                lda #$07                    ; Deferred VBI
                jsr SETVBV                  ; Install interrupt

                ; ...title screen logic...

                jsr RMT_OFF                 ; Stop music when done
                rts

; Title screen VBI - plays music
dvbi_tytul:
                jsr RASTERMUSICTRACKER+3    ; Update music player
                jmp XITVBV                  ; Exit interrupt

Game Music

EKRAN_GRY:
                ; Start game music
                lda #$20                    ; Line 32 = game theme
                jsr RMT_SET                 ; Start music

                ; Game VBI includes music
dvbi:
                ; ...game logic...

                ; Update music every frame
                jsr RASTERMUSICTRACKER+3    ; Music update

                ; ...more game logic...
                jmp XITVBV                  ; Exit interrupt

Advanced Audio Techniques

1. Music and SFX Mixing

Channel Management

RMT automatically handles mixing:

  • Music: Uses all 4 POKEY channels
  • SFX: Temporarily overrides specific channels
  • Automatic restoration: Music resumes after SFX ends

2. Dynamic Music Control

; Change music based on game state
check_danger:
                lda zmija_idzie             ; Is snake active?
                beq normal_music            ; No - normal music

                lda #$30                    ; Line 48 = danger music
                jsr RMT_SET                 ; Switch to tense music
                jmp continue_game

normal_music:
                lda #$20                    ; Line 32 = normal music
                jsr RMT_SET                 ; Switch to normal music

continue_game:
                ; ...game continues...

3. Audio Synchronization

; Synchronize SFX with visual effects
tree_hit_effect:
                ; Visual effect
                jsr ANIM_GWIAZDEK           ; Show stars animation

                ; Audio effect (synchronized)
                lda #$13                    ; Hit sound
                jsr SFX_SET                 ; Play immediately

                ; Both effects run simultaneously
                rts

Memory Management for Audio

RMT Memory Layout

; Memory organization
; $8000-$85FF: RMT Player code (1.5KB)
; $8600-$8FFF: Music module data (2.5KB)
; $9000+:      Available for other data

; Player location (set in RMT export)
RASTERMUSICTRACKER equ $8000

; Module location (configurable)
MODUL_RMT       equ $8600

Optimized Audio Calls

; Efficient VBI audio update
dvbi:
                ; Minimal audio processing in VBI
                jsr RASTERMUSICTRACKER+3    ; Just update player

                ; Don't call SFX_SET from VBI!
                ; SFX calls should be from main program only

                ; ...other VBI tasks...
                jmp XITVBV

Audio Performance Considerations

VBI Timing Constraints

  • Music update: ~200 cycles per frame
  • SFX processing: Additional ~100 cycles
  • Total VBI budget: ~1000 cycles (50Hz) or ~800 cycles (60Hz)
  • Rule: Keep audio processing minimal in VBI

Best Practices

  • Call music update once per frame in VBI
  • Trigger SFX from main program, not interrupts
  • Use appropriate music lines for different game states
  • Test on real hardware for timing accuracy
  • Reserve memory carefully for RMT player and modules

Creating Custom Audio Content

RMT Module Structure

; Music module organization (created in RMT editor)
; Line 0-31:   Title screen music
; Line 32-47:  Normal gameplay music
; Line 48-63:  Danger/tension music
; Line 64-71:  Victory music
; Line 72+:    Silent/empty patterns

Professional Audio Workflow

Drwal demonstrates professional game audio practices:

  • Modular music design - Different sections for different moods
  • Seamless transitions - Music changes don't interrupt gameplay
  • Integrated SFX - Sound effects complement music
  • Memory efficiency - Single module contains all audio

Input & Hardware Interface - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Comprehensive input handling and hardware interface programming, demonstrated through Drwal's sophisticated joystick, keyboard, and paddle integration.

Joystick Input System

Drwal implements a sophisticated input system that combines joystick and keyboard input into a unified interface.

Core Input Reading

; Main input loop - the heart of the game
spr_joy:
                jsr JOYFIRKEY               ; Read joystick + keyboard
                tya                         ; Transfer result to A
                cmp pam_joya                ; Compare with previous state
                beq spr_joy                 ; No change? Keep polling

                ; Input changed - process it
                sta pam_joya                ; Store new state
                ; ...handle input change...

Why This Polling Pattern Works

The beq spr_joy creates a tight polling loop, but the game doesn't freeze because:

  • VBI interrupts handle animation, music, and timing
  • DLI interrupts handle graphics effects
  • Main loop only processes input changes

This is cooperative multitasking - interrupts do the heavy lifting!

JOYFIRKEY Routine Analysis

; Combined joystick and keyboard input
JOYFIRKEY:
                ; Read joystick port 0
                lda STICK0                  ; Hardware joystick register
                and #%00001111              ; Mask direction bits
                sta temp_joy                ; Store joystick state

                ; Read fire button
                lda STRIG0                  ; Hardware trigger register
                and #%00000001              ; Mask fire button
                asl                         ; Shift to bit 4
                asl
                asl
                asl
                ora temp_joy                ; Combine with directions

                ; Check keyboard alternatives
                lda KBCODE                  ; Keyboard scan code
                cmp #$1C                    ; 'A' key (left)
                bne @+
                lda temp_joy
                and #%11111110              ; Clear left bit
                sta temp_joy
@               lda KBCODE
                cmp #$1F                    ; 'D' key (right)
                bne @+
                lda temp_joy
                and #%11111101              ; Clear right bit
                sta temp_joy
@               ; ...more keyboard checks...

                ldy temp_joy                ; Return in Y register
                rts

Advanced Input Features

1. Joycart Detection

Drwal automatically detects enhanced joystick controllers:

; Detect Joycart (2-button joystick)
EKRAN_TYTULOWY:
                lda #0                      ; Default: no Joycart
                ldx PADDL1                  ; Read paddle 1
                cpx #111                    ; Joycart threshold
                bcs @+                      ; No Joycart detected
                lda #1                      ; Joycart detected
@               sta joycart                 ; Store detection result

; Use Joycart features if available
check_second_button:
                lda joycart
                beq single_button           ; Standard joystick

                ; Check second button (Joycart)
                lda PADDL1                  ; Second button via paddle
                cmp #50                     ; Button pressed threshold
                bcs no_second_button        ; Not pressed

                ; Second button pressed - special action
                jsr SPECIAL_ATTACK
                jmp input_done

single_button:
                ; Standard single-button handling
                lda STRIG0
                bne no_fire
                jsr NORMAL_ATTACK

input_done:
                ; ...continue...

2. Input State Management

; Input state tracking
pam_joya:       dta %00001100               ; Previous joystick state
                ; Bit layout:
                ; 7654 3210
                ; |||| ||||
                ; |||| |||+- Right (0=pressed)
                ; |||| ||+-- Left (0=pressed)
                ; |||| |+--- Down (0=pressed)
                ; |||| +---- Up (0=pressed)
                ; |||+------ Fire (1=pressed)
                ; ||+------- Second button (1=pressed)
                ; |+-------- Reserved
                ; +--------- Reserved

; Input change detection
process_input:
                lda pam_joya                ; Previous state
                eor current_joy             ; XOR with current
                and current_joy             ; Mask with current
                ; Result: bits set for newly pressed buttons

                tax                         ; Save change mask
                and #%00000001              ; Check right
                beq @+
                jsr MOVE_RIGHT              ; Handle right press
@               txa
                and #%00000010              ; Check left
                beq @+
                jsr MOVE_LEFT               ; Handle left press
@               ; ...check other directions...

Hardware Register Interface

Direct Hardware Access

; Key hardware registers used in Drwal
STICK0          equ $0278                   ; Joystick 0 directions
STRIG0          equ $0284                   ; Joystick 0 fire button
PADDL0          equ $0270                   ; Paddle 0 (analog)
PADDL1          equ $0271                   ; Paddle 1 (Joycart 2nd button)
KBCODE          equ $D209                   ; Keyboard scan code
RANDOM          equ $D20A                   ; Hardware random number
VCOUNT          equ $D40B                   ; Vertical line counter

; Example: Using hardware random for game elements
get_random_cloud_speed:
                lda RANDOM                  ; Get hardware random
                and #%00000011              ; Mask to 0-3
                clc
                adc #1                      ; Make it 1-4
                sta cloud_speed             ; Store speed
                rts

Timing-Critical Hardware Access

; Precise timing using VCOUNT
wait_for_scanline:
                lda #100                    ; Target scanline
@               cmp VCOUNT                  ; Compare with current line
                bne @-                      ; Wait until match

                ; Now we're exactly at scanline 100
                lda #$FF                    ; Set sprite color
                sta COLPM0                  ; Exactly timed color change
                rts

; Using WSYNC for horizontal timing
precise_color_change:
                sta WSYNC                   ; Wait for horizontal sync
                nop                         ; Precise delay
                nop
                lda #$46                    ; New color
                sta COLBAK                  ; Change background color
                rts

Input Response Patterns

1. Immediate Response

; Immediate input response (axe swing)
handle_fire:
                lda STRIG0                  ; Check fire button
                bne no_fire                 ; Not pressed

                ; Fire pressed - immediate response
                lda power                   ; Check if player has energy
                beq no_power                ; No energy left

                jsr SWING_AXE               ; Immediate axe animation
                jsr PLAY_SWING_SOUND        ; Immediate audio feedback
                dec power                   ; Immediate power decrease

no_fire:
                rts

2. Buffered Input

; Buffered input for movement
handle_movement:
                lda current_joy
                and #%00000011              ; Check left/right
                beq no_movement             ; No direction pressed

                ; Store movement request
                sta movement_buffer         ; Buffer the input
                lda #10                     ; Movement delay
                sta movement_timer          ; Set timer

no_movement:
                rts

; Process buffered movement (called from VBI)
process_movement:
                lda movement_timer          ; Check timer
                beq no_buffered_move        ; Timer expired
                dec movement_timer          ; Count down
                bne no_buffered_move        ; Still waiting

                ; Timer expired - execute movement
                lda movement_buffer         ; Get buffered input
                jsr EXECUTE_MOVEMENT        ; Perform movement
                lda #0
                sta movement_buffer         ; Clear buffer

no_buffered_move:
                rts

Multi-Input Integration

Keyboard + Joystick Combination

; Unified input system
UNIFIED_INPUT:
                ; Start with joystick input
                lda STICK0                  ; Read joystick
                sta input_state             ; Store base state

                ; Override with keyboard if pressed
                lda KBCODE                  ; Check keyboard
                cmp #$1C                    ; 'A' key
                bne @+
                lda input_state
                and #%11111110              ; Force left direction
                sta input_state

@               lda KBCODE
                cmp #$1F                    ; 'D' key
                bne @+
                lda input_state
                and #%11111101              ; Force right direction
                sta input_state

@               lda KBCODE
                cmp #$39                    ; Space bar
                bne @+
                lda input_state
                ora #%00010000              ; Force fire button
                sta input_state

@               lda input_state             ; Return unified input
                rts

Input Debugging and Testing

Input State Display

; Debug: Display input state on screen
DEBUG_INPUT:
                lda current_joy             ; Get current input
                pha                         ; Save it

                ; Display direction bits
                and #%00000001              ; Right bit
                beq @+
                lda #'R'                    ; Show 'R' for right
                sta EKRAN+0
@               pla
                pha
                and #%00000010              ; Left bit
                beq @+
                lda #'L'                    ; Show 'L' for left
                sta EKRAN+1

@               pla
                and #%00010000              ; Fire bit
                beq @+
                lda #'F'                    ; Show 'F' for fire
                sta EKRAN+2
@               rts

Input System Best Practices

  • Always debounce inputs - Avoid multiple triggers
  • Use state comparison - Only act on changes
  • Provide multiple input methods - Keyboard + joystick
  • Handle edge cases - What if no controller is connected?
  • Test on real hardware - Emulators may behave differently

Data Structures & Memory Management - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Advanced data organization and memory management techniques, demonstrated through Drwal's efficient use of bit-packed data, lookup tables, and zero-page optimization.

Bit-Packed Data Structures

Drwal demonstrates sophisticated data compression using bit-packed structures to maximize memory efficiency.

Tree Structure Encoding

; Tree data structure - each byte encodes multiple properties
drzewo:         dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
                ; Bit layout for each tree segment:
                ; ?xxxx???
                ;        \\- kora: 0=bark type 66, 1=bark type 67
                ;       \\-- 1=branch on right side
                ;      \\--- 1=branch on left side
                ; \\-------- 1=end of tree (top reached)

; Example tree data with branches and bark types
pam_drzewa:     dta 0,1,1,0,0,0,0,1,0,3,0,1,0,1,1,5,1,0,0,1
                ; Decoded:
                ; 0 = no branches, bark type 0
                ; 1 = right branch, bark type 1
                ; 3 = both branches, bark type 1
                ; 5 = left branch + end marker, bark type 1

Decoding Tree Data

; Extract tree properties from packed data
DECODE_TREE_SEGMENT:    ; X = tree level
                lda drzewo,x                ; Get packed data
                pha                         ; Save original

                ; Extract bark type (bit 0)
                and #%00000001              ; Mask bit 0
                tay                         ; Y = bark type (0 or 1)
                lda rodzaj_pnia,y           ; Get bark character
                sta current_bark            ; Store bark type

                pla                         ; Restore original
                pha                         ; Save again

                ; Extract right branch (bit 1)
                and #%00000010              ; Mask bit 1
                beq no_right_branch         ; Bit clear = no branch
                lda #1
                sta has_right_branch        ; Mark right branch present
                jmp check_left_branch

no_right_branch:
                lda #0
                sta has_right_branch        ; Mark no right branch

check_left_branch:
                pla                         ; Restore original
                pha                         ; Save again

                ; Extract left branch (bit 2)
                and #%00000100              ; Mask bit 2
                beq no_left_branch          ; Bit clear = no branch
                lda #1
                sta has_left_branch         ; Mark left branch present
                jmp check_tree_end

no_left_branch:
                lda #0
                sta has_left_branch         ; Mark no left branch

check_tree_end:
                pla                         ; Restore original

                ; Extract end marker (bit 7)
                and #%10000000              ; Mask bit 7
                beq not_tree_end            ; Bit clear = not end
                lda #1
                sta tree_end_reached        ; Mark tree complete
                rts

not_tree_end:
                lda #0
                sta tree_end_reached        ; Mark tree continues
                rts

Memory Efficiency

Bit-packing saves significant memory:

  • Unpacked: 4 bytes per tree segment (80 bytes total)
  • Bit-packed: 1 byte per tree segment (20 bytes total)
  • Savings: 75% memory reduction!

Lookup Tables

Drwal extensively uses lookup tables for performance optimization, avoiding expensive calculations at runtime.

Screen Address Tables

; Pre-calculated screen addresses for tree trunk drawing
lo_adr_pnia:    dta <(EKRAN+(19*40)+18),<(EKRAN+(18*40)+18),<(EKRAN+(17*40)+18)
                dta <(EKRAN+(16*40)+18),<(EKRAN+(15*40)+18),<(EKRAN+(14*40)+18)
                dta <(EKRAN+(13*40)+18),<(EKRAN+(12*40)+18),<(EKRAN+(11*40)+18)
                dta <(EKRAN+(10*40)+18),<(EKRAN+(9*40)+18),<(EKRAN+(8*40)+18)
                dta <(EKRAN+(7*40)+18),<(EKRAN+(6*40)+18),<(EKRAN+(5*40)+18)
                dta <(EKRAN+(4*40)+18),<(EKRAN+(3*40)+18),<(EKRAN+(2*40)+18)
                dta <(EKRAN+(1*40)+18),<(EKRAN+(0*40)+18)

hi_adr_pnia:    dta >(EKRAN+(19*40)+18),>(EKRAN+(18*40)+18),>(EKRAN+(17*40)+18)
                dta >(EKRAN+(16*40)+18),>(EKRAN+(15*40)+18),>(EKRAN+(14*40)+18)
                dta >(EKRAN+(13*40)+18),>(EKRAN+(12*40)+18),>(EKRAN+(11*40)+18)
                dta >(EKRAN+(10*40)+18),>(EKRAN+(9*40)+18),>(EKRAN+(8*40)+18)
                dta >(EKRAN+(7*40)+18),>(EKRAN+(6*40)+18),>(EKRAN+(5*40)+18)
                dta >(EKRAN+(4*40)+18),>(EKRAN+(3*40)+18),>(EKRAN+(2*40)+18)
                dta >(EKRAN+(1*40)+18),>(EKRAN+(0*40)+18)

; Fast address lookup
GET_TREE_ADDRESS:       ; Y = tree level (0-19)
                lda lo_adr_pnia,y           ; Get low byte
                sta ZERO1                   ; Store in zero-page pointer
                lda hi_adr_pnia,y           ; Get high byte
                sta ZERO1+1                 ; Complete 16-bit address
                rts

Animation Frame Tables

; Snake animation frames - right movement
pzmija0_0:      .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00
pzmija0_1:      .HE 00 00 00 00 00 00 00 00 00 20 00 23 33 1c 14 08 00
pzmija0_2:      .HE 00 04 02 02 06 04 04 0c 08 08 0c 0c 0f 0b 0c 04 00
pzmija0_3:      .HE 00 00 00 00 00 00 00 00 00 20 00 23 33 1c 14 08 00
pzmija0_4:      .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00

; Animation frame pointers (low bytes)
snake_frames_lo: dta pzmija0_0,>pzmija0_1,>pzmija0_2,>pzmija0_3,>pzmija0_4

; Get animation frame
GET_SNAKE_FRAME:        ; A = frame number (0-4)
                tax                         ; Use as index
                lda snake_frames_lo,x       ; Get frame address low
                sta ZERO2                   ; Store pointer
                lda snake_frames_hi,x       ; Get frame address high
                sta ZERO2+1                 ; Complete pointer
                rts

Zero-Page Optimization

Drwal strategically uses zero-page memory for maximum performance.

Zero-Page Allocation

; Zero-page memory allocation ($80-$D3 available)
ZERO1           equ $0080                   ; (2 bytes) General pointer 1
ZERO2           equ $0082                   ; (2 bytes) General pointer 2
ZERO3           equ $0084                   ; (2 bytes) General pointer 3
ZERO4           equ $0086                   ; (2 bytes) General pointer 4
PAMY            equ $0088                   ; (1 byte)  Temporary storage

; Additional zero-page variables (defined elsewhere)
temp_joy        equ $0089                   ; (1 byte)  Joystick temp
current_bark    equ $008A                   ; (1 byte)  Current bark type
has_left_branch equ $008B                   ; (1 byte)  Left branch flag
has_right_branch equ $008C                  ; (1 byte)  Right branch flag

Efficient Pointer Usage

; Fast memory copy using zero-page pointers
COPY_SPRITE_DATA:
                ; Set up source pointer
                lda sprite_source          ; Source address high
                sta ZERO1+1                 ; Complete pointer

                ; Set up destination pointer
                lda player0+160            ; Destination high
                sta ZERO2+1                 ; Complete pointer

                ; Fast copy loop
                ldy #0                      ; Start at offset 0
copy_loop:
                lda (ZERO1),y               ; Read from source
                sta (ZERO2),y               ; Write to destination
                iny                         ; Next byte
                cpy #30                     ; 30 bytes per sprite
                bne copy_loop               ; Continue until done
                rts

Complex Data Structures

Multi-Dimensional Arrays

; PMG color data organized by position and sprite
; Format: [position][sprite] = color
pmg_kol_lg:     .HE 0a 10 36 12             ; Left-top colors
pmg_kol_pg:     .HE 12 36 10 0a             ; Right-top colors
pmg_kol_ld:     .HE 12 36 10 0a             ; Left-bottom colors
pmg_kol_pd:     .HE 0a 10 36 12             ; Right-bottom colors

; Access pattern: position * 4 + sprite = offset
GET_PMG_COLOR:  ; A = position (0-3), X = sprite (0-3)
                asl                         ; Position * 2
                asl                         ; Position * 4
                stx PAMY                    ; Save sprite number
                clc
                adc PAMY                    ; Add sprite offset
                tax                         ; Use as index
                lda pmg_colors,x            ; Get color
                rts

; Color table (linearized 2D array)
pmg_colors:     .HE 0a 10 36 12             ; Position 0 colors
                .HE 12 36 10 0a             ; Position 1 colors
                .HE 12 36 10 0a             ; Position 2 colors
                .HE 0a 10 36 12             ; Position 3 colors

State Machines

; Snake state machine data
snake_state:    dta 0                       ; Current state
                ; States: 0=idle, 1=moving, 2=attacking, 3=retreating

; State transition table
; Format: [current_state][condition] = new_state
state_transitions:
                ; From idle (state 0)
                dta 1,0,0,0                 ; player_near -> moving
                ; From moving (state 1)
                dta 1,2,3,1                 ; player_close -> attacking
                ; From attacking (state 2)
                dta 3,3,3,3                 ; always -> retreating
                ; From retreating (state 3)
                dta 0,0,0,0                 ; always -> idle

; State machine update
UPDATE_SNAKE_STATE:
                lda snake_state             ; Current state
                asl                         ; * 2
                asl                         ; * 4 (4 conditions per state)
                sta state_offset            ; Save base offset

                ; Check conditions and update state
                jsr CHECK_PLAYER_DISTANCE   ; Returns condition in A
                clc
                adc state_offset            ; Add to base offset
                tax                         ; Use as index
                lda state_transitions,x     ; Get new state
                sta snake_state             ; Update state
                rts

Memory Layout Optimization

Strategic Data Placement

; Memory map organization
; $2000-$2FFF: Main program code
; $3000-$3FFF: PMG data and graphics
; $4000-$4FFF: Screen memory (aligned to 4K boundary)
; $5000-$5FFF: Display list and small data
; $6000-$7FFF: Music and sound data
; $8000-$8FFF: RMT player and modules

; Alignment for performance
                .align $1000                ; Screen on 4K boundary
EKRAN:          ins 'grafika/drwal.sved2'   ; Screen data

                .align $100                 ; PMG on page boundary
pmg_data:       ; PMG graphics data

; Page-aligned lookup tables for speed
                .align $100
sine_table:     ; Sine wave data for smooth movement
cosine_table:   ; Cosine wave data

Memory Usage Analysis

Drwal Memory Efficiency

Data Type Size Optimization
Tree structure 20 bytes Bit-packed (75% savings)
Screen addresses 40 bytes Pre-calculated lookup
Animation frames 340 bytes Indexed access
PMG graphics 480 bytes Position-based organization
Zero-page 16 bytes Strategic allocation

Advanced Memory Techniques

Self-Modifying Code

; Self-modifying code for dynamic behavior
setup_dynamic_jump:
                ; Modify jump target based on game state
                lda game_mode               ; Get current mode
                asl                         ; * 2 (word addresses)
                tax                         ; Use as index
                lda jump_table,x            ; Get target address low
                sta dynamic_jump+1          ; Modify instruction
                lda jump_table+1,x          ; Get target address high
                sta dynamic_jump+2          ; Modify instruction

dynamic_jump:   jmp $0000                   ; Target modified at runtime

jump_table:     dta mode_0_handler
                dta mode_1_handler
                dta mode_2_handler

Data Structure Best Practices

  • Use bit-packing for boolean flags and small values
  • Pre-calculate expensive operations into lookup tables
  • Align data to page boundaries for performance
  • Use zero-page for frequently accessed variables
  • Organize data by access patterns, not logical grouping
  • Document bit layouts clearly in comments

Game Logic & State Management - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Comprehensive game logic implementation including state machines, collision detection, AI systems, and game progression mechanics demonstrated in Drwal.

Game State Management

Drwal implements a sophisticated state management system that tracks multiple game entities and their interactions.

Core Game States

; Primary game state variables
smierc:         dta 0                       ; Death state: 0=alive, 1=left death, 2=right death
jad:            dta 0                       ; Poison state: 0=healthy, 1=poisoned left, 2=poisoned right
power:          dta 40                      ; Player energy: 0=exhausted, 80=maximum
pam_pozdrwala:  dta 0,0                     ; Player position: current, previous
zmija_idzie:    dta 0                       ; Snake movement: 0=idle, 1-255=steps remaining
kierunek_zmii:  dta 1                       ; Snake direction: 0=left, 1=right

; Game progression state
drzewo:         dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0  ; Tree segments remaining
gestosc_korony: dta 5                       ; Crown density (difficulty)

State Transition Logic

; Main game state machine
GRAMY:
                ; Check for death conditions first
                ldx smierc                  ; Check death state
                beq player_alive            ; 0 = still alive

                ; Player is dead - handle death
                jsr RYS_MARTWEGO            ; Draw dead player
                jmp KONIEC_GRY              ; End game

player_alive:
                ; Check for poison
                lda jad                     ; Check poison state
                beq not_poisoned            ; 0 = not poisoned

                ; Handle poison effects
                jsr HANDLE_POISON           ; Apply poison damage

not_poisoned:
                ; Check for tree completion
                lda drzewo                  ; Check tree state
                bpl tree_continues          ; Positive = tree continues

                ; Tree completed
                jsr KONIEC_ETAPU            ; Handle level completion
                jmp START                   ; Restart (temporary)

tree_continues:
                ; Normal gameplay - handle input
                jmp spr_joy                 ; Continue input loop

Collision Detection System

Drwal implements multiple collision detection systems for different game interactions.

Snake vs Player Collision

; Snake collision detection
CHECK_SNAKE_COLLISION:
                lda zmija_idzie             ; Is snake moving?
                beq no_snake_collision      ; No movement = no collision

                ; Get snake position
                lda pzmii_0                 ; Snake segment 0 position
                sec
                sbc #8                      ; Collision tolerance
                cmp player_x_pos            ; Compare with player X
                bcs no_collision_x          ; Snake too far right

                lda pzmii_0
                clc
                adc #16                     ; Snake width + tolerance
                cmp player_x_pos
                bcc no_collision_x          ; Snake too far left

                ; X collision detected - check Y
                lda snake_y_pos             ; Snake Y position
                sec
                sbc #4                      ; Y tolerance
                cmp player_y_pos            ; Compare with player Y
                bcs no_collision_y          ; Snake too high

                lda snake_y_pos
                clc
                adc #12                     ; Snake height + tolerance
                cmp player_y_pos
                bcc no_collision_y          ; Snake too low

                ; Collision detected!
                jsr SNAKE_ATTACK            ; Handle snake bite
                rts

no_collision_x:
no_collision_y:
no_snake_collision:
                rts

Axe vs Tree Collision

; Tree chopping collision detection
CHECK_AXE_HIT:
                ; Check if fire button pressed
                lda STRIG0                  ; Read fire button
                bne no_axe_swing            ; Not pressed

                ; Check player energy
                lda power                   ; Check energy level
                beq no_energy               ; No energy left

                ; Determine hit location based on player position
                lda pam_pozdrwala           ; Get player position
                cmp #0                      ; Left-top?
                beq hit_left_side
                cmp #1                      ; Right-bottom?
                beq hit_right_side
                cmp #2                      ; Right-top?
                beq hit_right_side
                cmp #3                      ; Left-bottom?
                beq hit_left_side

hit_left_side:
                ; Check for left branch
                ldx tree_level              ; Current tree level
                lda drzewo,x                ; Get tree data
                and #%00000100              ; Check left branch bit
                beq hit_trunk               ; No branch = hit trunk

                ; Hit left branch
                jsr REMOVE_LEFT_BRANCH      ; Remove branch
                jmp axe_hit_success

hit_right_side:
                ; Check for right branch
                ldx tree_level              ; Current tree level
                lda drzewo,x                ; Get tree data
                and #%00000010              ; Check right branch bit
                beq hit_trunk               ; No branch = hit trunk

                ; Hit right branch
                jsr REMOVE_RIGHT_BRANCH     ; Remove branch
                jmp axe_hit_success

hit_trunk:
                ; Hit tree trunk
                jsr ADVANCE_TREE_LEVEL      ; Move up tree

axe_hit_success:
                ; Common hit processing
                dec power                   ; Reduce energy
                lda #$13                    ; Hit sound effect
                jsr SFX_SET                 ; Play sound
                jsr ANIM_GWIAZDEK           ; Show hit animation

no_energy:
no_axe_swing:
                rts

AI System - Snake Behavior

Drwal implements a sophisticated AI system for the snake enemy.

Snake State Machine

; Snake AI states
SNAKE_STATE_IDLE        equ 0               ; Waiting underground
SNAKE_STATE_EMERGING    equ 1               ; Coming to surface
SNAKE_STATE_HUNTING     equ 2               ; Actively pursuing player
SNAKE_STATE_ATTACKING   equ 3               ; Striking at player
SNAKE_STATE_RETREATING  equ 4               ; Returning underground

snake_ai_state:         dta SNAKE_STATE_IDLE
snake_ai_timer:         dta 0               ; Timer for state transitions
snake_target_x:         dta 0               ; Target X position

; Snake AI update (called from VBI)
UPDATE_SNAKE_AI:
                lda snake_ai_state          ; Get current state
                cmp #SNAKE_STATE_IDLE
                beq handle_idle_state
                cmp #SNAKE_STATE_EMERGING
                beq handle_emerging_state
                cmp #SNAKE_STATE_HUNTING
                beq handle_hunting_state
                cmp #SNAKE_STATE_ATTACKING
                beq handle_attacking_state
                cmp #SNAKE_STATE_RETREATING
                beq handle_retreating_state
                rts

handle_idle_state:
                ; Random chance to emerge
                lda RANDOM                  ; Get random number
                and #%01111111              ; Mask to 0-127
                cmp #10                     ; 10/128 chance per frame
                bcs stay_idle               ; Stay idle

                ; Start emerging
                lda #SNAKE_STATE_EMERGING   ; Change to emerging
                sta snake_ai_state
                lda #30                     ; 30 frame emergence
                sta snake_ai_timer
                jsr SET_SNAKE_TARGET        ; Choose target position

stay_idle:
                rts

handle_emerging_state:
                ; Count down emergence timer
                dec snake_ai_timer          ; Decrease timer
                bne still_emerging          ; Still emerging

                ; Emergence complete - start hunting
                lda #SNAKE_STATE_HUNTING    ; Change to hunting
                sta snake_ai_state
                lda #120                    ; 2 second hunt time
                sta snake_ai_timer

still_emerging:
                rts

handle_hunting_state:
                ; Move toward player
                jsr MOVE_SNAKE_TO_TARGET    ; Move snake

                ; Check if close enough to attack
                jsr CHECK_ATTACK_RANGE      ; Returns A=1 if in range
                beq not_in_range            ; Not close enough

                ; Start attack
                lda #SNAKE_STATE_ATTACKING  ; Change to attacking
                sta snake_ai_state
                lda #15                     ; 15 frame attack
                sta snake_ai_timer
                rts

not_in_range:
                ; Check hunt timer
                dec snake_ai_timer          ; Decrease timer
                bne still_hunting           ; Continue hunting

                ; Hunt time expired - retreat
                lda #SNAKE_STATE_RETREATING ; Change to retreating
                sta snake_ai_state
                lda #45                     ; 45 frame retreat
                sta snake_ai_timer

still_hunting:
                rts

Snake Movement AI

; Move snake toward target
MOVE_SNAKE_TO_TARGET:
                ; Get current snake position
                lda pzmii_0                 ; Snake X position
                cmp snake_target_x          ; Compare with target
                beq at_target_x             ; Already at target X
                bcs move_left               ; Snake right of target

move_right:
                ; Move snake right
                inc pzmii_0                 ; Move segment 0
                inc pzmii_1                 ; Move segment 1
                inc pzmii_2                 ; Move segment 2
                inc pzmii_3                 ; Move segment 3
                lda #1                      ; Right direction
                sta kierunek_zmii           ; Update direction
                jmp update_animation

move_left:
                ; Move snake left
                dec pzmii_0                 ; Move segment 0
                dec pzmii_1                 ; Move segment 1
                dec pzmii_2                 ; Move segment 2
                dec pzmii_3                 ; Move segment 3
                lda #0                      ; Left direction
                sta kierunek_zmii           ; Update direction

update_animation:
                ; Update snake animation frame
                inc klatka_zmii             ; Next animation frame
                lda klatka_zmii
                cmp #5                      ; 5 frames per cycle
                bne animation_ok            ; Frame valid
                lda #0                      ; Reset to frame 0
                sta klatka_zmii

animation_ok:
at_target_x:
                rts

; Set snake target based on player position
SET_SNAKE_TARGET:
                lda pam_pozdrwala           ; Get player position
                and #%00000001              ; Check if on right side
                beq target_left_side        ; Player on left

target_right_side:
                lda #200                    ; Right side X position
                sta snake_target_x
                rts

target_left_side:
                lda #80                     ; Left side X position
                sta snake_target_x
                rts

Game Progression System

Level Completion Logic

; Handle level completion
KONIEC_ETAPU:
                ; Stop all moving elements
                jsr ZMIJA_STOP              ; Stop snake
                jsr RMT_OFF                 ; Stop music

                ; Calculate bonus points
                jsr CALCULATE_POWER_BONUS   ; Points for remaining energy
                jsr CALCULATE_TIME_BONUS    ; Points for completion time

                ; Display completion sequence
                jsr SHOW_COMPLETION_TABLE   ; Show results table
                jsr ANIMATE_SCORE_COUNT     ; Animate score counting

                ; Prepare next level
                jsr INCREASE_DIFFICULTY     ; Make next level harder
                jsr RESET_GAME_STATE        ; Reset for new level

                ; Wait for player input
                jsr WCISNIJ_FIRE_START      ; Wait for fire/start
                rts

; Increase game difficulty
INCREASE_DIFFICULTY:
                ; Increase crown density (more branches)
                lda gestosc_korony          ; Current density
                cmp #8                      ; Maximum density
                bcs max_difficulty          ; Already at max
                inc gestosc_korony          ; Increase density

max_difficulty:
                ; Decrease power regeneration rate
                lda spadek_mocy+1           ; Current regen rate
                cmp #30                     ; Minimum rate
                bcs min_regen               ; Already at minimum
                inc spadek_mocy+1           ; Slower regeneration

min_regen:
                ; Increase snake aggression
                lda snake_aggression        ; Current aggression
                cmp #200                    ; Maximum aggression
                bcs max_aggression          ; Already at max
                clc
                adc #20                     ; Increase by 20
                sta snake_aggression

max_aggression:
                rts

Scoring System

; Add points to score
PUNKTY:         ; Adds 1 point to score
                ; Score is stored as BCD in screen memory
                ldx #6                      ; 6 digits (rightmost first)

add_one_loop:
                lda EKRAN+(3*40),x          ; Get current digit
                cmp #$19                    ; Is it '9'?
                bne not_nine                ; No - just increment

                ; Digit is 9 - set to 0 and carry
                lda #$10                    ; '0' character
                sta EKRAN+(3*40),x          ; Store new digit
                dex                         ; Move to next digit
                bne add_one_loop            ; Continue if more digits
                rts                         ; Overflow - score maxed

not_nine:
                inc EKRAN+(3*40),x          ; Increment digit
                rts                         ; Done

; Calculate power bonus
CALCULATE_POWER_BONUS:
                lda power                   ; Get remaining power
                beq no_power_bonus          ; No power = no bonus

power_bonus_loop:
                ldy #10                     ; 10 points per power unit
                sty PAMY                    ; Store multiplier

point_loop:
                jsr PUNKTY                  ; Add 1 point
                dec PAMY                    ; Count down
                bne point_loop              ; Continue until done

                dec power                   ; Reduce power display
                jsr RYS_WSKAZNIK            ; Update power bar
                jsr PAUSE1                  ; Brief pause for effect

                lda power                   ; Check remaining power
                bne power_bonus_loop        ; Continue if more power

no_power_bonus:
                rts

Game Balance and Tuning

Difficulty Parameters

; Tunable game parameters
INITIAL_POWER           equ 40              ; Starting energy
MAX_POWER               equ 80              ; Maximum energy
POWER_DECREASE_RATE     equ 18              ; Frames between power loss
SNAKE_BASE_SPEED        equ 3               ; Base snake movement speed
SNAKE_AGGRESSION_BASE   equ 50              ; Base snake activation chance
BRANCH_DENSITY_BASE     equ 5               ; Starting branch density

; Dynamic difficulty adjustment
ADJUST_DIFFICULTY:
                ; Check player performance
                lda player_deaths           ; Number of deaths
                cmp #3                      ; Too many deaths?
                bcc normal_difficulty       ; No - keep normal

                ; Make game easier
                lda POWER_DECREASE_RATE     ; Current power loss rate
                cmp #25                     ; Already at easiest?
                bcs already_easy            ; Yes - don't change
                inc POWER_DECREASE_RATE     ; Slower power loss

already_easy:
normal_difficulty:
                rts

Game Logic Best Practices

  • Use state machines for complex entity behavior
  • Separate collision detection by interaction type
  • Implement AI timers for predictable behavior
  • Balance difficulty progression carefully
  • Provide visual feedback for all player actions
  • Test edge cases thoroughly

Performance Optimization - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Advanced 6502 performance optimization techniques demonstrated through Drwal's efficient code patterns, memory usage, and timing optimizations.

6502 Performance Fundamentals

The 6502 processor runs at 1.79 MHz (NTSC) or 1.77 MHz (PAL), giving approximately 1000-1100 cycles per frame. Drwal demonstrates how to maximize this limited processing power.

Cycle Counting Basics

Instruction Type Cycles Example Optimization
Zero-page 3 LDA $80 Fastest memory access
Absolute 4 LDA $1000 Standard memory access
Indexed 4-5 LDA $1000,X +1 cycle if page crossed
Indirect 5-6 LDA ($80),Y Powerful but slower

Memory Access Optimization

Zero-Page Usage

; Drwal's strategic zero-page allocation
ZERO1           equ $0080                   ; 2 bytes - primary pointer
ZERO2           equ $0082                   ; 2 bytes - secondary pointer
ZERO3           equ $0084                   ; 2 bytes - tertiary pointer
ZERO4           equ $0086                   ; 2 bytes - quaternary pointer
PAMY            equ $0088                   ; 1 byte - temporary storage

; Fast vs slow memory access comparison
; SLOW (4 cycles):
                lda sprite_data             ; Absolute addressing
                sta player0+160             ; Absolute addressing

; FAST (3 cycles each):
                lda ZERO1                   ; Zero-page addressing
                sta ZERO2                   ; Zero-page addressing

Lookup Table Optimization

; SLOW: Runtime calculation (20+ cycles)
calculate_screen_address:
                ; Y = line number (0-19)
                lda #0                      ; Clear high byte
                sta result_hi
                tya                         ; A = line number

                ; Multiply by 40 (screen width)
                asl                         ; * 2
                rol result_hi
                asl                         ; * 4
                rol result_hi
                asl                         ; * 8
                rol result_hi
                sta temp                    ; Save * 8
                lda result_hi
                sta temp_hi

                tya                         ; Get original again
                asl                         ; * 2
                asl                         ; * 4
                asl                         ; * 8
                asl                         ; * 16
                asl                         ; * 32
                clc
                adc temp                    ; Add * 8 = * 40
                sta result
                lda result_hi
                adc temp_hi
                sta result_hi

                ; Add base address
                lda result
                clc
                adc EKRAN
                sta ZERO1+1
                rts

; FAST: Lookup table (6 cycles)
get_screen_address_fast:
                ; Y = line number (0-19)
                lda lo_adr_pnia,y           ; 4 cycles
                sta ZERO1                   ; 3 cycles
                lda hi_adr_pnia,y           ; 4 cycles
                sta ZERO1+1                 ; 3 cycles
                rts                         ; 6 cycles
                ; Total: 20 cycles vs 60+ cycles!

Loop Optimization

Unrolled Loops

; SLOW: Standard loop (7 cycles per iteration)
copy_sprite_slow:
                ldy #0                      ; 2 cycles
loop:           lda source_data,y           ; 4 cycles
                sta player0+160,y           ; 5 cycles
                iny                         ; 2 cycles
                cpy #30                     ; 2 cycles
                bne loop                    ; 3 cycles (taken)
                rts                         ; 6 cycles
                ; Total: 2 + (30 * 16) + 6 = 488 cycles

; FAST: Partially unrolled loop (4 cycles per iteration)
copy_sprite_fast:
                ldy #29                     ; 2 cycles - count down
loop:           lda source_data,y           ; 4 cycles
                sta player0+160,y           ; 5 cycles
                dey                         ; 2 cycles
                bpl loop                    ; 3 cycles (taken)
                rts                         ; 6 cycles
                ; Total: 2 + (30 * 14) + 6 = 428 cycles

; FASTEST: Fully unrolled (no loop overhead)
copy_sprite_unrolled:
                lda source_data+0           ; 4 cycles
                sta player0+160+0           ; 4 cycles
                lda source_data+1           ; 4 cycles
                sta player0+160+1           ; 4 cycles
                lda source_data+2           ; 4 cycles
                sta player0+160+2           ; 4 cycles
                ; ... repeat for all 30 bytes
                rts                         ; 6 cycles
                ; Total: (30 * 8) + 6 = 246 cycles

Drwal's Optimized Loops

; Drwal's lumberjack drawing routine (optimized)
RYS_DRWALA_L:
                ldy #3                      ; Count down from 3
@               lda drwal_linia_1-1,y       ; Use Y as 1-based index
                sta EKRAN+(17*40)+14-1,y    ; Direct screen write
                lda drwal_linia_2-1,y       ; Next line
                sta EKRAN+(18*40)+14-1,y
                lda drwal_linia_3-1,y       ; Next line
                sta EKRAN+(19*40)+14-1,y
                lda drwal_linia_4-1,y       ; Next line
                sta EKRAN+(20*40)+14-1,y
                lda drwal_linia_5-1,y       ; Next line
                sta EKRAN+(21*40)+14-1,y
                dey                         ; Count down
                bne @-                      ; Branch if not zero
                rts
                ; Draws 5 lines × 3 bytes = 15 bytes in one loop

Interrupt Optimization

VBI Performance Budget

; Drwal's VBI - carefully optimized for timing
dvbi:
                ; CRITICAL: Save registers (6 cycles)
                pha                         ; Save A
                txa                         ; 2 cycles
                pha                         ; Save X
                tya                         ; 2 cycles
                pha                         ; Save Y

                ; HIGH PRIORITY: Music (200 cycles)
                jsr RASTERMUSICTRACKER+3    ; Music update

                ; MEDIUM PRIORITY: Animation timers (50 cycles)
                lda lch0                    ; Cloud timer 0
                bne @+                      ; Skip if not zero
                lda #2                      ; Reset timer
                sta lch0
                dec pchmur0_0               ; Move cloud
@               dec lch0                    ; Decrement timer

                ; Similar for other cloud timers...

                ; LOW PRIORITY: Game logic (100 cycles)
                lda zmija_idzie             ; Snake movement
                beq @+                      ; Skip if not moving
                jsr UPDATE_SNAKE_POSITION   ; Update snake
@
                ; CRITICAL: Restore registers (9 cycles)
                pla                         ; Restore Y
                tay
                pla                         ; Restore X
                tax
                pla                         ; Restore A

                ; CRITICAL: Exit interrupt (6 cycles)
                jmp XITVBV                  ; Exit VBI

                ; Total budget: ~400 cycles (safe for 50Hz)

DLI Micro-Optimization

; Ultra-fast DLI for color changes
dli_1:
                pha                         ; 3 cycles - save A
                lda #$88                    ; 2 cycles - new color
                sta WSYNC                   ; 3 cycles - wait for sync
             :2 nop                         ; 4 cycles - precise timing
                sta COLBAK                  ; 4 cycles - change color
                lda dli1                   ; 3 cycles - next DLI high
                sta VDSLST+1                ; 4 cycles - set vector
                pla                         ; 4 cycles - restore A
                rti                         ; 6 cycles - exit
                ; Total: 40 cycles (very fast!)

Code Size Optimization

Shared Subroutines

; Instead of duplicating code, Drwal uses shared routines
; INEFFICIENT: Separate routines for each axe position
RYS_TOPOR_LG_BAD:
                ldy #0
@               lda plr0_lg,y
                sta player0+160,y
                lda plr1_lg,y
                sta player1+160,y
                lda plr2_lg,y
                sta player2+160,y
                lda plr3_lg,y
                sta player3+160,y
                iny
                cpy #30
                bne @-
                ; Set colors...
                ; Set positions...
                rts

; EFFICIENT: Parameterized routine
RYS_TOPOR:      ; X = position (0=LG, 1=PG, 2=LD, 3=PD)
                ; Calculate source address
                txa                         ; A = position
                asl                         ; * 2
                asl                         ; * 4
                asl                         ; * 8 (8 bytes per position)
                clc
                adc topor_data_table
                sta ZERO1+1

                ; Copy using pointer
                ldy #0
@               lda (ZERO1),y               ; Read from calculated address
                sta player0+160,y           ; Write to PMG
                iny
                cpy #30
                bne @-
                rts

topor_data_table:
                ; All axe positions in one table
                .HE 80 c0 e4 fc...          ; LG data
                .HE 00 00 00 00...          ; PG data
                .HE 00 00 00 00...          ; LD data
                .HE 00 00 00 00...          ; PD data

Memory Layout for Performance

Page Alignment

; Align frequently accessed data to page boundaries
                .align $100                 ; Start on page boundary
sprite_data:
                ; All sprite data here - no page crossing penalties

                .align $100                 ; Next page boundary
lookup_tables:
                ; All lookup tables here - fast indexed access

; Page crossing penalty example
; BAD: Table crosses page boundary
table_bad:      .HE 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
                ; If this starts at $xxF0, accessing table_bad+16
                ; crosses to next page, adding 1 cycle penalty

; GOOD: Table aligned to page
                .align $100
table_good:     .HE 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
                ; No page crossing possible within 256 bytes

Timing-Critical Optimizations

Precise Scanline Timing

; Drwal's precise color timing in DLI
dli_precise_color:
                pha                         ; Save A (3 cycles)
                lda #$46                    ; New color (2 cycles)
                sta WSYNC                   ; Wait for scanline start (3 cycles)
                ; Now we're at the start of the scanline
                nop                         ; 2 cycles delay
                nop                         ; 2 cycles delay
                sta COLBAK                  ; Change color (4 cycles)
                ; Color changes exactly 8 cycles into scanline
                pla                         ; Restore A (4 cycles)
                rti                         ; Exit (6 cycles)

Self-Modifying Code for Speed

; Dynamic code modification for maximum speed
setup_fast_copy:
                ; Modify copy routine based on data size
                lda copy_size               ; Get size to copy
                sta copy_loop+1             ; Modify CPY instruction

copy_loop:
                lda source,y                ; Copy data
                sta dest,y
                iny
                cpy #30                     ; This value gets modified!
                bne copy_loop
                rts

; Even faster: Modify jump targets
setup_jump_table:
                lda game_state              ; Get current state
                asl                         ; * 2 (word addresses)
                tax
                lda state_handlers,x        ; Get handler address low
                sta dynamic_jump+1          ; Modify JMP instruction
                lda state_handlers+1,x      ; Get handler address high
                sta dynamic_jump+2          ; Modify JMP instruction

dynamic_jump:   jmp $0000                   ; Address modified at runtime!

Performance Measurement

Cycle Counting Tools

; Debug: Measure routine performance
MEASURE_PERFORMANCE:
                lda VCOUNT                  ; Get current scanline
                sta start_line              ; Save start time

                jsr ROUTINE_TO_MEASURE      ; Call routine

                lda VCOUNT                  ; Get end scanline
                sec
                sbc start_line              ; Calculate difference
                sta performance_result      ; Store result

                ; Each scanline = ~114 cycles
                ; So result * 114 = approximate cycle count
                rts

Performance Optimization Guidelines

  • Use zero-page for frequently accessed variables
  • Pre-calculate expensive operations into lookup tables
  • Count down in loops when possible (BPL vs BNE)
  • Align data to avoid page crossing penalties
  • Keep VBI under 400 cycles for stable timing
  • Unroll small loops for maximum speed
  • Use self-modifying code sparingly but effectively
  • Profile on real hardware - emulators may not show true performance

Advanced Assembly Techniques - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Sophisticated assembly programming techniques including self-modifying code, indirect addressing, and advanced MADS features demonstrated in Drwal.

Self-Modifying Code

Drwal demonstrates several self-modifying code techniques for dynamic behavior and performance optimization.

Dynamic Address Modification

; Self-modifying immediate values in DLI
dli_2:
                pha
                ; These values are modified at runtime:
                lda pchmur0_0:#83           ; Cloud 0 position (modified)
                sta HPOSP0
                lda pchmur0_1:#128          ; Cloud 1 position (modified)
                sta HPOSP1
                lda pchmur0_2:#180          ; Cloud 2 position (modified)
                sta HPOSP2
                lda pchmur0_3:#243          ; Cloud 3 position (modified)
                sta HPOSP3
                pla
                rti

; Code that modifies the DLI
UPDATE_CLOUD_POSITIONS:
                ; Update cloud 0 position
                dec pchmur0_0               ; Modify the immediate value
                ; The DLI will now use the new value automatically!

                ; Update other clouds similarly
                dec pchmur0_1
                dec pchmur0_2
                dec pchmur0_3
                rts

How Self-Modification Works

The pchmur0_0:#83 syntax creates a label pointing to the immediate value byte in the LDA instruction. When we modify pchmur0_0, we're directly changing the instruction's operand!

Dynamic Jump Tables

; Self-modifying jump for state machines
GAME_STATE_HANDLER:
                ; Modify jump target based on current state
                lda game_state              ; Get current state (0-3)
                asl                         ; Multiply by 2 (word addresses)
                tax                         ; Use as index

                lda state_jump_table,x      ; Get target address low
                sta state_jump+1            ; Modify JMP instruction
                lda state_jump_table+1,x    ; Get target address high
                sta state_jump+2            ; Modify JMP instruction

state_jump:     jmp $0000                   ; Target modified at runtime

state_jump_table:
                dta STATE_TITLE
                dta STATE_GAME
                dta STATE_GAMEOVER
                dta STATE_COMPLETE

Advanced Indirect Addressing

Complex Pointer Manipulation

; Multi-level indirection for flexible data access
DRAW_TREE_SEGMENT:      ; X = tree level
                ; Get tree data
                lda drzewo,x                ; Get packed tree data
                and #%00000001              ; Extract bark type
                tay                         ; Y = bark type (0 or 1)

                ; Get bark character
                lda rodzaj_pnia,y           ; Get bark character code
                sta current_bark            ; Store for later use

                ; Calculate screen address using lookup table
                lda lo_adr_pnia,x           ; Get screen address low
                sta ZERO1                   ; Store in pointer
                lda hi_adr_pnia,x           ; Get screen address high
                sta ZERO1+1                 ; Complete pointer

                ; Draw trunk using indirect addressing
                ldy #3                      ; 4 bytes to draw
@               lda pien,y                  ; Get trunk character
                sta (ZERO1),y               ; Draw using pointer
                dey
                bpl @-

                ; Check for branches and draw them
                lda drzewo,x                ; Get tree data again
                and #%00000100              ; Check left branch bit
                beq no_left_branch          ; Skip if no branch

                ; Draw left branch using calculated offset
                lda ZERO1                   ; Get base address
                sec
                sbc #7                      ; Move 7 positions left
                sta ZERO2                   ; Store branch address
                lda ZERO1+1
                sbc #0                      ; Handle borrow
                sta ZERO2+1

                ; Draw branch data
                ldy #6                      ; 7 bytes for branch
@               lda galaz_l1,y              ; Get branch graphics
                sta (ZERO2),y               ; Draw using pointer
                dey
                bpl @-

no_left_branch:
                ; Similar logic for right branch...
                rts

Dynamic Data Structure Access

; Flexible sprite animation system
ANIMATE_SPRITE:         ; A = sprite number, X = frame number
                ; Calculate sprite data address
                ; Each sprite has 5 frames of 17 bytes each
                sta sprite_num              ; Save sprite number
                txa                         ; A = frame number

                ; Multiply frame by 17 (frame size)
                sta temp                    ; Save frame number
                asl                         ; * 2
                asl                         ; * 4
                asl                         ; * 8
                asl                         ; * 16
                clc
                adc temp                    ; Add original = * 17
                sta frame_offset            ; Save frame offset

                ; Multiply sprite number by 85 (5 frames * 17 bytes)
                lda sprite_num              ; Get sprite number
                sta temp                    ; Save it
                asl                         ; * 2
                asl                         ; * 4
                clc
                adc temp                    ; * 5
                asl                         ; * 10
                asl                         ; * 20
                asl                         ; * 40
                clc
                adc temp                    ; * 41
                asl                         ; * 82
                clc
                adc temp                    ; * 83
                clc
                adc temp                    ; * 84
                clc
                adc temp                    ; * 85

                ; Add frame offset
                clc
                adc frame_offset            ; Total offset

                ; Add to base address
                clc
                adc sprite_data_base       ; Add base address high
                sta ZERO1+1                 ; Store pointer high

                ; Copy sprite data using pointer
                ldy #16                     ; 17 bytes (0-16)
@               lda (ZERO1),y               ; Read sprite data
                sta player0+160,y           ; Write to PMG memory
                dey
                bpl @-
                rts

Advanced MADS Features

Conditional Assembly

; Conditional compilation for different versions
DEBUG           equ 1                       ; Set to 1 for debug version
NTSC            equ 0                       ; Set to 1 for NTSC, 0 for PAL

                .if DEBUG
                ; Debug-only code
DEBUG_DISPLAY:
                lda current_state           ; Show current state
                ora #$10                    ; Convert to screen code
                sta EKRAN+39                ; Display in corner
                rts
                .endif

                .if NTSC
VBI_RATE        equ 60                      ; 60 Hz for NTSC
                .else
VBI_RATE        equ 50                      ; 50 Hz for PAL
                .endif

; Use conditional values
TIMER_INIT:
                lda #VBI_RATE               ; Use appropriate rate
                sta frame_timer             ; Initialize timer
                rts

Macro Definitions

; Define reusable code patterns as macros
                .macro SET_SPRITE_COLOR
                lda #:1                     ; Parameter 1 = color
                sta COLPM:2                 ; Parameter 2 = sprite number
                .endm

                .macro WAIT_FRAMES
                lda #:1                     ; Parameter 1 = frame count
                sta wait_counter
@               lda wait_counter
                bne @-
                .endm

; Use macros in code
SETUP_SPRITES:
                SET_SPRITE_COLOR $46, 0     ; Set sprite 0 to color $46
                SET_SPRITE_COLOR $12, 1     ; Set sprite 1 to color $12
                SET_SPRITE_COLOR $36, 2     ; Set sprite 2 to color $36
                SET_SPRITE_COLOR $0A, 3     ; Set sprite 3 to color $0A

                WAIT_FRAMES 30              ; Wait 30 frames
                rts

Memory Bank Switching

Advanced Memory Management

; Simulate bank switching for large data sets
CURRENT_BANK    equ $90                     ; Current bank number
BANK_DATA_PTR   equ $91                     ; Pointer to current bank data

; Bank switching simulation
SWITCH_BANK:    ; A = bank number (0-3)
                sta CURRENT_BANK            ; Store current bank
                asl                         ; * 2 (word table)
                tax                         ; Use as index

                lda bank_table,x            ; Get bank address low
                sta BANK_DATA_PTR           ; Store pointer
                lda bank_table+1,x          ; Get bank address high
                sta BANK_DATA_PTR+1         ; Complete pointer
                rts

; Bank address table
bank_table:
                dta bank_0_data
                dta bank_1_data
                dta bank_2_data
                dta bank_3_data

; Access banked data
READ_BANKED_DATA:       ; Y = offset within bank
                lda (BANK_DATA_PTR),y       ; Read from current bank
                rts

; Bank data (each bank is 256 bytes)
bank_0_data:    .HE 00 01 02 03...          ; Bank 0 data
bank_1_data:    .HE 10 11 12 13...          ; Bank 1 data
bank_2_data:    .HE 20 21 22 23...          ; Bank 2 data
bank_3_data:    .HE 30 31 32 33...          ; Bank 3 data

Interrupt Chaining

Complex Interrupt Management

; Advanced DLI chaining system
DLI_CHAIN_SETUP:
                ; Set up first DLI in chain
                ldx dli_chain_start
                stx VDSLST                  ; Set DLI vector
                sty VDSLST+1

                lda #%11000000              ; Enable DLI + VBI
                sta NMIEN                   ; Enable interrupts
                rts

; First DLI in chain
dli_chain_start:
                pha                         ; Save A
                txa                         ; Save X
                pha

                ; Perform first DLI task
                lda #$88                    ; Sky color
                sta COLBAK                  ; Set background

                ; Set up next DLI
                ldx dli_chain_middle
                stx VDSLST                  ; Update vector
                sty VDSLST+1

                pla                         ; Restore X
                tax
                pla                         ; Restore A
                rti                         ; Exit interrupt

; Middle DLI in chain
dli_chain_middle:
                pha                         ; Save A

                ; Perform middle DLI task
                lda #$46                    ; Ground color
                sta COLBAK                  ; Set background

                ; Set up final DLI
                ldx dli_chain_end
                stx VDSLST                  ; Update vector
                sty VDSLST+1

                pla                         ; Restore A
                rti                         ; Exit interrupt

; Final DLI in chain
dli_chain_end:
                pha                         ; Save A

                ; Perform final DLI task
                lda #$00                    ; Black color
                sta COLBAK                  ; Set background

                ; Reset chain for next frame
                ldx dli_chain_start
                stx VDSLST                  ; Reset vector
                sty VDSLST+1

                pla                         ; Restore A
                rti                         ; Exit interrupt

Code Generation Techniques

Runtime Code Generation

; Generate optimized code at runtime
GENERATE_COPY_ROUTINE:  ; A = number of bytes to copy
                sta copy_count              ; Save count

                ; Generate LDA instruction
                lda #$B9                    ; LDA absolute,Y opcode
                sta generated_code+0        ; Store opcode
                lda source_data            ; Source address high
                sta generated_code+2        ; Store address high

                ; Generate STA instruction
                lda #$99                    ; STA absolute,Y opcode
                sta generated_code+3        ; Store opcode
                lda dest_data              ; Destination address high
                sta generated_code+5        ; Store address high

                ; Generate INY instruction
                lda #$C8                    ; INY opcode
                sta generated_code+6        ; Store opcode

                ; Generate CPY instruction
                lda #$C0                    ; CPY immediate opcode
                sta generated_code+7        ; Store opcode
                lda copy_count              ; Get count
                sta generated_code+8        ; Store immediate value

                ; Generate BNE instruction
                lda #$D0                    ; BNE opcode
                sta generated_code+9        ; Store opcode
                lda #$F7                    ; Branch offset (-9)
                sta generated_code+10       ; Store offset

                ; Generate RTS instruction
                lda #$60                    ; RTS opcode
                sta generated_code+11       ; Store opcode

                ; Execute generated code
                jsr generated_code          ; Call generated routine
                rts

generated_code: .DS 12                      ; Space for generated code

Advanced Debugging Techniques

Runtime Debugging

; Advanced debugging system
DEBUG_TRACE:    ; A = trace point number
                .if DEBUG
                pha                         ; Save A
                ora #$10                    ; Convert to screen code
                ldx trace_position          ; Get current position
                sta EKRAN+960,x             ; Store in bottom line
                inc trace_position          ; Move to next position
                lda trace_position
                cmp #40                     ; End of line?
                bne @+
                lda #0                      ; Reset to start
                sta trace_position
@               pla                         ; Restore A
                .endif
                rts

; Memory watch system
WATCH_MEMORY:   ; Watch specific memory location
                .if DEBUG
                lda WATCH_ADDRESS           ; Get watched value
                cmp last_watch_value        ; Compare with last
                beq no_change               ; No change

                ; Value changed - display it
                sta last_watch_value        ; Update last value
                ora #$10                    ; Convert to screen code
                sta EKRAN+999               ; Display in corner

no_change:
                .endif
                rts

Advanced Techniques Best Practices

  • Use self-modifying code sparingly - It's powerful but hard to debug
  • Document all pointer manipulations clearly
  • Test conditional assembly with all possible configurations
  • Keep generated code simple - Complex generation is error-prone
  • Use macros for repeated patterns but avoid over-abstraction
  • Profile interrupt chains carefully for timing
  • Validate all indirect addressing bounds

Animation & Visual Effects - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Advanced animation systems and visual effects programming, demonstrated through Drwal's sophisticated multi-layer animation, timing systems, and visual feedback mechanisms.

Multi-Frame Animation System

Drwal implements a complex animation system with multiple independent animated elements running simultaneously.

Snake Animation Framework

; Snake animation data - 5 frames per direction
; Right-moving snake frames
pzmija0_0:      .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00
pzmija0_1:      .HE 00 00 00 00 00 00 00 00 00 20 00 23 33 1c 14 08 00
pzmija0_2:      .HE 00 04 02 02 06 04 04 0c 08 08 0c 0c 0f 0b 0c 04 00
pzmija0_3:      .HE 00 00 00 00 00 00 00 00 00 20 00 23 33 1c 14 08 00
pzmija0_4:      .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00

; Left-moving snake frames
lzmija0_0:      .HE 00 00 00 00 00 00 00 00 00 00 01 02 c4 cc 38 20 00
lzmija0_1:      .HE 00 00 00 00 00 00 00 00 00 04 00 c4 cc 38 28 10 00
lzmija0_2:      .HE 00 20 40 40 60 20 20 30 10 10 30 30 f0 d0 30 20 00
lzmija0_3:      .HE 00 00 00 00 00 00 00 00 04 00 c4 cc 38 28 10 00 00
lzmija0_4:      .HE 00 00 00 00 00 00 00 00 00 00 01 02 c4 cc 38 20 00

; Animation state variables
klatka_zmii:    dta 0                       ; Current frame (0-4)
kierunek_zmii:  dta 1                       ; Direction (0=left, 1=right)
zmija_timer:    dta 0                       ; Animation timing

Animation Update System

; Snake animation update (called from VBI)
UPDATE_SNAKE_ANIMATION:
                lda zmija_idzie             ; Is snake moving?
                beq no_snake_animation      ; No movement = no animation

                ; Update animation timer
                dec zmija_timer             ; Decrease timer
                bne no_frame_change         ; Not time for new frame

                ; Reset timer and advance frame
                lda #3                      ; 3 frames between changes
                sta zmija_timer             ; Reset timer

                inc klatka_zmii             ; Next frame
                lda klatka_zmii
                cmp #5                      ; 5 frames total
                bne frame_ok                ; Frame valid
                lda #0                      ; Reset to frame 0
                sta klatka_zmii

frame_ok:
                ; Load new frame data
                jsr LOAD_SNAKE_FRAME        ; Load current frame

no_frame_change:
no_snake_animation:
                rts

; Load snake frame based on direction and frame number
LOAD_SNAKE_FRAME:
                lda kierunek_zmii           ; Get direction
                bne load_right_frame        ; 1 = right direction

load_left_frame:
                ; Calculate left frame address
                lda klatka_zmii             ; Get frame number
                asl                         ; * 2
                asl                         ; * 4
                asl                         ; * 8
                asl                         ; * 16
                clc
                adc klatka_zmii             ; Add original = * 17
                clc
                adc lzmija0_0
                sta ZERO1+1
                jmp copy_frame_data

load_right_frame:
                ; Calculate right frame address
                lda klatka_zmii             ; Get frame number
                asl                         ; * 2
                asl                         ; * 4
                asl                         ; * 8
                asl                         ; * 16
                clc
                adc klatka_zmii             ; Add original = * 17
                clc
                adc pzmija0_0
                sta ZERO1+1

copy_frame_data:
                ; Copy frame to PMG memory
                ldy #16                     ; 17 bytes per frame
@               lda (ZERO1),y               ; Read frame data
                sta player0+200,y           ; Write to snake area
                dey
                bpl @-
                rts

Cloud Animation System

Independent Cloud Movement

; Cloud animation timers (different speeds)
lch0:           dta 0                       ; Cloud 0 timer
lch1:           dta 0                       ; Cloud 1 timer
lch2:           dta 0                       ; Cloud 2 timer
lch3:           dta 0                       ; Cloud 3 timer

; Cloud positions (modified by DLI)
pchmur0_0:      dta 83                      ; Cloud 0 X position
pchmur0_1:      dta 128                     ; Cloud 1 X position
pchmur0_2:      dta 180                     ; Cloud 2 X position
pchmur0_3:      dta 243                     ; Cloud 3 X position

; Cloud animation update (called from VBI)
UPDATE_CLOUD_ANIMATION:
                ; Cloud 0 - speed 2
                lda lch0                    ; Get timer
                bne @+                      ; Skip if not zero
                lda #2                      ; Reset timer (speed)
                sta lch0
                dec pchmur0_0               ; Move cloud left
                bne @+                      ; Check for wrap
                lda #255                    ; Wrap to right side
                sta pchmur0_0
@               dec lch0                    ; Decrement timer

                ; Cloud 1 - speed 3
                lda lch1                    ; Get timer
                bne @+                      ; Skip if not zero
                lda #3                      ; Reset timer (slower)
                sta lch1
                dec pchmur0_1               ; Move cloud left
                bne @+                      ; Check for wrap
                lda #255                    ; Wrap to right side
                sta pchmur0_1
@               dec lch1                    ; Decrement timer

                ; Cloud 2 - speed 4
                lda lch2                    ; Get timer
                bne @+                      ; Skip if not zero
                lda #4                      ; Reset timer (even slower)
                sta lch2
                dec pchmur0_2               ; Move cloud left
                bne @+                      ; Check for wrap
                lda #255                    ; Wrap to right side
                sta pchmur0_2
@               dec lch2                    ; Decrement timer

                ; Cloud 3 - speed 5
                lda lch3                    ; Get timer
                bne @+                      ; Skip if not zero
                lda #5                      ; Reset timer (slowest)
                sta lch3
                dec pchmur0_3               ; Move cloud left
                bne @+                      ; Check for wrap
                lda #255                    ; Wrap to right side
                sta pchmur0_3
@               dec lch3                    ; Decrement timer

                rts

Visual Feedback Effects

Star Animation for Tree Hits

; Star animation data
gwiazdki_1:     .HE 3b 3c 3d                ; Star pattern 1
gwiazdki_2:     .HE 3d 3b 3c                ; Star pattern 2
gwiazdki_3:     .HE 3c 3d 3b                ; Star pattern 3

; Animate stars when tree is hit
ANIM_GWIAZDEK:
                ; Set up screen address for stars
                jsr ADR_LINII               ; Calculate line address

                ; Animation loop - repeat 3 times
                lda #3                      ; 3 animation cycles
                sta PAMY                    ; Store counter

anim_gwiazdek:
                ; First star pattern
                ldy #2                      ; 3 bytes to copy
@               lda gwiazdki_1,y            ; Get star data
                sta (ZERO1),y               ; Draw on screen
                dey
                bpl @-
                jsr PAUSE5                  ; Brief pause

                ; Second star pattern
                ldy #2                      ; 3 bytes to copy
@               lda gwiazdki_2,y            ; Get star data
                sta (ZERO1),y               ; Draw on screen
                dey
                bpl @-
                jsr PAUSE5                  ; Brief pause

                ; Third star pattern
                ldy #2                      ; 3 bytes to copy
@               lda gwiazdki_3,y            ; Get star data
                sta (ZERO1),y               ; Draw on screen
                dey
                bpl @-
                jsr PAUSE5                  ; Brief pause

                ; Repeat animation
                ldx PAMY                    ; Get counter
                dex                         ; Decrease
                bne anim_gwiazdek           ; Continue if not zero

                rts

Power Bar Animation

; Power bar visual representation
EKR_WSKAZNIK:   .HE 00 00 00 00 00 00 00 00 00 00
                .HE 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b  ; Full power chars
                .HE 5c 5c 5c 5c 5c 5c 5c 5c 5c 5c  ; Empty power chars
                .HE 00 00 00 00 00 00 00 00 00 00

; Update power bar display
RYS_WSKAZNIK:   ; A = current power level (0-80)
                ; Convert power to bar segments
                lsr                         ; Divide by 2
                lsr                         ; Divide by 4
                lsr                         ; Divide by 8 (0-10 segments)
                tax                         ; X = filled segments

                ; Fill power bar
                ldy #0                      ; Start at beginning

fill_segments:
                cpx #0                      ; Any segments to fill?
                beq empty_segments          ; No - fill with empty

                lda #$5b                    ; Full power character
                sta EKR_WSKAZNIK+10,y       ; Store in bar
                iny                         ; Next position
                dex                         ; One less segment
                cpy #10                     ; End of bar?
                bne fill_segments           ; Continue
                rts                         ; Done

empty_segments:
                cpy #10                     ; End of bar?
                beq done_bar                ; Yes - finished

                lda #$5c                    ; Empty power character
                sta EKR_WSKAZNIK+10,y       ; Store in bar
                iny                         ; Next position
                jmp empty_segments          ; Continue

done_bar:
                rts

Timing and Synchronization

Frame-Based Animation Timing

; Master animation timer system
frame_counter:  dta 0                       ; Global frame counter
anim_phase:     dta 0                       ; Animation phase (0-3)

; Update all animations (called from VBI)
UPDATE_ALL_ANIMATIONS:
                inc frame_counter           ; Increment global counter

                ; Update animation phase every 4 frames
                lda frame_counter           ; Get counter
                and #%00000011              ; Mask to 0-3
                sta anim_phase              ; Store phase

                ; Update different animations at different rates
                lda frame_counter           ; Get counter
                and #%00000001              ; Every 2 frames
                bne @+
                jsr UPDATE_FAST_ANIMATIONS  ; Fast animations

@               lda frame_counter           ; Get counter
                and #%00000011              ; Every 4 frames
                bne @+
                jsr UPDATE_MEDIUM_ANIMATIONS ; Medium animations

@               lda frame_counter           ; Get counter
                and #%00000111              ; Every 8 frames
                bne @+
                jsr UPDATE_SLOW_ANIMATIONS  ; Slow animations

@               rts

; Different animation speeds
UPDATE_FAST_ANIMATIONS:
                ; Snake movement, player actions
                jsr UPDATE_SNAKE_ANIMATION
                jsr UPDATE_PLAYER_ANIMATION
                rts

UPDATE_MEDIUM_ANIMATIONS:
                ; Cloud movement, power decrease
                jsr UPDATE_CLOUD_ANIMATION
                jsr UPDATE_POWER_DECREASE
                rts

UPDATE_SLOW_ANIMATIONS:
                ; Background effects, score display
                jsr UPDATE_BACKGROUND_EFFECTS
                rts

Color Cycling Effects

Dynamic Color Changes

; Color cycling for visual interest
color_cycle_counter: dta 0                  ; Cycle counter
sky_colors:     .HE $88,$8A,$8C,$8E,$8C,$8A ; Sky color cycle

; Update color cycling (called from VBI)
UPDATE_COLOR_CYCLING:
                inc color_cycle_counter     ; Advance counter
                lda color_cycle_counter
                and #%00111111              ; 64-frame cycle
                lsr                         ; Divide by 2
                lsr                         ; Divide by 4
                lsr                         ; Divide by 8 (8-frame steps)
                tax                         ; Use as index

                lda sky_colors,x            ; Get color for this step
                sta sky_color_current       ; Store for DLI use
                rts

; DLI that uses cycling color
dli_sky_cycle:
                pha                         ; Save A
                lda sky_color_current       ; Get current sky color
                sta WSYNC                   ; Wait for sync
                sta COLBAK                  ; Set background color
                pla                         ; Restore A
                rti                         ; Exit interrupt

Particle Effects

Simple Particle System

; Particle system for wood chips
MAX_PARTICLES   equ 4                       ; Maximum particles

; Particle data structure
particle_active: .DS MAX_PARTICLES          ; Active flags
particle_x:     .DS MAX_PARTICLES           ; X positions
particle_y:     .DS MAX_PARTICLES           ; Y positions
particle_vx:    .DS MAX_PARTICLES           ; X velocities
particle_vy:    .DS MAX_PARTICLES           ; Y velocities
particle_life:  .DS MAX_PARTICLES           ; Lifetime counters

; Create particle effect
CREATE_PARTICLES:       ; X = source X, Y = source Y
                stx source_x                ; Save source position
                sty source_y

                ldx #0                      ; Start with particle 0
create_loop:
                lda particle_active,x       ; Is this particle free?
                bne next_particle           ; No - try next

                ; Initialize new particle
                lda #1                      ; Mark as active
                sta particle_active,x

                lda source_x                ; Set initial position
                sta particle_x,x
                lda source_y
                sta particle_y,x

                ; Random velocity
                lda RANDOM                  ; Get random number
                and #%00000111              ; Mask to 0-7
                sec
                sbc #4                      ; Make it -4 to +3
                sta particle_vx,x           ; Set X velocity

                lda RANDOM                  ; Get another random
                and #%00000011              ; Mask to 0-3
                sec
                sbc #6                      ; Make it -6 to -3 (upward)
                sta particle_vy,x           ; Set Y velocity

                lda #30                     ; 30 frame lifetime
                sta particle_life,x         ; Set lifetime

                rts                         ; Created one particle

next_particle:
                inx                         ; Try next particle
                cpx #MAX_PARTICLES          ; End of list?
                bne create_loop             ; No - continue
                rts                         ; No free particles

; Update particle system (called from VBI)
UPDATE_PARTICLES:
                ldx #0                      ; Start with particle 0
update_loop:
                lda particle_active,x       ; Is particle active?
                beq next_update             ; No - skip

                ; Update position
                lda particle_x,x            ; Get X position
                clc
                adc particle_vx,x           ; Add X velocity
                sta particle_x,x            ; Store new X

                lda particle_y,x            ; Get Y position
                clc
                adc particle_vy,x           ; Add Y velocity
                sta particle_y,x            ; Store new Y

                ; Apply gravity to Y velocity
                inc particle_vy,x           ; Gravity effect

                ; Update lifetime
                dec particle_life,x         ; Decrease lifetime
                bne next_update             ; Still alive

                ; Particle died
                lda #0                      ; Mark as inactive
                sta particle_active,x

next_update:
                inx                         ; Next particle
                cpx #MAX_PARTICLES          ; End of list?
                bne update_loop             ; No - continue
                rts                         ; Done

Animation System Design Principles

  • Use frame-based timing for consistent animation speed
  • Separate animation data from logic for easy modification
  • Implement different update rates for performance optimization
  • Use lookup tables for smooth interpolation
  • Keep particle systems simple on 8-bit hardware
  • Synchronize visual effects with audio feedback
  • Test animations at both 50Hz and 60Hz refresh rates

Debugging & Development Workflow - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Professional debugging techniques and development workflow practices demonstrated through Drwal's well-organized codebase and debugging-friendly structure.

Code Organization for Debugging

Drwal demonstrates excellent code organization that makes debugging and maintenance much easier.

Comment Conventions

; Drwal's consistent commenting style
; ------------------------------------------------------------------------------------------------
;               SECTION HEADER
; ------------------------------------------------------------------------------------------------

; ------------------------------------------------------------------------------------------------
;               SUBROUTINE NAME (PARAMETERS)
; Description of what the subroutine does
; Parameters: X = parameter description
;            Y = parameter description
; Returns:   A = return value description
; Modifies:  X, Y, ZERO1, ZERO2
SUBROUTINE_NAME:
                ; Step-by-step comments
                lda parameter               ; Get input parameter
                asl                         ; Multiply by 2 for word table
                tax                         ; Use as index

                ; Detailed explanation of complex operations
                lda lookup_table,x          ; Get value from table
                sta result                  ; Store result
                rts                         ; Return to caller

; ------------------------------------------------------------------------------------------------

Meaningful Label Names

; GOOD: Descriptive labels from Drwal
RYS_DRWALA_L:                               ; Draw lumberjack left
KAS_DRWALA_P:                               ; Clear lumberjack right
RYS_TOPOR_LG:                               ; Draw axe left-top
ANIM_GWIAZDEK:                              ; Animate stars
KONIEC_ETAPU:                               ; End of level
WCISNIJ_FIRE_START:                         ; Wait for fire/start

; BAD: Cryptic labels (avoid these)
SUB1:                                       ; What does this do?
LOOP:                                       ; Which loop?
TEMP:                                       ; Temporary what?
DATA:                                       ; What kind of data?

Debugging Techniques

Visual Debugging

; Visual debugging - show values on screen
DEBUG_SHOW_VALUE:       ; A = value to display
                pha                         ; Save original value
                lsr                         ; Get high nibble
                lsr
                lsr
                lsr
                ora #$10                    ; Convert to screen code
                sta EKRAN+39                ; Display in top-right

                pla                         ; Restore original
                and #$0F                    ; Get low nibble
                ora #$10                    ; Convert to screen code
                sta EKRAN+79                ; Display below high nibble
                rts

; Show program flow
DEBUG_TRACE:            ; A = trace point number
                ora #$10                    ; Convert to screen code
                ldx trace_position          ; Get current position
                sta EKRAN+920,x             ; Store in trace line
                inc trace_position          ; Move to next position
                lda trace_position
                cmp #40                     ; End of line?
                bne @+
                lda #0                      ; Reset to start
                sta trace_position
@               rts

trace_position: dta 0                       ; Current trace position

Memory Watching

; Watch critical memory locations
WATCH_MEMORY:
                ; Watch player position
                lda pam_pozdrwala           ; Get player position
                cmp last_player_pos         ; Compare with last
                beq @+                      ; No change
                sta last_player_pos         ; Update last value
                ora #$10                    ; Convert to screen code
                sta EKRAN+0                 ; Display position

@               ; Watch power level
                lda power                   ; Get power level
                cmp last_power              ; Compare with last
                beq @+                      ; No change
                sta last_power              ; Update last value
                lsr                         ; Divide by 16 for display
                lsr
                lsr
                lsr
                ora #$10                    ; Convert to screen code
                sta EKRAN+1                 ; Display power

@               ; Watch snake state
                lda zmija_idzie             ; Get snake state
                cmp last_snake_state        ; Compare with last
                beq @+                      ; No change
                sta last_snake_state        ; Update last value
                ora #$10                    ; Convert to screen code
                sta EKRAN+2                 ; Display state

@               rts

; Last known values for comparison
last_player_pos:    dta $FF                 ; Initialize to invalid
last_power:         dta $FF
last_snake_state:   dta $FF

Error Handling

Bounds Checking

; Safe array access with bounds checking
SAFE_ARRAY_ACCESS:      ; X = index, returns A = value or $FF if error
                cpx #20                     ; Check upper bound
                bcs array_error             ; Index too high

                lda drzewo,x                ; Access array safely
                rts                         ; Return value

array_error:
                lda #$FF                    ; Error value
                ; Optional: Set error flag or display error
                inc error_count             ; Count errors
                rts

; Safe pointer operations
SAFE_POINTER_WRITE:     ; A = value to write
                ; Check if pointer is valid
                lda ZERO1+1                 ; Get high byte of pointer
                cmp #$40                    ; Check if in screen memory
                bcc pointer_error           ; Too low
                cmp #$50                    ; Check upper bound
                bcs pointer_error           ; Too high

                ; Pointer is safe - write value
                ldy #0                      ; Offset 0
                lda write_value             ; Get value to write
                sta (ZERO1),y               ; Write safely
                rts

pointer_error:
                ; Handle pointer error
                inc pointer_errors          ; Count errors
                rts

error_count:        dta 0                   ; Error counter
pointer_errors:     dta 0                   ; Pointer error counter
write_value:        dta 0                   ; Value to write

State Validation

; Validate game state consistency
VALIDATE_GAME_STATE:
                ; Check player position is valid
                lda pam_pozdrwala           ; Get player position
                cmp #4                      ; Valid range is 0-3
                bcs state_error             ; Invalid position

                ; Check power level is reasonable
                lda power                   ; Get power level
                cmp #81                     ; Maximum is 80
                bcs state_error             ; Power too high

                ; Check snake state is valid
                lda zmija_idzie             ; Get snake movement
                cmp #200                    ; Reasonable maximum
                bcs state_error             ; Snake state invalid

                ; All checks passed
                lda #0                      ; No error
                sta state_error_flag
                rts

state_error:
                ; State validation failed
                lda #1                      ; Error flag
                sta state_error_flag
                inc state_errors            ; Count errors

                ; Optional: Reset to safe state
                lda #0                      ; Safe player position
                sta pam_pozdrwala
                lda #40                     ; Safe power level
                sta power
                lda #0                      ; Safe snake state
                sta zmija_idzie
                rts

state_error_flag:   dta 0                   ; Current error state
state_errors:       dta 0                   ; Total error count

Development Tools

Conditional Debug Code

; Conditional compilation for debug features
DEBUG           equ 1                       ; Set to 1 for debug build

                .if DEBUG
                ; Debug-only variables
debug_enabled:  dta 1
trace_buffer:   .DS 256                     ; Trace buffer
                .endif

; Debug macros
                .macro DEBUG_PRINT
                .if DEBUG
                lda #:1                     ; Parameter 1 = character
                jsr DEBUG_CHAR              ; Print debug character
                .endif
                .endm

                .macro DEBUG_VALUE
                .if DEBUG
                lda :1                      ; Parameter 1 = value
                jsr DEBUG_SHOW_VALUE        ; Show value on screen
                .endif
                .endm

; Usage in code
MAIN_LOOP:
                DEBUG_PRINT 'M'             ; Mark main loop entry

                jsr HANDLE_INPUT            ; Handle input
                DEBUG_VALUE pam_joya        ; Show joystick state

                jsr UPDATE_GAME             ; Update game
                DEBUG_PRINT 'U'             ; Mark update complete

                jmp MAIN_LOOP               ; Continue loop

Performance Monitoring

; Performance monitoring system
PERF_START_TIMER:       ; Start performance timer
                lda VCOUNT                  ; Get current scanline
                sta perf_start_line         ; Save start time
                rts

PERF_END_TIMER:         ; End performance timer and display result
                lda VCOUNT                  ; Get current scanline
                sec
                sbc perf_start_line         ; Calculate difference
                bpl @+                      ; Positive result
                clc
                adc #262                    ; Handle frame wrap (NTSC)
@               sta perf_result             ; Store result

                ; Display result (each line ≈ 114 cycles)
                lsr                         ; Divide by 2 for display
                ora #$10                    ; Convert to screen code
                sta EKRAN+38                ; Show in corner
                rts

; Usage example
MEASURE_ROUTINE:
                jsr PERF_START_TIMER        ; Start timing
                jsr EXPENSIVE_ROUTINE       ; Routine to measure
                jsr PERF_END_TIMER          ; End timing and display
                rts

perf_start_line:    dta 0                   ; Start scanline
perf_result:        dta 0                   ; Performance result

Testing Strategies

Unit Testing Framework

; Simple unit testing for assembly
TEST_FRAMEWORK:
                lda #0                      ; Reset test counter
                sta tests_passed
                sta tests_failed

                ; Run individual tests
                jsr TEST_PLAYER_MOVEMENT
                jsr TEST_COLLISION_DETECTION
                jsr TEST_ANIMATION_SYSTEM

                ; Display results
                lda tests_passed            ; Get passed count
                ora #$10                    ; Convert to screen code
                sta EKRAN+0                 ; Display passed

                lda tests_failed            ; Get failed count
                ora #$10                    ; Convert to screen code
                sta EKRAN+1                 ; Display failed
                rts

; Individual test
TEST_PLAYER_MOVEMENT:
                ; Setup test conditions
                lda #0                      ; Start position
                sta pam_pozdrwala

                ; Perform operation
                jsr MOVE_PLAYER_RIGHT       ; Function to test

                ; Check result
                lda pam_pozdrwala           ; Get new position
                cmp #1                      ; Expected result
                beq test_passed             ; Test passed

                ; Test failed
                inc tests_failed            ; Count failure
                rts

test_passed:
                inc tests_passed            ; Count success
                rts

tests_passed:   dta 0                       ; Passed test counter
tests_failed:   dta 0                       ; Failed test counter

Build and Deployment

Build Configuration

; Build configuration options
VERSION_MAJOR   equ 2                       ; Major version
VERSION_MINOR   equ 0                       ; Minor version
VERSION_PATCH   equ 8                       ; Patch version

BUILD_DEBUG     equ 0                       ; Debug build flag
BUILD_NTSC      equ 0                       ; NTSC/PAL flag
BUILD_JOYCART   equ 1                       ; Joycart support flag

; Conditional features based on build config
                .if BUILD_DEBUG
                ; Include debug features
                icl 'debug.asm'
                .endif

                .if BUILD_NTSC
REFRESH_RATE    equ 60                      ; NTSC refresh rate
                .else
REFRESH_RATE    equ 50                      ; PAL refresh rate
                .endif

                .if BUILD_JOYCART
                ; Include Joycart support code
                icl 'joycart.asm'
                .endif

; Version display
SHOW_VERSION:
                lda #VERSION_MAJOR          ; Show major version
                ora #$10
                sta EKRAN+0
                lda #VERSION_MINOR          ; Show minor version
                ora #$10
                sta EKRAN+1
                lda #VERSION_PATCH          ; Show patch version
                ora #$10
                sta EKRAN+2
                rts

Debugging Best Practices

  • Use consistent naming conventions throughout the project
  • Comment complex algorithms step by step
  • Implement bounds checking for array and pointer access
  • Use conditional compilation for debug features
  • Monitor performance of critical routines
  • Validate game state regularly during development
  • Test on real hardware as well as emulators
  • Keep debug code simple to avoid introducing bugs

Project Structure & Build Process - Mad Assembler (MADS) Examples from Drwal

Mad Assembler Documentation - Based on Drwal Game Analysis

Professional project organization and build process management, demonstrated through Drwal's well-structured file organization and asset pipeline.

File Organization

Drwal demonstrates excellent project structure that separates concerns and makes the project maintainable.

Directory Structure

drwal/
├── drwal.asm                    # Main source file
├── etykiety.hea                 # Label definitions and constants
├── grafika/                     # Graphics assets
│   ├── fntA.fnt                # Font A - UI elements
│   ├── fntB_0.fnt              # Font B0 - Game graphics
│   ├── fntB_1.fnt              # Font B1 - Alternative graphics
│   ├── drwal.sved2             # Main screen data
│   └── drwal_gr7.gr7           # Title screen graphics
├── muzyka/                      # Audio assets
│   ├── rmtplayr.a65            # RMT player source
│   └── drwal_modul.rmt         # Music module
└── build/                       # Build output (generated)
    ├── drwal.xex               # Final executable
    ├── drwal.lst               # Assembly listing
    └── drwal.lab               # Label file

Main Source File Structure

; drwal.asm - Main source file organization
; ================================================================================================
; HEADER SECTION
; ================================================================================================
; Drwal
; skromna gra
; wersja: 2025.10.08

; ================================================================================================
; INCLUDES AND CONSTANTS
; ================================================================================================
                icl 'etykiety.hea'          ; External label definitions

; Zero-page allocation
ZERO1           equ $0080                   ; General pointer 1
ZERO2           equ $0082                   ; General pointer 2
ZERO3           equ $0084                   ; General pointer 3
ZERO4           equ $0086                   ; General pointer 4
PAMY            equ $0088                   ; Temporary storage

; ================================================================================================
; MEMORY LAYOUT
; ================================================================================================
                org $2000                   ; Program start address

; ================================================================================================
; DATA SECTION
; ================================================================================================
pmg:            ; PMG memory area
                ; Game data stored in unused PMG space

; Graphics data
plr0_lg:        .HE 80 c0 e4 fc...         ; Player 0 left-top graphics
plr1_lg:        .HE 20 60 60 60...         ; Player 1 left-top graphics
; ... more graphics data

; Game variables
drzewo:         dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
pam_joya:       dta %00001100
power:          dta 40
; ... more variables

; ================================================================================================
; PMG GRAPHICS SECTION
; ================================================================================================
                org pmg+$300               ; PMG data area
missile:        .HE 00 00 00...            ; Missile graphics
player0:        .HE 00 00 00...            ; Player 0 graphics
player1:        .HE 00 00 00...            ; Player 1 graphics
player2:        .HE 00 00 00...            ; Player 2 graphics
player3:        .HE 00 00 00...            ; Player 3 graphics

; ================================================================================================
; FONT DATA SECTION
; ================================================================================================
FNTA:           ins 'grafika/fntA.fnt'     ; Font A
FNTB_0:         ins 'grafika/fntB_0.fnt'   ; Font B0
FNTB_1:         ins 'grafika/fntB_1.fnt'   ; Font B1

; ================================================================================================
; ANIMATION DATA SECTION
; ================================================================================================
; Snake animation frames
pzmija0_0:      .HE 00 00 00...            ; Right frame 0
pzmija0_1:      .HE 00 00 00...            ; Right frame 1
; ... more animation data

; ================================================================================================
; SCREEN MEMORY SECTION
; ================================================================================================
                .align $1000               ; 4K alignment for screen
EKRAN:          ins 'grafika/drwal.sved2'  ; Main screen data

; ================================================================================================
; DISPLAY LIST SECTION
; ================================================================================================
DISPLAY_LIST:
                dta $70                    ; Blank lines
                dta %11110000              ; DLI enabled line
                ; ... display list data

; ================================================================================================
; INTERRUPT HANDLERS SECTION
; ================================================================================================
; Display List Interrupts
dli:
dli_2:          pha
                ; Cloud setup code
                pla
                rti

; Vertical Blank Interrupt
dvbi:           ; Game timing and animation
                jmp XITVBV

; ================================================================================================
; MAIN PROGRAM SECTION
; ================================================================================================
START:          ; Program entry point
                jsr OBRAZ_OFF
                jsr EKRAN_TYTULOWY
                jsr INICJOWANIE
                jmp EKRAN_GRY

; ================================================================================================
; SUBROUTINES SECTION
; ================================================================================================
; Drawing routines
RYS_DRWALA:     ; Draw lumberjack
                ; ... implementation

; Game logic routines
HANDLE_INPUT:   ; Input processing
                ; ... implementation

; ================================================================================================
; AUDIO SECTION
; ================================================================================================
                icl 'muzyka/rmtplayr.a65'  ; RMT player
                ins 'muzyka/drwal_modul.rmt' ; Music data

; ================================================================================================
; PROGRAM END
; ================================================================================================
                run START                  ; Set run address
                end                        ; End of assembly

Asset Management

Graphics Asset Pipeline

; Graphics file inclusion with proper organization
; ================================================================================================
; FONT ASSETS
; ================================================================================================
; Font A - User interface elements (score, text)
FNTA:
                ins 'grafika/fntA.fnt'

; Font B0 - Game world graphics (tree, background)
FNTB_0:
                ins 'grafika/fntB_0.fnt'

; Font B1 - Character graphics (lumberjack sprites)
FNTB_1:
                ins 'grafika/fntB_1.fnt'

; ================================================================================================
; SCREEN ASSETS
; ================================================================================================
; Main game screen (40x24 characters)
                .align $1000               ; Must be 4K aligned
EKRAN:
                ins 'grafika/drwal.sved2'

; Title screen (Graphics 7 mode)
EKRAN_TYT:
                ins 'grafika/drwal_gr7.gr7',0,3040

; ================================================================================================
; SPRITE ASSETS
; ================================================================================================
; All sprite data is defined inline for performance
; This allows for easy modification and optimization

Audio Asset Integration

; ================================================================================================
; AUDIO SYSTEM INTEGRATION
; ================================================================================================

; RMT Player configuration
STEREOMODE      equ 0                      ; 0 = mono, 1 = stereo

; Include RMT player source
                icl 'muzyka/rmtplayr.a65'

; ================================================================================================
; MUSIC DATA
; ================================================================================================
; Music module location
MODUL_RMT       equ $8600                  ; Load address for music

; Include music module
                opt h-                     ; Disable headers
                ins 'muzyka/drwal_modul.rmt'
                opt h+                     ; Re-enable headers
MODUL_RMT_END:

; ================================================================================================
; AUDIO INTERFACE
; ================================================================================================
; Music control routines
RMT_SET:        ; A = music line number
                ldx MODUL_RMT
                jsr RASTERMUSICTRACKER
                rts

RMT_OFF:        ; Stop music
                lda #$46                   ; Silent line
                jsr RMT_SET
                jsr RASTERMUSICTRACKER+9   ; Mute
                rts

SFX_SET:        ; A = effect number
                asl                        ; * 2
                tay                        ; Effect index
                ldx #0                     ; Channel 0
                txa                        ; Pitch 0
                jsr RASTERMUSICTRACKER+15  ; Play SFX
                rts

Build Configuration

MADS Assembly Options

; Build configuration directives
                opt h+                     ; Enable Atari headers
                opt l+                     ; Generate listing file
                opt s+                     ; Generate symbol table

; Conditional assembly for different builds
DEBUG_BUILD     equ 0                      ; 0 = release, 1 = debug
NTSC_VERSION    equ 0                      ; 0 = PAL, 1 = NTSC
JOYCART_SUPPORT equ 1                      ; 0 = standard, 1 = enhanced

                .if DEBUG_BUILD
                ; Debug-specific code
                icl 'debug_routines.asm'
                .endif

                .if NTSC_VERSION
FRAME_RATE      equ 60                     ; NTSC frame rate
                .else
FRAME_RATE      equ 50                     ; PAL frame rate
                .endif

Memory Map Documentation

; ================================================================================================
; MEMORY MAP DOCUMENTATION
; ================================================================================================
; $0000-$007F: OS zero page (reserved)
; $0080-$00FF: Program zero page variables
;   $0080-$0087: Pointer variables (ZERO1-ZERO4)
;   $0088-$008F: Temporary variables
;   $0090-$00FF: Available for expansion
;
; $0100-$01FF: 6502 stack (reserved)
; $0200-$02FF: OS page 2 (reserved)
; $0300-$1FFF: Available for program use
;
; $2000-$2FFF: Main program code
; $3000-$3FFF: PMG data and game variables
; $4000-$4FFF: Screen memory (4K aligned)
; $5000-$5FFF: Display list and small data
; $6000-$7FFF: Available for expansion
; $8000-$85FF: RMT player code
; $8600-$8FFF: Music module data
; $9000-$BFFF: Available for expansion
; $C000-$CFFF: OS ROM (reserved)
; $D000-$D7FF: Hardware registers (reserved)
; $D800-$FFFF: OS ROM (reserved)
; ================================================================================================

Version Control Integration

Version Information

; ================================================================================================
; VERSION INFORMATION
; ================================================================================================
VERSION_MAJOR   equ 2                      ; Major version number
VERSION_MINOR   equ 0                      ; Minor version number
VERSION_PATCH   equ 8                      ; Patch version number
VERSION_DATE    dta d'2025.10.08'          ; Build date

; Version display routine
SHOW_VERSION:
                ; Display version on title screen
                lda #VERSION_MAJOR
                ora #$10                   ; Convert to screen code
                sta EKRAN_TYT+100          ; Show major version

                lda #VERSION_MINOR
                ora #$10
                sta EKRAN_TYT+101          ; Show minor version

                lda #VERSION_PATCH
                ora #$10
                sta EKRAN_TYT+102          ; Show patch version
                rts

Build Automation

Makefile Example

# Makefile for Drwal project
# Requires MADS assembler

# Configuration
MADS = mads
SOURCE = drwal.asm
TARGET = build/drwal.xex
LISTING = build/drwal.lst
LABELS = build/drwal.lab

# Build flags
MADS_FLAGS = -s -l:$(LISTING) -t:$(LABELS)

# Default target
all: $(TARGET)

# Build executable
$(TARGET): $(SOURCE) etykiety.hea grafika/* muzyka/*
	@mkdir -p build
	$(MADS) $(MADS_FLAGS) $(SOURCE) -o:$(TARGET)

# Debug build
debug: MADS_FLAGS += -d:DEBUG_BUILD=1
debug: $(TARGET)

# NTSC build
ntsc: MADS_FLAGS += -d:NTSC_VERSION=1
ntsc: $(TARGET)

# Clean build files
clean:
	rm -rf build/*

# Install to disk image
install: $(TARGET)
	cp $(TARGET) disk/drwal.xex

# Run in emulator
run: $(TARGET)
	atari800 -xl -cart $(TARGET)

.PHONY: all debug ntsc clean install run

Documentation Generation

Automated Documentation

; ================================================================================================
; DOCUMENTATION MARKERS
; ================================================================================================
; Use special comment markers for automated documentation generation

;@ROUTINE: RYS_DRWALA
;@PURPOSE: Draw lumberjack character at specified position
;@INPUT:   X = position (0=left-top, 1=right-bottom, 2=right-top, 3=left-bottom)
;@OUTPUT:  None
;@MODIFIES: A, Y, ZERO1, ZERO2
;@CALLS:   KAS_DRWALA_P, RYS_TOPOR_LD, RYS_DRWALA_L, KAS_GALAZ_P
;@EXAMPLE: ldx #0 / jsr RYS_DRWALA  ; Draw lumberjack at left-top
RYS_DRWALA:
                cpx #3
                bne poz_2
                jsr KAS_DRWALA_P
                jsr RYS_TOPOR_LD
                jsr RYS_DRWALA_L
                jsr KAS_GALAZ_P
                rts

;@ROUTINE: ANIM_GWIAZDEK
;@PURPOSE: Animate star effect when tree is hit
;@INPUT:   ZERO1 = screen address for animation
;@OUTPUT:  None
;@MODIFIES: A, Y, PAMY
;@CALLS:   PAUSE5
;@EXAMPLE: jsr ADR_LINII / jsr ANIM_GWIAZDEK  ; Animate stars at current line
ANIM_GWIAZDEK:
                lda #3
                sta PAMY
                ; ... animation code ...

Quality Assurance

Code Review Checklist

; ================================================================================================
; CODE REVIEW CHECKLIST
; ================================================================================================
; □ All subroutines have clear documentation
; □ Memory usage is within bounds
; □ Zero-page usage is optimized
; □ Interrupt routines are timing-safe
; □ All branches have been tested
; □ Error conditions are handled
; □ Code follows naming conventions
; □ No magic numbers (use constants)
; □ Performance-critical sections are optimized
; □ Build works on both debug and release
; ================================================================================================

; Example of good constant usage
SCREEN_WIDTH    equ 40                     ; Characters per line
SCREEN_HEIGHT   equ 24                     ; Lines per screen
MAX_POWER       equ 80                     ; Maximum player energy
TREE_LEVELS     equ 20                     ; Number of tree segments

; GOOD: Using named constants
CLEAR_SCREEN:
                ldx #SCREEN_WIDTH * SCREEN_HEIGHT
@               lda #0
                sta EKRAN-1,x
                dex
                bne @-
                rts

; BAD: Magic numbers
CLEAR_SCREEN_BAD:
                ldx #960                   ; What does 960 mean?
@               lda #0
                sta EKRAN-1,x
                dex
                bne @-
                rts

Project Structure Best Practices

  • Separate assets by type - graphics, audio, code
  • Use consistent naming conventions across all files
  • Document memory layout clearly
  • Include version information in builds
  • Automate builds with makefiles or scripts
  • Use conditional assembly for different configurations
  • Keep documentation close to code
  • Plan for expansion in memory layout

Mad Assembler (MADS) Commands Reference - From Drwal Game

Mad Assembler Documentation - Based on Drwal Game Analysis

This comprehensive reference lists all Mad Assembler commands and directives used in the Drwal game source code, with real examples and practical descriptions.

Assembler Directives

Directives are commands for the assembler, not the CPU. They define data and control the assembly process.

equ (Equate)

Purpose: Assigns a constant value to a label, improving code readability.

Example from Drwal:

ZERO1 equ $0080   ; Now we can use 'ZERO1' instead of address $80
ZERO2 equ $0082   ; Zero page pointer (2 bytes)
PAMY  equ $0088   ; Memory flag (1 byte)

org (Origin)

Purpose: Sets the memory address where the following code or data will be located.

Examples from Drwal:

org $2000         ; Program starts at address $2000
org pmg+$300      ; PMG data starts at PMG base + $300

dta (Define Data)

Purpose: Stores one or more 8-bit values. Can define numbers, text strings, or a mix.

Examples from Drwal:

; String with hex value in the middle
tekst0 dta d'ETAP',$61,d'ZALICZONY'

; Tree data array (20 bytes)
drzewo dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

; Game state variables
power           dta 40        ; Player power level
smierc          dta 0         ; Death flag
zmija_idzie     dta 0         ; Snake movement counter

; Display list data
DISPLAY_LIST:
                dta $70
                dta %11110000  ; DLI trigger
                dta %01000100,a(EKRAN)

.HE (Store Hex Bytes)

Purpose: Stores a sequence of hex values without requiring the $ prefix, cleaner for graphics data.

Examples from Drwal:

; PMG colors for different axe positions
pmg_kol_pg .HE 12 36 10 0a    ; Stores $12, $36, $10, $0A
pmg_kol_lg .HE 0a 10 36 12    ; Different color set

; Sprite graphics data (30 bytes each)
plr0_lg    .HE 80 c0 e4 fc fc e4 c0 c0 80 00 00 00 00 00 00 00...
plr1_lg    .HE 20 60 60 60 00 60 60 60 60 60 60 60 60 60 60 60...

; Snake animation frames
pzmija0_0  .HE 00 00 00 00 00 00 00 00 00 00 80 40 23 33 1c 04 00
lzmija0_0  .HE 00 00 00 00 00 00 00 00 00 00 01 02 c4 cc 38 20 00

; Screen line data
EKR_WSKAZNIK .HE 00 00 00 00 00 00 00 00 00 00 5b 5b 5b 5b...

.align

Purpose: Aligns the next line of code or data to a specified memory boundary.

Example from Drwal:

.align $1000      ; Ensure EKRAN data starts on 4KB boundary
EKRAN:
    ins 'grafika/drwal.sved2'

icl (Include)

Purpose: Inserts another source code file at the current location.

Example from Drwal:

icl 'etykiety.hea'    ; Include label definitions file

ins (Insert)

Purpose: Inserts a raw binary file directly into the compiled output.

Examples from Drwal:

; Font data insertion
FNTA:
    ins 'grafika/fntA.fnt'
FNTB_0:
    ins 'grafika/fntB_0.fnt'
FNTB_1:
    ins 'grafika/fntB_1.fnt'

; Screen graphics data
EKRAN:
    ins 'grafika/drwal.sved2'

run

Purpose: Sets the program's starting execution address in the file header.

Example from Drwal:

run START         ; Program execution begins at START label

Address Manipulation Operators

MADS uses the < and > operators to get the low and high bytes of a 16-bit address.

< (Low Byte) and > (High Byte)

Purpose: Extract low and high bytes from 16-bit addresses.

Examples from Drwal:

; Set Display List pointer (16-bit address)
ldx DISPLAY_LIST     ; Load Y with high byte of address
stx DLPTRS           ; Store low byte
sty DLPTRS+1         ; Store high byte

; Set interrupt vectors
ldy dvbi            ; VBI routine high byte
lda #$07
jsr SETVBV

; Address table generation
lo_adr_pnia dta <(EKRAN+(19*40)+18),<(EKRAN+(18*40)+18)...
hi_adr_pnia dta >(EKRAN+(19*40)+18),>(EKRAN+(18*40)+18)...

Special MADS Features Used in Drwal

Anonymous Labels

Purpose: Local labels for short jumps without cluttering namespace.

Examples from Drwal:

; Countdown loop pattern
                ldy #3
                lda #0
@               sta EKRAN+(17*40)+14-1,y
                sta EKRAN+(18*40)+14-1,y
                dey
                bne @-          ; Jump back to previous @
                rts

; Forward jump pattern
                cpx #0
                beq @+          ; Jump forward to next @
                lda #$FF
                sta SOMEWHERE
@               ; Execution continues here

Immediate Addressing with #

Purpose: Load immediate values into registers.

Examples from Drwal:

lda #$88             ; Load immediate hex value
ldx #10              ; Load immediate decimal value
ldy #%11110000       ; Load immediate binary value
cmp #80              ; Compare with immediate value

Address Expressions

Purpose: Calculate addresses using arithmetic expressions.

Examples from Drwal:

; Screen address calculations
sta EKRAN+(17*40)+14-1,y     ; Line 17, column 14-1+Y
sta EKRAN+(18*40)+14-1,y     ; Line 18, column 14-1+Y

; PMG memory layout
org pmg+$300                 ; PMG base + offset
player0+200,y                ; Player 0 sprite + line offset

MADS-Specific Features

These features are specific to Mad Assembler and may not be available in other 6502 assemblers:

  • .HE directive - Hex data without $ prefix
  • Anonymous labels (@) - Local scope labels
  • Complex expressions - Arithmetic in address calculations
  • String literals - d'text' format in dta directive

Drwal Game Code Analysis - Complete Mad Assembler (MADS) Project

Mad Assembler Documentation - Based on Drwal Game Analysis

Complete structural analysis of a real-world Mad Assembler project, showing how all MADS features work together in a complete Atari 8-bit game.

Let's examine the structure and key components of the Drwal game to understand how a complete 6502 assembly program is organized.

Memory Layout

; Zero page variables ($80-$D3)
ZERO1           equ $0080   ; (2) General purpose pointer
ZERO2           equ $0082   ; (2) General purpose pointer
ZERO3           equ $0084   ; (2) General purpose pointer
ZERO4           equ $0086   ; (2) General purpose pointer
PAMY            equ $0088   ; (1) Memory flag

; Program starts at $2000
                org $2000

; PMG (Player/Missile Graphics) area
pmg:
; Graphics data, fonts, and game data here...

; Screen memory aligned to 4KB boundary
                .align $1000
EKRAN:
                ins 'grafika/drwal.sved2'

Game Data Structures

Tree Representation

; Tree structure: 20 levels, each byte encodes:
; ?xxxx???
;        \\- bark type: 0=type1, 1=type2
;       \\-- 1=branch on right
;      \\--- 1=branch on left
; \\-------- 1=end of tree
drzewo          dta 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
pam_drzewa      dta 0,1,1,0,0,0,0,1,0,3,0,1,0,1,1,5,1,0,0,1

Player State

pam_joya        dta %00001100   ; Joystick state
pam_pozdrwala   dta 0,0         ; Player position
pozdrwala_atak  dta 0           ; Attack position
power           dta 40          ; Player power (0-80)
smierc          dta 0           ; Death flag
jad             dta 0           ; Poison flag

Snake AI

zmija_idzie     dta 0           ; Snake movement counter
kierunek_zmii   dta 1           ; Snake direction (0=left, 1=right)
klatka_zmii     dta 0           ; Animation frame
pzmii_0         dta 0           ; Snake X positions
pzmii_1         dta 4
pzmii_2         dta 8
pzmii_3         dta 8

Graphics System

Character Set Switching

; Two font sets for different screen areas
FNTA:           ins 'grafika/fntA.fnt'    ; UI font
FNTB_0:         ins 'grafika/fntB_0.fnt'  ; Game graphics (left)
FNTB_1:         ins 'grafika/fntB_1.fnt'  ; Game graphics (right)

; DLI switches fonts mid-screen
dli3:
                lda fontB:>FNTB_0
                sta CHBASE       ; Switch to game font

Sprite Data Organization

; PMG data for axe in different positions
plr0_lg         .HE 80 c0 e4 fc fc e4 c0 c0 80 00...  ; Left-top
plr0_ld         .HE 00 00 00 00 00 00 00 00 00 00...  ; Left-bottom
plr0_pg         .HE 00 00 00 00 00 00 00 00 00 00...  ; Right-top
plr0_pd         .HE 00 00 00 00 00 00 00 00 00 00...  ; Right-bottom

; Color and position data for each axe position
pmg_kol_pg      .HE 12 36 10 0a    ; Colors for right-top
pmg_poz_pg      .HE 8e 94 94 96    ; Positions for right-top

Game Logic Flow

START:
                jsr OBRAZ_OFF      ; Turn off display
                jsr EKRAN_TYTULOWY ; Show title screen
                jsr INICJOWANIE    ; Initialize game
                jmp EKRAN_GRY      ; Start main game

EKRAN_GRY:
                ; Setup graphics, interrupts, music
                ; Fall into main game loop

GRAMY:          ; Main game loop
spr_joy:        jsr JOYFIRKEY      ; Read input
                cmp pam_joya       ; Check for changes
                beq spr_joy        ; Loop if no change

                ; Process input and update game state
                ; Loop back to spr_joy

Advanced Techniques Used

1. Sprite Multiplexing

The game uses only 4 hardware sprites but displays clouds, axe, and snake by reprogramming the same sprites at different screen positions using DLIs.

2. Smooth Scrolling Clouds

Multiple cloud layers move at different speeds, created by updating sprite positions in the VBI with different timing counters.

3. Dynamic Tree Generation

The tree is procedurally drawn using lookup tables for screen addresses and bit-encoded data for branch positions.

4. Collision Detection

Uses hardware collision registers (P3PF) to detect when the snake touches the player, with additional logic to prevent multiple hits.

Performance Optimizations

  • Zero-page usage: All frequently accessed pointers in zero-page for faster access
  • Lookup tables: Pre-calculated screen addresses instead of runtime multiplication
  • Interrupt-driven timing: VBI ensures consistent game speed regardless of code complexity
  • Efficient loops: Countdown loops using DEX/DEY and BNE for minimal overhead
  • Hardware collision: Uses Atari's built-in collision detection instead of software checks

Mad Assembler (MADS) Code Examples - From Drwal Game

Mad Assembler Documentation - Based on Drwal Game Analysis

Practical Mad Assembler programming examples extracted and explained from the complete Drwal game source code.

Here are practical examples demonstrating key concepts from the Drwal game and general 6502 programming.

Complete Subroutine Example

; Draw lumberjack at specified position
; Input: X = position (0-3)
; 0=left-top, 1=right-bottom, 2=right-top, 3=left-bottom
RYS_DRWALA:
poz_3:          cpx #3
                bne poz_2
                ; Position 3: left-bottom
                jsr KAS_DRWALA_P    ; Clear right side
                jsr RYS_TOPOR_LD    ; Draw left-bottom axe
                jsr RYS_DRWALA_L    ; Draw left lumberjack
                jsr KAS_GALAZ_P     ; Clear right branch
                rts

poz_2:          cpx #2
                bne poz_1
                ; Position 2: right-top
                jsr KAS_DRWALA_L    ; Clear left side
                jsr RYS_TOPOR_PG    ; Draw right-top axe
                jsr RYS_DRWALA_P    ; Draw right lumberjack
                rts

poz_1:          cpx #1
                bne poz_0
                ; Position 1: right-bottom
                jsr KAS_DRWALA_L    ; Clear left side
                jsr RYS_TOPOR_PD    ; Draw right-bottom axe
                jsr RYS_DRWALA_P    ; Draw right lumberjack
                jsr KAS_GALAZ_L     ; Clear left branch
                rts

poz_0:          ; Position 0: left-top (default)
                jsr KAS_DRWALA_P    ; Clear right side
                jsr RYS_TOPOR_LG    ; Draw left-top axe
                jsr RYS_DRWALA_L    ; Draw left lumberjack
                rts

Memory Copy with Indirect Addressing

; Copy sprite data to PMG memory
; Input: X = sprite type (0-4 for different animation frames)
COPY_SNAKE_SPRITE:
                ; Calculate source address
                lda snake_lo_table,x
                sta ZERO1
                lda snake_hi_table,x
                sta ZERO1+1

                ; Copy 17 bytes of sprite data
                ldy #16
@:              lda (ZERO1),y
                sta player0+200,y    ; Snake appears at line 200
                dey
                bpl @-
                rts

; Lookup tables for sprite addresses
snake_lo_table: dta pzmija0_0,>pzmija0_1,>pzmija0_2,>pzmija0_3,>pzmija0_4

Timing and Animation

; Animate clouds with different speeds
; Called from VBI (60 times per second)
ANIMATE_CLOUDS:
                ; Cloud layer 0 (fastest)
                lda cloud_timer0
                bne @+
                lda #2              ; Reset timer (move every 2 frames)
                sta cloud_timer0
                dec cloud_pos0      ; Move cloud left
                dec cloud_pos1
                dec cloud_pos2
                dec cloud_pos3
@:              dec cloud_timer0

                ; Cloud layer 1 (medium speed)
                lda cloud_timer1
                bne @+
                lda #3              ; Reset timer (move every 3 frames)
                sta cloud_timer1
                dec cloud_pos4      ; Move cloud left
                dec cloud_pos5
@:              dec cloud_timer1

                ; Cloud layer 2 (slowest)
                lda cloud_timer2
                bne @+
                lda #5              ; Reset timer (move every 5 frames)
                sta cloud_timer2
                dec cloud_pos6      ; Move cloud left
                dec cloud_pos7
@:              dec cloud_timer2
                rts

Input Handling with Debouncing

; Read joystick with change detection
; Returns: Y = joystick state, Z flag set if no change
JOYFIRKEY:
                ; Read hardware joystick
                lda STICK0
                and #%00001111      ; Mask direction bits
                tay                 ; Save in Y

                ; Check for fire button
                lda STRIG0
                beq fire_pressed
                ; No fire, just return direction
                tya
                cmp pam_joya        ; Compare with previous state
                rts

fire_pressed:   ; Fire button adds bit 4
                tya
                ora #%00010000      ; Set fire bit
                tay
                cmp pam_joya        ; Compare with previous state
                rts

Screen Address Calculation

; Calculate screen address for character position
; Input: A = Y coordinate (0-23), X = X coordinate (0-39)
; Output: ZERO1 = screen address
CALC_SCREEN_ADDR:
                ; Multiply Y by 40 (screen width)
                sta temp_y
                lda #0
                sta ZERO1+1         ; Clear high byte

                lda temp_y
                asl                 ; Y * 2
                rol ZERO1+1
                asl                 ; Y * 4
                rol ZERO1+1
                asl                 ; Y * 8
                rol ZERO1+1
                sta temp_mult8
                lda ZERO1+1
                sta temp_mult8_hi

                lda temp_y
                asl                 ; Y * 2
                asl                 ; Y * 4
                asl                 ; Y * 8
                asl                 ; Y * 16
                asl                 ; Y * 32
                clc
                adc temp_mult8      ; Y * 32 + Y * 8 = Y * 40
                sta ZERO1
                lda ZERO1+1
                adc temp_mult8_hi
                sta ZERO1+1

                ; Add X coordinate
                txa
                clc
                adc ZERO1
                sta ZERO1
                lda ZERO1+1
                adc #0
                sta ZERO1+1

                ; Add screen base address
                lda ZERO1
                clc
                adc EKRAN
                sta ZERO1+1
                rts

temp_y:         dta 0
temp_mult8:     dta 0
temp_mult8_hi:  dta 0

Simple Sound Effect

; Play a simple beep sound
; Input: A = pitch (0-255), X = duration
PLAY_BEEP:
                sta AUDF1           ; Set frequency
                lda #%10100000      ; Pure tone, medium volume
                sta AUDC1           ; Set distortion and volume

beep_loop:      lda RTCLOK+2        ; Wait for frame
@:              cmp RTCLOK+2
                beq @-
                dex
                bne beep_loop

                lda #0              ; Turn off sound
                sta AUDC1
                rts

Study Tips

  • Start with simple examples and gradually add complexity
  • Use the Atari emulator to test code changes
  • Study the Drwal source code section by section
  • Practice with small programs before attempting full games
  • Use comments extensively to document your code