; Feature flags NO_INTERRUPTS equ 0 ; turn off for crossrunner debugging NO_MUSIC equ 1 ; turn music + tool loading off ; External data space provided by the main program segment tiledata EXT TileStore EXT ; Sprite plane data and mask banks are provided as an external segment ; ; The sprite data holds a set of pre-rendered sprites that are optimized to support the rendering pipeline. There ; are four copies of each sprite, along with the cooresponding mask laid out into 4x4 tile regions where the ; empty row and column is shared between adjacent blocks. ; ; Logically, the memory is laid out as 4 columns of sprites and 4 rows. ; ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | | | | | | | | | | | | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | 0 | 0 | | 1 | 1 | | 2 | 2 | | 3 | 3 | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | | | | | | | | | | | | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | 4 | 4 | | 5 | 5 | | 6 | 6 | | 7 | 7 | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; | | | | | | | | | | | | | ... ; +---+---+---+---+---+---+---+---+---+---+---+---+-... ; ; For each sprite, when it needs to be copied into an on-screen tile, it could exist at any offset compared to its ; natural alignment. By having a buffer around the sprite data, an address pointer can be set to a different origin ; and a simple 8x8 block copy can cut out the appropriate bit of the sprite. For example, here is a zoomed-in look ; at a sprite with an offset, O, at (-2,-3). As shown, by selecting an appropriate origin, just the top corner ; of the sprite data will be copied. ; ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | || | || | | | || | | | | ; +---+-- O----------------+ --+---++---+---+---+---++---+---+---+---+.. ; | | | | | || | | | || | | | | ; +---+-- | | --+---++---+---+---+---++---+---+---+---+.. ; | | | | | || | | | || | | | | ; +---+-- | | --+---++---+---+---+---++---+---+---+---+.. ; | | | | | || | | | || | | | | ; +===+== | ++===+== | ==+===++===+===+===+===++===+===+===+===+.. ; | | | || | S | S | S || S | S | S | || | | | | ; +---+-- +----------------+ --+---++---+---+---+---++---+---+---+---+.. ; | | || S | S S | S || S | S | S | S || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | | | || S | S | S | S || S | S | S | S || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | | | || S | S | S | S || S | S | S | S || | | | | ; +===+===+===+===++===+===+===+===++===+===+===+===++===+===+===+===+.. ; | | | | || S | S | S | S || S | S | S | S || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | | | || S | S | S | S || S | S | S | S || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | | | || S | S | S | S || S | S | S | S || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; | | | | || | S | S | S || S | S | S | || | | | | ; +---+---+---+---++---+---+---+---++---+---+---+---++---+---+---+---+.. ; . . . . . . . . . . . . . . . . . ; ; Each sprite will take up, effectively 9 tiles of storage space per ; instance (plus edges) and there are 4 instances for the H/V bits ; and 4 more for the masks. This results in a need for 43,264 bytes ; for all 16 sprites. spritedata EXT spritemask EXT ; Core engine functionality. The idea is that that source file can be PUT into ; a main source file and all of the functionality will be available. ; ; There are some constancts that must be externally defined that can affect how ; the GTE runtime works ; ; NO_MUSIC : Set to non-zero to avoid using any source ; NO_INTERRUPTS : Set to non-zero to avoid installing custom interrupt handlers mx %00 ; Assumes the direct page is set and EngineMode and UserId has been initialized _CoreStartUp jsr IntStartUp ; Enable certain interrupts jsr InitMemory ; Allocate and initialize memory for the engine jsr EngineReset ; All of the resources are allocated, put the engine in a known state jsr InitGraphics ; Initialize all of the graphics-related data jsr InitSprites ; Initialize the sprite subsystem jsr InitTiles ; Initialize the tile subsystem jsr InitTimers ; Initialize the timer subsystem rts _CoreShutDown jsr IntShutDown rts ; Install interrupt handlers. We use the VBL interrupt to keep animations ; moving at a consistent rate, regarless of the rendered frame rate. The ; one-second timer is generally just used for counters and as a handy ; frames-per-second trigger. IntStartUp lda #NO_INTERRUPTS bne :no_interrupts PushLong #0 pea $0015 ; Get the existing 1-second interrupt handler and save _GetVector PullLong OldOneSecVec pea $0015 ; Set the new handler and enable interrupts PushLong #OneSecHandler _SetVector pea $0006 _IntSource PushLong #VBLTASK ; Also register a Heart Beat Task _SetHeartBeat :no_interrupts rts IntShutDown lda #NO_INTERRUPTS bne :no_interrupts pea $0007 ; disable 1-second interrupts _IntSource PushLong #VBLTASK ; Remove our heartbeat task _DelHeartBeat pea $0015 PushLong OldOneSecVec ; Reset the interrupt vector _SetVector :no_interrupts rts ; Interrupt handlers. We install a heartbeat (1/60th second and a 1-second timer) OneSecHandler mx %11 phb pha jsr _SetDataBank rep #$20 inc OneSecondCounter sep #$20 ldal $E0C032 and #%10111111 ;clear IRQ source stal $E0C032 pla plb clc rtl mx %00 ; This is OK, it's referenced by a long address VBLTASK hex 00000000 TaskCnt dw 1 hex 5AA5 VblTaskCode mx %11 lda #1 stal TaskCnt ; Reset the task count _VblTaskPatch jml _TaskStub ; Just something to catch the interrupt _TaskStub rtl mx %00 ; Reset the engine to a known state ; Blitter initialization EngineReset stz ScreenHeight stz ScreenWidth stz ScreenY0 stz ScreenY1 stz ScreenX0 stz ScreenX1 stz ScreenTileHeight stz ScreenTileWidth stz StartX stz OldStartX stz StartXMod164 stz StartY stz OldStartY stz StartYMod208 ; stz EngineMode stz DirtyBits stz LastRender ; Initialize as is a full render was performed stz LastPatchOffset stz BG1StartX stz BG1StartXMod164 stz BG1StartY stz BG1StartYMod208 stz BG1OffsetIndex stz BG0TileOriginX stz BG0TileOriginY stz OldBG0TileOriginX stz OldBG0TileOriginY stz BG1TileOriginX stz BG1TileOriginY stz OldBG1TileOriginX stz OldBG1TileOriginY stz TileMapWidth stz TileMapHeight stz TileMapPtr stz TileMapPtr+2 stz FringeMapPtr stz FringeMapPtr+2 stz BG1TileMapWidth stz BG1TileMapHeight stz BG1TileMapPtr stz BG1TileMapPtr+2 stz CompileBankTop stz SCBArrayPtr stz SCBArrayPtr+2 stz SpriteBanks stz SpriteMap stz ActiveSpriteCount stz OneSecondCounter lda #13 sta tmp15 stz tmp14 ; Rebuild all of the bank blitters :loop ldx #BlitBuff lda #^BlitBuff ldy tmp14 jsr BuildBank lda tmp14 clc adc #4 sta tmp14 dec tmp15 bne :loop ; Set the scanline tables to reasonable default values ; ldx #{416*2}-2 ; lda #0 ;:sxm_loop ; sta StartXMod164Arr,x ; dex ; dex ; bpl :sxm_loop rts WaitForKey sep #$20 stal KBD_STROBE_REG ; clear the strobe :WFK ldal KBD_REG bpl :WFK rep #$20 and #$007F rts ClearKbdStrobe sep #$20 stal KBD_STROBE_REG rep #$20 rts ; Read the keyboard and paddle controls and return in a game-controller-like format _ReadControl pea $0000 ; low byte = key code, high byte = %------AB sep #$20 ldal OPTION_KEY_REG ; 'B' button and #$80 beq :BNotDown lda #>PAD_BUTTON_B ora 2,s sta 2,s :BNotDown ldal COMMAND_KEY_REG and #$80 beq :ANotDown lda #>PAD_BUTTON_A ora 2,s sta 2,s :ANotDown ldal KBD_STROBE_REG ; read the keyboard bit #$80 beq :KbdNotDwn ; check the key-down status and #$7f ora 1,s sta 1,s cmp LastKey beq :KbdDown sta LastKey lda #>PAD_KEY_DOWN ; set the keydown flag ora 2,s sta 2,s bra :KbdDown :KbdNotDwn stz LastKey :KbdDown rep #$20 pla rts ; Helper function to take a local pixel coordinate [0, ScreenWidth-1],[0, ScreenHeight-1] and return the ; row and column in the tile store that is corresponds to. This takes into consideration the StartX and ; StartY offsets. ; ; This is more specialized than the code in the _MarkDirtySprite routine below since it does not deal with ; negative or off-screen values. _OriginToTileStore lda StartYMod208 lsr lsr and #$FFFE ; Store the pre-multiplied by 2 for indexing tay lda StartXMod164 lsr and #$FFFE ; Same pre-multiply by 2 for later tax rts ; X = local x-coordinate (0, playfield width) ; Y = local y-coordinate (0, playfield height) _LocalToTileStore clc tya adc StartYMod208 ; Adjust for the scroll offset cmp #208 ; check if we went too far positive bcc *+5 sbc #208 lsr lsr and #$FFFE ; Store the pre-multiplied by 2 for indexing tay clc txa adc StartXMod164 cmp #164 bcc *+5 sbc #164 lsr and #$FFFE ; Same pre-multiply by 2 for later tax rts