ApplyMovement Function
2025-03-29
CFF978 is the function that handles applying enemy movement to the enemies X/Y coordinates.
ApplyMovement:
; Initialize loop, we're looping through all 8 enemies (0-7)
CFF978 LDX #$0007
CFF97B STX $C6
; Start of loop - Checking movement flag and movement timer
; The movement flag gets set when an enemy needs to move
; The movement timer limits how often the enemy moves.
CFF97D LDX $C6 ; The currently active enemy
CFF97F LDA $98A7,X ; Load the Xth enemy's active movement flag
CFF982 BEQ $CFF9CE ; If the enemy isn't actively moving, skip
CFF984 DEC $9819,X ; Decrement the Xth enemy's movement timer
CFF987 BNE $CFF9CE ; If it's not 0 skip
; Reset the movement timer to the max value
CFF989 LDA $9742,X ; Load the max value of the movement timer
CFF98C STA $9819,X ; Store it to the current movement timer
; Movement between A and B is broken down into 8 smaller steps
CFF98F CLC
CFF990 LDA $9737,X ; This is a counter keeping track of how far into the movement we are
CFF993 ADC $98C2,X ; Add the movement speed to the counter
CFF996 STA $9737,X ; And store it back
CFF999 STA $00 ; Keep a temporary copy in $00, this is read by the trig function later
CFF99B CMP #$08 ; If it's not 8 then...
CFF99D BNE $CFF9A5 ; Skip the next two lines, otherwise
CFF99F STZ $98A7,X ; Zero out the movement flag
CFF9A2 STZ $9737,X ; Zero out the counter
; Calculate Y movement
CFF9A5 LDA $9786,X ; Load the enemies movement angle
CFF9A8 JSR $F9FB ; This uses a trig lookup table to calculate movement
CFF9AB STA $C8 ; The output, which is Y movement, is stored in $C8
;Calculate X Movement
CFF9AD LDX $C6 ; Reload current enemy
CFF9AF CLC
CFF9B0 LDA $9786,X ; Reload movement angle
CFF9B3 ADC #$40 ; Add 0x40 (64, or 90 degrees)
CFF9B5 JSR $F9FB ; Calculate movement
CFF9B8 STA $C9 ; Store the output, X movement, into $C9
; Apply the calcuated movement
CFF9BA LDX $C6 ; Load the enemy one more time
CFF9BC CLC
CFF9BD LDA $98B7,X ; Load current Y Coord
CFF9C0 ADC $C8 ; Add calculated value
CFF9C2 STA $1D26,X ; Store updated Y Coord
CFF9C5 CLC
CFF9C6 LDA $98AF,X ; Load current X Coord
CFF9C9 ADC $C9 ; Add calculated value
CFF9CB STA $1D0F,X ; Store updated X Coord
; Decrement the loop counter, if it's still positive continue looping, otherwise we're done
CFF9CE DEC $C6
CFF9D0 BPL $CFF97D
CFF9D2 RTS
The details of partial movement aren’t completely understood by me, but the gist is that the counter ($9737) is used to keep track of partial pixel movement (this is probably what the copy stored in $00 and referenced by $F9FB)
The code for $F9FB can be seen here
CFF9FB REP #$20
CFF9FD ASL
CFF9FE ASL
CFF9FF AND #$03FF
CFFA02 TAX
CFFA03 LDA $C0F900,X
CFFA07 AND #$00FF
CFFA0A CPX #$0200
CFFA0D BCC $CFFA13
CFFA0F EOR #$FFFF
CFFA12 INC
CFFA13 STA $02
CFFA15 SEP #$20
CFFA17 PHB
CFFA18 TDC
CFFA19 PHA
CFFA1A PLB
CFFA1B LDA $00
CFFA1D STA $4202
CFFA20 LDA $02
CFFA22 STA $4203
CFFA25 PHP
CFFA26 LDA $03
CFFA28 STZ $06
CFFA2A LDX $4216
CFFA2D STA $4203
CFFA30 STX $04
CFFA32 REP #$21
CFFA34 LDA $05
CFFA36 ADC $4216
CFFA39 STA $05
CFFA3B TDC
CFFA3C PLP
CFFA3D SEP #$20
CFFA3F PLB
CFFA40 LDA $05
CFFA42 RTS
The effect of this is that enemies move in 8 step increments, if an enemy is moving from 0->8 then each increment they will move 1 pixel. If an enemy is moving from 0->4 then every other increment they will move 1 pixel. This has been observed and is accurate, the details of where the enemy’s X/Y positions will be on each individual frame are not completely understood, but it seems that once we’ve reached this point in movement the movement is set and no other actions will occur until movement is finished.1
The maximum value of the movement timer is loaded from the enemies sprite data. For Blue Imps it is loaded from E4F681 and the value is 0x21. That value is shifted right 4 times then stored to 7E9742 + Y (Y being the enemy number being processed).
0x21 >> 4 = 1
, so every other frame the Blue Imp moves.
In observation the sprite of the Blue Imp seems to move every frame despite the actual X/Y coordinate only being updated every other frame, the sprite value is likely related to the number of frames is takes for the sprite’s movement animation to play although further observations are needed.
At this point this is what I’m hoping, but it’s possible that I’m completely wrong and will eventually have to flesh this out some more. ↩︎