Thursday, September 5, 2013

Getting the most out of CA65 - Part 1

I aim to complete a quality game for the NES using the CA65 macro assembler as one of my primary tools to do so. I seem to enjoy much of my time playing with ideas to extend the assembler itself via macro code. I find its macro language enjoyable to work with and have come up with a few things that may be worth sharing, even if just to show how to utilize the macro language for your own goals.

 Some of these ideas I don't really need at this time, but looking at what I may need from the perspective of a very large NES project, I have tried to implement things I may need in the future.

High Level code constructs

This is slowly evolving as I find things that don't quite work perfectly or something small gets added. See the documentation here: https://www.assembla.com/spaces/ca65hl/wiki
There is no code created by these macros. They work by creating labels, which CA65 can do utilizing the .sprint() and .ident() built-in functions. I may explain how they work at the macro level in more detail later, but as far as the IF-ENDIF structure, once a stack macro is implemented, tracking logical blocks and spitting out labels is not too difficult. The more difficult part was implementing the flexibility of what can be included as a 'boolean expression'.

Organizing code with libraries and header files

This is a simple but very nice way to organize chunks of code that can be reused later. This simple example is NES controller reading. You could code the routine once, and never have to worry about it again. (This example ignores the DPMC conflict.)

First, write the 'library' code. I've standardized on prefixing all filenames with 'lib_', so this is: lib_controller.inc
The .inc indicates that this is to be included somewhere in the project, it could be in a separate module. (Note, in this code func is a custom macro.)

Code:

.ifndef   _LIB_CONTROLLER_
_LIB_CONTROLLER_ = 1

.scope _LIB_CONTROLLER_ ; keep everything in its own scope

.pushseg
.segment "ZEROPAGE"

    pressed:          .res    2    ; new this frame - held down
    pressedLastFrame: .res    2    ; last frame / call
    pressedNew:       .res    2    ; newly pressed since last frame
    releasedNew:      .res    2    ; newly released since last frame

.popseg

exportfunc         readPort
exportfunc         readPort0
exportfunc         readPort1

.export pressed
.export pressedLastFrame
.export pressedNew
.export releasedNew


func readPort0
    ldx #0
    .byte $CD           ; compare absolute, skip ldx #1
endfunc

func readPort1
    ldx #1
endfunc

func readPort
; IN : player number in reg X = 0, or 1 => 1 or 2
; OUT: Y: undefined, A: PadsNewReleased, X: unchanged


    lda pressed,x         ; save last frame's joystick
    sta pressedLastFrame,x

    lda #1
    sta pressed,x         ; set bit0 to activate carry in do-while loop
    sta $4016
    lsr a                    ; a has 0
    sta $4016

    :
        lda $4016,x
        and #3               ; famicom
        cmp #1              ; friendly reads
        rol pressed,x
    bcc :-

    lda pressed,x
    eor pressedLastFrame,x
    tay
    and pressed,x
    sta pressedNew,x
    tya
    eor pressedNew,x
    sta releasedNew,x
    
    rts
    
endfunc

.endscope

.endif

If this has been included somewhere, now you can access this functionality from any module by including the header file: int_iController.h
; Interface for lib_controller.inc

.ifndef _INT_CONTROLLER_H_
_INT_CONTROLLER_H_ = 1

.scope iController
    
    importfunc readPort0
    importfunc readPort1
    importfunc readPort
    
    
    .importZP pressed
    .importZP pressedLastFrame  
    .importZP pressedNew 
    .importZP releasedNew
    
.endscope

.endif

Any module that includes the header file can access the controller reading functionality and the variables via the iController scope:

Somewhere in code:

; include the header:
.include "int_iController.h"
; elsewhere:
lda iController::pressednew
and #BUTTON_A
bne buttonApressed

This has the advantage of quickly reusing code and speeding up assembly of large projects, since the library code will never have to be assembled again if it is not changed.

Some minor improvements

Pass "-I headerFilefolder -I libraryFileFolder" to the command line of CA65 and keep all headers and libraries in a standard location.

I have standardized on a header file format of prefixing with int_ and created a macro to help make things a bit nicer.

Somewhere in code:

uses iController

This will attempt to include "int_iController.h" More to come next time.

Wednesday, March 27, 2013

Updated MetaSprite Code (again)

001 002 .proc evalMetaSprites 003 004 ; metasprite Object in RAM format, one record: 005 ; should be organized as one array of each element 006 ; (example, if there are $10 records, the first $10 bytes are all Ypos 007 008 ; Ypos : byte 009 ; Attribute modifier : byte 010 011 ; vhbcxypp --- Attribute modifier byte 012 ; |||||||| 013 ; ||||||++---- Sprite Colour toggle: EOR with metatile attribs 014 ; |||||+------ Y clipping bit 015 ; ||||+------- X clipping bit 016 ; |||+-------- Clipping or wrap : 0: clip sprites at screen edge, 1: allow wrap 017 ; ||+--------- Background priority bit 018 ; |+---------- Horizontal flipping toggle: EOR metatile attribs 019 ; +----------- Vertical flipping toggle: EOR metatile attribs 020 021 ; Metatile number : byte 022 ; Xpos : byte 023 024 ; -------------------------------------------------------------------------------------------- 025 026 ; metasprite format to define metasprite in ROM data: 027 028 ; xxxxxxxx --- Sprite X coordinate (if required) 029 ; yyyyyyyy --- Sprite Y coordinate (if required) 030 031 ; tttttttt --- Tile number (if required) 032 033 ; vhytxxpp --- Attribute byte (always) 034 ; |||||||| 035 ; ||||||++---- Sprite Colour 036 ; |||||+------ Tile number of next sprite (0: required, 1 : Tile number of this sprite + 1, skip if X control was LF) 037 ; ||||+------- Y position of next sprite (0 : required, 0 : unchanged) 038 ; ||++-------- X position of next sprite (01 : required, 00 : X pos of this sprite + 8, 039 ; || 10 : X pos of the first sprite, Y + 8 (CR/LF), 11 : Escape code for last sprite) 040 ; |+---------- Horizontal flipping 041 ; +----------- Vertical flipping 042 043 ; static 044 declareBSS 045 OAMoffset .byte 046 endBSS 047 048 ; notallOnScreen use zeropage: 049 050 locals 051 sprModifier .byte 052 sprAttrib .byte 053 sprTile .byte 054 sprXpos .byte 055 sprYpos .byte 056 057 sprXbit8 .byte 058 sprYbit8 .byte 059 060 metaspritePointerLo .byte 061 metaspritePointerHi .byte 062 063 sprAdder .byte 064 sprXposStart .byte 065 mSpriteOffset .byte 066 carryYplusHeight .byte 067 carryXplusWidth .byte 068 endlocals 069 070 notallOnScreen = local::sprAdder 071 saveOAMOffset = local::sprTile 072 metaspritePointer = local::metaspritePointerLo 073 074 ; init: 075 ldy #::METASPRITE_COUNT 076 sty local::mSpriteOffset 077 078 079 ; skip reserved sprites 080 if lda M::OAMoffset < #::OAM_ROTATION_START 081 lda #::OAM_ROTATION_START 082 endif 083 084 sta M::OAMoffset 085 sta saveOAMOffset ; use as notallOnScreen: save OAMoffset 086 087 DoNextmetasprite: 088 089 090 ; loop through all entries and skip those with att = $FF 091 ; jump to OAM rotation and leave if no more entries 092 ldx local::mSpriteOffset 093 094 repeat 095 096 if dex == negative 097 jmp clearRemainingOAM 098 endif 099 100 mb a, local::sprModifier := mSpriteMemory[ x ]::att ; load Attrib/meta data which is modifier for all 101 102 until tay : iny != zero ; same as: until mSpriteMemory[ x ]::att <> #$FF 103 104 mb local::sprYbit8 := a >> 2 ; put Y 'bit8' into bit 0 local var (only bit0 matters) 105 mb local::sprXbit8 := a >> 1 ; put X 'bit8' into bit 0 local var (only bit0 matters) 106 107 stx local::mSpriteOffset 108 109 mb local::sprYpos := mSpriteMemory[ x ]::yPos 110 mb local::sprXpos := mSpriteMemory[ x ]::xPos 111 sta local::sprXposStart 112 113 mb a, x := mSpriteMemory[ x ]::mTile ; load metatile number 114 115 ; get metatile pointer 116 mb local::metaspritePointerLo := metaspriteNumbersLo[ x ] 117 mb local::metaspritePointerHi := metaspriteNumbersHi[ x ] 118 119 ; first data from metatile string is Xwidth, Yheight, Tile, and Attribs/command byte 120 121 ldy #0 122 sty notallOnScreen 123 124 mb a := local::sprXpos + (metaspritePointer)[ y ] ; add width ( is stored 8 less than actual ) 125 tax ; x has right side of object X coord 126 rol local::carryXplusWidth 127 128 iny 129 mb a := local::sprYpos + (metaspritePointer)[ y ] ; add height ( is stored 8 less than actual ) 130 tay ; y has bottom of object Y coord 131 rol local::carryYplusHeight 132 133 ; ----------- check if all onscreen 134 135 if local::sprModifier & #010000 goto AllOnScreen 136 137 tya ; y has bottom of object Y coord 138 cmp #238 139 rol 140 ora local::sprXbit8 141 ora local::sprYbit8 142 ora local::carryXplusWidth 143 ora local::carryYplusHeight 144 and #1 145 146 if sta notallOnScreen == zero goto AllOnscreen 147 148 ; ------ end check all onscreen 149 150 ; ----------- check if all offscreen 151 152 ; check if all offscreen for X 153 lda local::carryXplusWidth 154 eor #1 155 and local::sprXbit8 156 and #1 157 bne DoNextmetasprite 158 159 ; check all offscreen for Y 160 lda local::carryYplusHeight 161 eor #1 162 and local::sprYbit8 163 and #1 164 bne DoNextmetasprite 165 166 ; check offscreen special case, bottom of screen area: 167 lda local::sprYpos 168 cmp #239 169 rol 170 eor #1 ; b0 = 0 if y > 238 171 ora local::sprYbit8 ; b0 still = 0 if sprYbit8 = 0 172 and #1 173 beq DoNextmetasprite 174 175 ; ------------ end check offscreen 176 177 AllOnscreen: 178 179 bit local::sprModifier ; copy b7 and b6 into N, V 180 181 if V set ; H flipping: 182 mb local::sprXpos := x ; x has right side of object X coord 183 mb local::sprXposStart := x 184 185 if ror local::carryXplusWidth == carry set 186 mb local::sprModifier := local::sprModifier ^ # ( 1 << OFFSCREEN_XBIT ) 187 endif 188 endif 189 190 iny 191 192 ; --------- end width 193 ; --------- process height: 194 195 ror local::carryYplusHeight 196 if lda local::sprModifier == N set ; V flipping: 197 mb local::sprYpos := y ; ; y holds Ypos + height..add height ( is stored 8 less than actual ) 198 if carry set 199 mb a := a ^ #( 1 << OFFSCREEN_YBIT ) 200 endif 201 endif 202 203 sta local::sprModifier 204 205 mb local::sprYbit8 := a >> 2 ; put Y 'bit8' into bit 0 local var (only bit0 matters) 206 mb local::sprXbit8 := a >> 1 ; put X 'bit8' into bit 0 local var (only bit0 matters) 207 208 ; --------------------------- 209 210 ldx saveOAMOffset ; X now has OAMoffset 211 212 ldy #2 ; start at tile entry 213 214 mb local::sprTile := (metaspritePointer)[ y ] 215 iny 216 217 ; if all sprites are onscreen, jump to sped up routine: 218 219 if notallOnScreen == zero 220 jmp DrawFast 221 endif 222 223 DoNextTile: 224 225 mb local::sprAttrib := (metaspritePointer)[ y ] ; read attrib and flags 226 iny 227 228 ; if both offscreen bits are clear OR No clipping mode is on 229 230 if ( local::sprYpos < #239 && ( local::sprXbit8 | local::sprYbit8 >> 1 == carry clear )) 231 232 mb OAM_SHADOW[ x + 2] := local::sprAttrib & #%11000011 ^ local::sprModifier 233 mb OAM_SHADOW[ x + 3] := local::sprXpos 234 mb OAM_SHADOW[ x + 0] := local::sprYpos 235 mb OAM_SHADOW[ x + 1] := local::sprTile 236 237 if x := x + 4 == zero 238 ldx #::OAM_ROTATION_START 239 endif 240 241 endif 242 243 sec ; sec carry to end making reg A hold #8 244 mb a := local::sprAttrib & #$F0 245 rol 246 asl 247 asl ; test bit 3, 2 as C, N 248 249 ; if X MODIFIER value is 00 or 01: 250 if C clear 251 asl ; A will have #8 252 if C set ; X MODIFIER IS 01 253 mb a := (metaspritePointer)[ y ] 254 iny 255 clc 256 endif 257 258 ; HERE. either way, carry clear 259 260 if { flagSet local::sprModifier, 6 } ; if bit6 (h mirror) 261 ; negate adder 262 mb a := a ^ #$FF 263 sec 264 endif 265 266 sta local::sprAdder 267 268 mb local::sprXpos := a +c local::sprXpos 269 270 ror 271 if a ^ local::sprAdder == N set ; if carry and adder sign are different: 272 inc local::sprXbit8 ; toggle offscreen bit 273 endif 274 275 else 276 if N clear ; X MODIFIER IS 10 277 278 ; this idea takes the same # of cycles as the clearer code: 279 ; ------------------------------------------------------- 280 ; asl ; reg A holds #8 281 ; and local::sprModifier 282 ; cmp #1 ; only bit8 could be set, copy it into C 283 ; rol local::sprXbit8 284 285 mb local::sprXbit8 := local::sprModifier >> 3 286 mb local::sprXpos := local::sprXposStart 287 288 lda #8 289 jmp Add8toYPos 290 endif 291 ; else: X MODIFIER IS 11: 292 stx saveOAMOffset ; save OAM offset here 293 ;--------------- 294 jmp DoNextmetasprite ; if = 0 then bail and do next RAm based metasprite 295 296 endif 297 298 ; -----------END X pos 299 300 ; ----------- Y pos 301 302 ; load Ypos? Skip all this if not 0 303 if ! local::sprAttrib & #001000 304 305 mb a := (metaspritePointer)[ y ] 306 iny 307 308 Add8toYPos: ; jump here if CR/LF code 309 310 clc 311 if flagSet local::sprModifier ; if bit7 (v mirror) 312 mb a := a ^ #$FF 313 sec 314 endif 315 316 sta local::sprAdder 317 mb local::sprYpos := a +c local::sprYpos 318 319 ror 320 if a ^ local::sprAdder == N set 321 inc local::sprYbit8 322 endif 323 324 endif 325 ; -----------END Y pos 326 327 ; -----------check tile: 328 329 ; BIT4: check if 0: load tile, else use next tile 330 331 inc local::sprTile ; inc by default 332 333 if ! local::sprAttrib & #000100 334 mb local::sprTile := (metaspritePointer)[ y ] 335 iny 336 endif 337 338 ; -------end tile processing 339 340 jmp DoNextTile 341 342 ; super fast, no clipping version: 343 344 DrawFast: 345 346 mb local::sprAttrib := (metaspritePointer)[ y ] ; read attrib and flags 347 iny 348 349 mb OAM_SHADOW[ x + 2] := local::sprAttrib & #%11000011 ^ local::sprModifier 350 mb OAM_SHADOW[ x + 3] := local::sprXpos 351 mb OAM_SHADOW[ x + 0] := local::sprYpos 352 mb OAM_SHADOW[ x + 1] := local::sprTile 353 354 if x := x + 4 == zero 355 ldx #::OAM_ROTATION_START 356 endif 357 358 sec ; sec carry to end making reg A hold #8 359 mb a := local::sprAttrib & #$F0 360 rol 361 asl 362 asl ; test bit 3, 2 as C, N 363 364 ; if X MODIFIER value is 00 or 01: 365 if C clear 366 asl ; A will have #8 367 if C set ; X MODIFIER IS 01 368 mb a := (metaspritePointer)[ y ] 369 iny 370 clc 371 endif 372 373 ; HERE. either way, carry clear 374 375 if { flagSet local::sprModifier, 6 } ; if bit6 (h mirror) 376 ; negate adder 377 mb a := a ^ #$FF 378 sec 379 endif 380 381 mb local::sprXpos := a +c local::sprXpos 382 383 else 384 if N clear ; X MODIFIER IS 10 385 mb local::sprXpos := local::sprXposStart 386 387 lda #8 388 jmp Add8toYPosFast 389 endif 390 ; else: X MODIFIER IS 11: 391 stx saveOAMOffset ; save OAM offset here 392 ;--------------- 393 jmp DoNextmetasprite ; if = 0 then bail and do next RAm based metasprite 394 395 endif 396 397 ; -----------END X pos 398 399 ; ----------- Y pos 400 401 ; load Ypos? Skip all this if not 0 402 if ! local::sprAttrib & #001000 403 404 mb a := (metaspritePointer)[ y ] 405 iny 406 407 Add8toYPosFast: ; jump here if CR/LF code 408 409 clc 410 if flagSet local::sprModifier ; if bit7 (v mirror) 411 mb a := a ^ #$FF 412 sec 413 endif 414 415 mb local::sprYpos := a +c local::sprYpos 416 endif 417 ; -----------END Y pos 418 419 ; -----------check tile: 420 421 ; BIT4: check if 0: load tile, else use next tile 422 423 inc local::sprTile ; inc by default 424 425 if ! local::sprAttrib & #000100 426 mb local::sprTile := (metaspritePointer)[ y ] 427 iny 428 endif 429 430 ; -------end tile processing 431 432 jmp DrawFast 433 434 ; -------------------------------------------------------------------------------------------- 435 436 clearRemainingOAM: 437 OAMnext = saveOAMOffset ; alias 438 439 ; -------------------------------------------------------------------------------------------- 440 441 ldx OAMnext ; x holds start of next blank spot 442 443 ; take away blank spaces from total slots if we wrapped 444 if OAMnext <= M::OAMoffset 445 mb a := OAMnext - #::OAM_ROTATION_START 446 endif 447 448 mb a := a - M::OAMoffset ; bytes used 449 mb a := a >> 2 ; slots used 450 sta OAMnext ; use as notallOnScreen, since this value is in reg X 451 452 mb y := #(64 - ( ::OAM_ROTATION_START / 4 ) ) - OAMnext ; total avaliable slots MINUS slots used 453 lda #$FF 454 455 cpy #00 456 while not zero do 457 458 sta OAM_SHADOW, x 459 mb x := x + 4 460 if zero 461 ldx #::OAM_ROTATION_START 462 endif 463 dey 464 endwhile 465 466 mb M::OAMoffset := M::OAMoffset + #::OAM_ROTATION_AMOUNT 467 468 rts 469 470 .endproc

Monday, March 25, 2013

MetaSprite Routine Updated

001 .proc evalMetaSprites 002 003 ; metasprite Object in RAM format, one record: 004 ; should be organized as one array of each element 005 ; (example, if there are $10 records, the first $10 bytes are all Ypos 006 007 ; Ypos : byte 008 ; Attribute modifier : byte 009 010 ; vhbcxypp --- Attribute modifier byte (always) 011 ; |||||||| 012 ; ||||||++---- Sprite Colour toggle: EOR with metatile attribs 013 ; |||||+------ Y clipping bit 014 ; ||||+------- X clipping bit 015 ; |||+-------- Clipping or wrap : 0: clip sprites at screen edge, 1: allow wrap 016 ; ||+--------- Background priority bit 017 ; |+---------- Horizontal flipping toggle: EOR metatile attribs 018 ; +----------- Vertical flipping toggle: EOR metatile attribs 019 020 ; Metatile number : byte 021 ; Xpos : byte 022 023 ; -------------------------------------------------------------------------------------------- 024 025 ; metasprite format to define metasprite in ROM data: 026 027 ; xxxxxxxx --- Sprite X coordinate (if required) 028 ; yyyyyyyy --- Sprite Y coordinate (if required) 029 030 ; tttttttt --- Tile number (if required) 031 032 ; vhytxxpp --- Attribute byte (always) 033 ; |||||||| 034 ; ||||||++---- Sprite Colour 035 ; |||||+------ Tile number of next sprite (0: required, 1 : Tile number of this sprite + 1, skip if X control was LF) 036 ; ||||+------- Y position of next sprite (0 : required, 0 : unchanged) 037 ; ||++-------- X position of next sprite (01 : required, 00 : X pos of this sprite + 8, 038 ; || 10 : X pos of the first sprite, Y + 8 (CR/LF), 11 : Escape code for last sprite) 039 ; |+---------- Horizontal flipping 040 ; +----------- Vertical flipping 041 042 ; static 043 declareBSS 044 OAMoffset .byte 045 endBSS 046 047 ; temp use zeropage: 048 049 locals 050 sprModifier .byte 051 sprAttrib .byte 052 sprTile .byte 053 sprXpos .byte 054 sprYpos .byte 055 056 sprXbit8 .byte 057 sprYbit8 .byte 058 059 metaspritePointerLo .byte 060 metaspritePointerHi .byte 061 062 sprAdder .byte 063 sprXposStart .byte 064 mSpriteOffset .byte 065 endlocals 066 067 metaspritePointer = local::metaspritePointerLo 068 069 ; init: 070 ldy #::METASPRITE_COUNT 071 sty local::mSpriteOffset 072 073 074 ; skip reserved sprites 075 if lda M::OAMoffset < #::OAM_ROTATION_START 076 lda #::OAM_ROTATION_START 077 endif 078 079 sta M::OAMoffset 080 sta local::sprAdder ; use as temp: save OAMoffset 081 082 DoNextmetasprite: 083 084 085 ; loop through all entries and skip those with att = $FF 086 ; jump to OAM rotation and leave if no more entries 087 ldx local::mSpriteOffset 088 089 repeat 090 091 if dex == negative 092 jmp clearRemainingOAM 093 endif 094 095 mb a, local::sprModifier := mSpriteMemory[ x ]::att ; load Attrib/meta data which is modifier for all 096 097 until tay : iny != zero ; same as: until mSpriteMemory[ x ]::att <> #$FF 098 099 stx local::mSpriteOffset 100 101 mb local::sprYpos := mSpriteMemory[ x ]::yPos 102 mb local::sprXpos := mSpriteMemory[ x ]::xPos 103 sta local::sprXposStart 104 105 mb a, x := mSpriteMemory[ x ]::mTile ; load metatile number 106 107 ; get metatile pointer 108 mb local::metaspritePointerLo := metaspriteNumbersLo[ x ] 109 mb local::metaspritePointerHi := metaspriteNumbersHi[ x ] 110 111 ; from here, Y indexes into ROM data 112 ; first data is Xwidth, Yheight, Tile, and Attribs/command byte 113 114 ldy #0 115 116 ; --------- process width: 117 118 bit local::sprModifier ; copy b7 and b6 into N, V 119 120 if V set ; H flipping: 121 mb local::sprXpos := local::sprXpos + (metaspritePointer)[ y ] ; add width ( is stored 8 less than actual ) 122 sta local::sprXposStart 123 if carry set 124 mb local::sprModifier := local::sprModifier ^ # ( 1 << OFFSCREEN_XBIT ) 125 endif 126 endif 127 128 iny 129 130 ; --------- end width 131 ; --------- process height: 132 133 if lda local::sprModifier == N set ; V flipping: 134 mb local::sprYpos := local::sprYpos + (metaspritePointer)[ y ] ; add height ( is stored 8 less than actual ) 135 lda local::sprModifier 136 if carry set 137 mb a := a ^ #( 1 << OFFSCREEN_YBIT ) 138 endif 139 endif 140 141 iny 142 143 ; --------- end height: 144 145 ; set offscreen bits: 146 sta local::sprModifier 147 148 mb local::sprYbit8 := a >> 2 ; put Y 'bit8' into bit 0 local var (only bit0 matters) 149 mb local::sprXbit8 := a >> 1 ; put X 'bit8' into bit 0 local var (only bit0 matters) 150 151 ; ---------------- 152 153 mb local::sprTile := (metaspritePointer)[ y ] 154 iny 155 156 ldx local::sprAdder ; X now has OAMoffset 157 158 DoNextTile: 159 160 mb local::sprAttrib := (metaspritePointer)[ y ] ; read attrib and flags 161 iny 162 163 ; if both offscreen bits are clear OR No clipping mode is on 164 if local::sprXbit8 | local::sprYbit8 >> 1 == carry clear || local::sprModifier & #%00010000 165 166 mb OAM_SHADOW[ x + 2] := local::sprAttrib & #%11000011 ^ local::sprModifier 167 mb OAM_SHADOW[ x + 3] := local::sprXpos 168 mb OAM_SHADOW[ x + 0] := local::sprYpos 169 mb OAM_SHADOW[ x + 1] := local::sprTile 170 171 if x := x + 4 == zero 172 ldx #::OAM_ROTATION_START 173 endif 174 175 endif 176 177 sec ; sec carry to end making reg A hold #8 178 mb a := local::sprAttrib & #$F0 179 rol 180 asl 181 asl ; test bit 3, 2 as C, N 182 183 ; if X MODIFIER value is 00 or 01: 184 if C clear 185 asl ; A will have #8 186 if C set ; X MODIFIER IS 01 187 mb a := (metaspritePointer)[ y ] 188 iny 189 clc 190 endif 191 192 ; HERE. either way, carry clear 193 194 if { flagSet local::sprModifier, 6 } ; if bit6 (h mirror) 195 ; negate adder 196 mb a := a ^ #$FF 197 sec 198 endif 199 200 sta local::sprAdder 201 202 mb local::sprXpos := a +c local::sprXpos 203 204 ror 205 if a ^ local::sprAdder == N set ; if carry and adder sign are different: 206 inc local::sprXbit8 ; toggle offscreen bit 207 endif 208 209 else 210 if N clear ; X MODIFIER IS 10 ; 9615 cycles 211 212 ; this idea takes the same # of cycles as the clearer code: 213 ; ------------------------------------------------------- 214 ; asl ; reg A holds #8 215 ; and local::sprModifier 216 ; cmp #1 ; only bit8 could be set, copy it into C 217 ; rol local::sprXbit8 218 219 mb local::sprXbit8 := local::sprModifier >> 3 220 mb local::sprXpos := local::sprXposStart 221 222 lda #8 223 jmp Add8toYPos 224 endif 225 ; else: X MODIFIER IS 11: 226 stx local::sprAdder ; save OAM offset here 227 ;--------------- 228 jmp DoNextmetasprite ; if = 0 then bail and do next RAm based metasprite 229 230 endif 231 232 ; -----------END X pos 233 234 ; ----------- Y pos 235 236 ; load Ypos? Skip all this if not 0 237 if ! local::sprAttrib & #%00001000 238 239 mb a := (metaspritePointer)[ y ] 240 iny 241 242 Add8toYPos: ; jump here if CR/LF code 243 244 clc 245 if flagSet local::sprModifier ; if bit7 (v mirror) 246 mb a := a ^ #$FF 247 sec 248 endif 249 250 sta local::sprAdder 251 252 mb local::sprYpos := a +c local::sprYpos 253 254 ror 255 if a ^ local::sprAdder == N set 256 inc local::sprYbit8 257 endif 258 259 endif 260 ; -----------END Y pos 261 262 ; -----------check tile: 263 264 ; BIT4: check if 0: load tile, else use next tile 265 266 inc local::sprTile ; inc by default 267 268 if ! local::sprAttrib & #%00000100 269 mb local::sprTile := (metaspritePointer)[ y ] 270 iny 271 endif 272 273 ; -------end tile processing 274 275 jmp DoNextTile 276 277 ; -------------------------------------------------------------------------------------------- 278 279 clearRemainingOAM: 280 281 OAMnext = local::sprAdder ; alias 282 283 ; -------------------------------------------------------------------------------------------- 284 285 ldx OAMnext ; x holds start of next blank spot 286 287 ; take away blank spaces from total slots if we wrapped 288 if OAMnext <= M::OAMoffset 289 mb a := OAMnext - #::OAM_ROTATION_START 290 endif 291 292 mb a := a - M::OAMoffset ; bytes used 293 mb a := a >> 2 ; slots used 294 sta OAMnext ; use as temp, since this value is in reg X 295 296 mb y := #(64 - ( ::OAM_ROTATION_START / 4 ) ) - OAMnext ; total avaliable slots MINUS slots used 297 lda #$FF 298 299 cpy #00 300 while not zero do 301 302 sta OAM_SHADOW, x 303 mb x := x + 4 304 if zero 305 ldx #::OAM_ROTATION_START 306 endif 307 dey 308 endwhile 309 310 mb M::OAMoffset := M::OAMoffset + #::OAM_ROTATION_AMOUNT 311 312 rts 313 314 .endproc