Jump to content
IGNORED

Bank switching 1MB


ZackAttack

Recommended Posts

I'm creating a new banking scheme that will handle up to 1MB. My understanding of the Atari is that addresses $0X3E and $0X3F are owned by the TIA, but unused. So writing to 003E, 013E, etc. will have no effect on the system. I plan to divide the 4K ROM address space into 16 banks that are each 256 bytes in size. Each bank will have their own set of 256 pages to choose from. (16*16*256=1MB) Writing a value to 003E will select which page to map to Bank 0. Writing a value to 013E will select which page to map to Bank 1 and so on all the way up to 0F3E/Bank F.

 

1) Am I missing any obvious design flaws?

2) Would anyone else be interested in using this banking scheme?

Link to comment
Share on other sites

The existing 3F bankswitch scheme (used by Tigervision) supports up to 512KB. I don't know if anybody's ever made use if it.

 

Tigervision also had a 3E scheme which used 3F to control the 512KB of ROM and 3E to control 32 KB of RAM.

 

The games Tigervision put out didn't utilize the full capacity.

Link to comment
Share on other sites

So there probably won't be much interest in this then. Maybe that will change once a game gets released on it.

 

I guess I should have mentioned that the existing schemes won't work for me because I am designing my engine to be split up into 256 byte chunks. All the existing schemes use banks that are much larger from what I can tell. I figure if everything is built to fit into a 256 byte bank that is always page aligned, there won't be any concern about throwing the timing off by crossing a page boundary. An added bonus is that is should allow me to shave a few cycles off my P0 rendering macro. It'll be so nice to gain a few cycles during H-blank.

 

I made some modifications to stella and my codebase to support the 1MB scheme I outlined above. It seems to work ok in the emulator. Dasm made it really easy to generate a 1MB file. I'll try it out on the encore once I can find the details on how to add new banking schemes to it. So far I haven't had any luck with that.

Link to comment
Share on other sites

256 bytes banks seems like overkill to me. In the end you will find yourself combining banks (but with extra overhead for bank switching). Also page crossings are no major problem (and your solution will no really help here anyway), too fragmented code segments are much worse.

 

From my own experience IMO 1K ROM banks seems like the best compromise between flexibility and bank switching overhead.

 

Assigning banks to fixed addresses reduces flexibility a bit too, but that's mainly true for RAM banks.

 

BTW: Unless it is all data, to utilize such an amount of memory efficiently, you will also need a decent amount of RAM. What are your plans here?

Edited by Thomas Jentzsch
Link to comment
Share on other sites

Page crossing is a major problem when I have an absolute indexed instruction in a kernel. The absolute address must point to the beginning of a page or the instruction will not have a deterministic execution time.

 

I could merge the first 4 banks into a single larger bank giving 1K sized banks. One of the 256 byte banks could be RAM allowing up to 32KB of RAM.

 

That would allow the 1MB to be divided up as follows:

4 banks merged into a single 1K bank providing room for up to 256KB of code

1 bank of RAM up to 32KB

8 banks will be used to store up to 256 subkernels. Each subkernel will be duplicated and relocated into each of the 8 banks. This will allow up to 8 subkernels to be used in a single frame in any combination.

1 bank will hold up to 256 P0 sprites. Each page will hold one sprite that is up to 64 bytes. The remainder of the page will need to be padded with $00.

1 bank will hold the corresponding color data for the P0 sprites. There will be some extra space available to use in these pages for code or data.

1 bank will hold level data

 

Obviously this will have a lot of wasted space, but I think it's necessary to allow the kernels to meet such demanding timing constraints.

Link to comment
Share on other sites

Page crossing is a major problem when I have an absolute indexed instruction in a kernel. The absolute address must point to the beginning of a page or the instruction will not have a deterministic execution time.

Really? :o Don't say so! ;)

 

Actually this is a problem that must be solved by careful development (macros can help a lot here), not bank switching.

 

I could merge the first 4 banks into a single larger bank giving 1K sized banks. One of the 256 byte banks could be RAM allowing up to 32KB of RAM.

 

That would allow the 1MB to be divided up as follows:

4 banks merged into a single 1K bank providing room for up to 256KB of code

1 bank of RAM up to 32KB

8 banks will be used to store up to 256 subkernels. Each subkernel will be duplicated and relocated into each of the 8 banks. This will allow up to 8 subkernels to be used in a single frame in any combination.

1 bank will hold up to 256 P0 sprites. Each page will hold one sprite that is up to 64 bytes. The remainder of the page will need to be padded with $00.

1 bank will hold the corresponding color data for the P0 sprites. There will be some extra space available to use in these pages for code or data.

If you have a lot of sprites (and animations) this will require a bank switching during the kernel execution.

 

Obviously this will have a lot of wasted space, but I think it's necessary to allow the kernels to meet such demanding timing constraints.

Unless you have a special game prototyped, IMO it makes little sense to design a bank switching tailored like this. Why would you need that many sub kernels? I yet have to see a game idea that needs that. E.g. Boulder Dash just had 11 kernels during display (score and 8 rows of tiles, with the score kernel alternating between 3 kernels). And what if someone wants to do a 98 pixel "hires" kernel? Then you need larger RAM chunks or multiple of them, or you will have to bank switch inside the kernel.

 

Andrew and I have wondered quite a lot about a bank switching which is better tailored to Boulder Dash. And we came up with four 1k chunks, each can be used by either a 1k ROM or a 512 byte RAM block. We also thought about an idea which allows us to address the RAM continuously, without having the R/W gaps. But after testing that idea, I found the disadvantages to be not worth it.

Link to comment
Share on other sites

Keeping track (mentally) of all those different banks will be difficult. Also having to do all that bank switching will take up extra processing time, something the Atari just doesn't have to spare.

 


This file includes macros for testing SAME PAGE branching as well as DIFFERENT PAGE branching (it's possible to need a 4 cycle branch).
macro.h.zip


To use them, include the file in your source:

TIA_BASE_READ_ADDRESS = $30
    include VCS.H
    include macro.h

Then an S or D in front of the branch command.

Normal loop

loop:
  ...
  dex
  bne loop



check Same page branch

loop:
  ...
  dex
  sbne loop



confirm Different page branch

loop:
  ...
  dex
  dbne loop
  • Like 1
Link to comment
Share on other sites

My motivation for creating a new scheme is that I want to have the ability to swap banks as small as 256 bytes. This is the result of putting together a prototype of a graphics engine that supports 8way scrolling with 1 pixels scrolling resolution in all directions and single line kernels. The prototype fits in 4k, but that required a lot of creativity with how everything was placed in the ROM image.

 

P0 is only drawn once per frame, but must be able to be moved around and support a different color for each row of graphic data. My solution in the prototype was to set the Y register just before scanning started according the desired P0 location and then embed the following macro in every kernel.

DRAW_PLAYER_CYCLE_COUNT = 22
	MAC DRAW_PLAYER
	LDA (PlayerColor),y	;5 Palette and Graphic data must be stored at page start (xx00) to keep this at 5 cycles always
	STA COLUP0		;3
	LDA (PlayerGraphic),y	;5 Ditto
	AND pPlayerSpriteMask,y	;4
	STA GRP0		;3
	INY			;2
	ENDM

By putting all the P0 graphic data at the same location on the 6507 address bus I can replace the indirect addressing with absolute and do away with the mask. So having a bank size of 256 bytes allows for this macro to be used instead.

DRAW_PLAYER_CYCLE_COUNT = 16
	MAC DRAW_PLAYER
	LDA PlayerColor,y	;4 Palette and Graphic data must be stored at page start (xx00) to keep this at 5 cycles always
	STA COLUP0		;3
	LDA PlayerGraphic,y	;4 Ditto
	STA GRP0		;3
	INY			;2
	ENDM

This macro works by exploiting the fact that Y can't be incremented past 255. It allows for the illusion of having a contiguous section of memory with 192 bytes of padding on each side of the graphics data. 192 + 64 + 192 is clearly more than 256, but the wrap around allows the same padding to be used on both sides of the graphics data. So if you wanted the P0 sprite to be visible on scanline 10 you would set Y to 245 just before the frame starts. This causes 0 to be stored in GRP0 for the first 10 lines while Y is 245-255. On the 11th line Y is incremented to 0 thanks to the wrap around and now the actual graphics data stored at the beginning of the page is copied to GRP0. After Y is incremented past the graphics data it will once again point to the 0's in the rest of the page which effectively disables P0 again after it is drawn.

 

A neat side effect of this approach is that all sprite graphic data is stored right side up.

Link to comment
Share on other sites

If I get you right, you want to use 256 bytes for each sprite at each possible vertical position. Correct?

 

So one single sprite with e.g. 4 animations and 150 vertical positions would require ~154k for the pattern, plus about 1/4th for color data. So in total that would be about 192k. Probably a bit less, but still...

Link to comment
Share on other sites

If I get you right, you want to use 256 bytes for each sprite at each possible vertical position. Correct?

 

So one single sprite with e.g. 4 animations and 150 vertical positions would require ~154k for the pattern, plus about 1/4th for color data. So in total that would be about 192k. Probably a bit less, but still...

No, vertical position is determined by the value of Y when the rendering of the frame starts (the top SubKernel begins execution). It's 256 bytes for each sprite. The color data is only as large as the sprite. It doesn't matter what gets stored in COLUP0 when GRP0 is set to $00.

 

So one single sprite with 4 animations and 256 vertical positions (scrolling off top and bottom) would require 1K of graphics data and up to 256 bytes of color data depending on the height of the sprite.

Edited by ZackAttack
Link to comment
Share on other sites

Wow - that's a lot of ROM filled with 0s.

 

Have you seen DPC+? Its an updated version of the DPC used in Pitfall 2. In Pitfall the way to draw the player is:

 lda DF0DATAW ; 4 cycles
 sta GRP0     ; 3 cycles
 lda DF1DATA  ; 4 cycles
 sta COLUP0   ; 3 cycles

The W in DF0DATAW means "use windowed mode" which will automatically return 0 on the scan lines the player shouldn't be drawn.

 

 

One of the new features in DPC+ is a FAST FETCH mode, which will override LDA # for an even faster update:

 

 lda #<DF0DATAW ; 2 cycles
 sta GRP0       ; 3 cycles
 lda #<DF1DATA  ; 2 cycles
 sta COLUP0     ; 3 cycles   
Link to comment
Share on other sites

OK, I got you (finally).

 

Let's assume, the kernel is 180 pixel tall and the sprite is at 120. So Y will be at 256-120=136. The first 120 rows will be 0. And after 120 rows it will wrap around to zero and start drawing the sprite for e.g. 16 rows. And then the remaining 44 rows with 0 again.

 

Correct? Then I understand your idea. But as Darrel said, that's quite a lot of space wasted just for padding.

 

I suppose branching between kernels is out of question? The result would be efficiently the same, just maybe one line more gap between reused sprites. Branching out of display the loop would be done with setting color to 0.

Link to comment
Share on other sites

I did see that DPC+ has the windowed mode which is essentially the same thing as what I'm doing.

 

The decision not to use DPC+ is really just my own personal preference. I wanted my first project to be limited to assembly and bank switching only. That said, I have absolutely nothing against DPC+ or the harmony cart. It's just not what I want to do for this specific project.

 

It is a lot of wasted space, but trading huge amounts of rom space for a few cycles in the kernels is part of my overall strategy to pulling this thing off.

 

I'm not sure what you mean by branching between kernels. But since it involves branching it is most likely out of the question.

 

I had considered limiting the horizontal position of P0 to the left side of the screen and then updating P0 at the end of each scanline. The biggest problem with that is it gets in the way of strobing RESP registers on the right side of the screen. So keeping it as small as possible and putting it in HBlank seemed like the best choice.

 

I posted a video of the engine prototype in action here. This was taken when it was still a 4k image. I could post the bin if anyone wanted it. The video already shows everything it can do.

Link to comment
Share on other sites

 

Keeping track (mentally) of all those different banks will be difficult. Also having to do all that bank switching will take up extra processing time, something the Atari just doesn't have to spare.

 

 

This file includes macros for testing SAME PAGE branching as well as DIFFERENT PAGE branching (it's possible to need a 4 cycle branch).

Darrell, I noticed that you are adding this to version 1.05. There was a version 1.06 that had Edwin Blink's Vertical Sync macro, and Dennis Debro's Boundary macro.

 

 

I added Supercat's and your Macros to it, as well as the updated Clean Start macro I wrote (saves bytes). I've tentatively labelled this version 1.07 as it is unofficial, and a new official version has not been released in some time because Atari2600.org is gone. Maybe Andrew could release a new official version?

 

 

macro107.zip

Edited by Omegamatrix
Link to comment
Share on other sites

I posted a video of the engine prototype in action here. This was taken when it was still a 4k image. I could post the bin if anyone wanted it. The video already shows everything it can do.

That demo sure looks cool.

 

But from what I can tell, you only use the sprites? One for the player and the other one (reused) for the platforms?

 

So I suppose you have plans for using more objects and the playfield too?

Link to comment
Share on other sites

I actually found the idea of having several small slots for ROM banks pretty interesting. Yes, there's quite a bit of overhead, but with a scheme like this you won't be switching banks so frequently, since you'll be able to piece together all the routines and data needed for each part of the game. You can load only the graphics you'll actually use, only the part of the level that's currently on screen, the AI routines for objects that are currently active, and so on. I don't know about the technical feasibility of such a scheme, but the idea is definitely interesting.

Link to comment
Share on other sites

...with a scheme like this you won't be switching banks so frequently, since you'll be able to piece together all the routines and data needed for each part of the game.

From my own experience I would say the opposite is true. You can only overcome the problem with massive redundancy (code and data), because usually there is quite a lot of code and data which is reused. So normally you would need access to multiple small banks at the same time. And then you have to bank switch a lot more than with larger banks.

 

But that's all theory. Probably ZackAttack should just start developing a game based on his specs. If it works, fine. And if not, he can still adjust the bank switching.

Link to comment
Share on other sites

That demo sure looks cool.

 

But from what I can tell, you only use the sprites? One for the player and the other one (reused) for the platforms?

 

So I suppose you have plans for using more objects and the playfield too?

Thanks. The plan is to use more objects as needed. The sky, platforms, grass, and dirt are all placed according to level data. So they could easily be moved around in a level editor. It's probably not that noticeable in the demo, but the platforms are all different distances vertically. For technical reasons the horizontal position must be snapped to a 32 pixel boundary. The playfield probably won't get much use because it can't be scrolled smoothly. It's main use will probably be filing in some gaps on the edge of the screen.

 

I actually found the idea of having several small slots for ROM banks pretty interesting. Yes, there's quite a bit of overhead, but with a scheme like this you won't be switching banks so frequently, since you'll be able to piece together all the routines and data needed for each part of the game. You can load only the graphics you'll actually use, only the part of the level that's currently on screen, the AI routines for objects that are currently active, and so on. I don't know about the technical feasibility of such a scheme, but the idea is definitely interesting.

It should actually reduce some overhead for the graphics portion at least. In the demo I'm using the RTS trick that Roland suggested to string together the 8 sub kernels. This consumes 18 bytes of ram and the SP register. If I make each sub kernel use the same entry point location within the bank I should be able to just JMP directly to the next sub kernel instead of using the RTS trick. There is also some savings in the setup code because the bank switch requires a single byte store instead of storing a two byte return address on the stack.

 

From my own experience I would say the opposite is true. You can only overcome the problem with massive redundancy (code and data), because usually there is quite a lot of code and data which is reused. So normally you would need access to multiple small banks at the same time. And then you have to bank switch a lot more than with larger banks.

 

But that's all theory. Probably ZackAttack should just start developing a game based on his specs. If it works, fine. And if not, he can still adjust the bank switching.

I've already started moving from the prototype to developing an actual game based. The goal is to have better animations and more complex background tiles by March.

Link to comment
Share on other sites

Thanks. The plan is to use more objects as needed. The sky, platforms, grass, and dirt are all placed according to level data. So they could easily be moved around in a level editor. It's probably not that noticeable in the demo, but the platforms are all different distances vertically. For technical reasons the horizontal position must be snapped to a 32 pixel boundary. The playfield probably won't get much use because it can't be scrolled smoothly. It's main use will probably be filing in some gaps on the edge of the screen.

So effectively you are looking for a kernel which displays multiple sprites most efficiently, right?

 

During a scanline there is more than enough CPU time to display two multi-colored sprites (even during repositioning one sprite). It only becomes critical when the playfield gets involved.

 

I suppose I must be missing something.

Link to comment
Share on other sites

Darrell, I noticed that you are adding this to version 1.05. There was a version 1.06 that had Edwin Blink's Vertical Sync macro, and Dennis Debro's Boundary macro.

 

 

I added Supercat's and your Macros to it, as well as the updated Clean Start macro I wrote (saves bytes). I've tentatively labelled this version 1.07 as it is unofficial, and a new official version has not been released in some time because Atari2600.org is gone. Maybe Andrew could release a new official version?

 

 

attachicon.gifmacro107.zip

 

Thanks! I've dropped it in my current projects. Hadn't run across Dennis' Boundary macro before. I've seen Edwin's Vertical Sync, but never used it as I'm always running out of processing time so I put code in there. As an example, this is my Vertical Sync for Draconian where I use the time to update the audio registers for sound effects and zero out TIA graphic registers so the first visible scanline doesn't display leftover graphics from the bottom:

 

VerticalBlank:
        ldx #2
        stx WSYNC
        stx VSYNC
        stx VBLANK
        ldy #$2B + 6 + 4
        sty TIM64T
        ldx #0          ; 2 12
AudioLoop:        
        lda #<DF0DATA   ; 2  2
        sta AUDC0,x     ; 4  6
        inx             ; 2  8
        cpx #6          ; 2 10
        bne AudioLoop   ; 3 13 * 6 - 1 = 77  89/13        
;        sta WSYNC       ; line 1 of VSYNC AUDC0 loop runs longer, so remove a WSYNC
        inc Frame
        sta WSYNC       ; line 2 of VSYNC
        ldx #0
ZERO = *-1              ; used to load A with 0 as we cannot use LDA #0        
        stx GRP0
        stx ENABL
        stx ENAM1
        stx ENAM0
        stx PF0
        stx PF1
        stx PF2
        stx GRP1
        stx COLUBK
        stx COLUPF
        stx CTRLPF
        stx VDELP0
        stx VDELP1
        stx WSYNC       ; line 3 of VSYNC
        stx VSYNC       ; turn off VSYNC
Edited by SpiceWare
Link to comment
Share on other sites



So effectively you are looking for a kernel which displays multiple sprites most efficiently, right?

 

During a scanline there is more than enough CPU time to display two multi-colored sprites (even during repositioning one sprite). It only becomes critical when the playfield gets involved.

 

I suppose I must be missing something.

The basic idea is that P0 is always the playable character and everything else is used to create the background/level/enemies. P1, M1, BALL, and COLUBK are primarily used to draw the background in order to support smooth scrolling in all directions. I think a good example is a hill. It needs to have an upward slope on the left side, a section of level ground on the top, and a downward slope on the right. Here is a simple diagram to help explain. The top portion shows what the player will see. The bottom portion shows the locations of M1 and BALL in dark green on the left and right sides respectively. The light green and brown areas are drawn by changing COLUBK at the correct times. The wider the top of the hill is the more PF register would be needed to fill in that top light green section. Trying to update that many PF registers in a single line would be too time consuming. So to me it seems easier to just change the background color at the right time and use the objects to hide the coarseness of the background color changing.

 

post-40226-0-28036400-1423154002.png

 

 

Link to comment
Share on other sites

OK, I got you.

 

Let's see, what you need for the hill example:

  • two reads and writes to HMP1 and HMM1 (table reads, using the same index register) (14)
  • one write to HMOVE (3)
  • one potential read and write to GRP0 and COLUP0 (table reads, the other index register) (14 using your fast macro)
  • two reads and writes to COLUBK, which have to be timed well (lots of different kernels) (12, read from RAM)
  • two updates to the index registers (4)
  • a loop condition when to switch to the bottom of the hill (disabling P1 and M1) (probably based on the P1/M1 index) (3)

So that's 50 cycles. Maybe a few cycles (<10) more for getting the two COLUBK write timings right. So you still have ~16 cycles left.

 

But then, where is the timing getting critical? Why do you need so much memory to get the cycle count down? Why do you think you cannot afford masking or branching?

Link to comment
Share on other sites

The Y register is used by the DRAW_PLAYER macro, so if the Y register is used anywhere else in the kernel it must be saved in memory and then restored after. In this example it shouldn't be an issue since X can be used to index into the HMP1/HMM1 lookup table and to detect when the bottom of the hill has been reached.

 

The reads for the COLUBK could probably be immediate to save a couple cycles there.

 

Supporting vertical scrolling also needs to be taken into account. If the hill is scrolled off the bottom of the screen it must terminate early and jump to the overscan routine. That means those remaining 16 cycles will mostly be accounted for.

  • Decrement the variable that tracks how many lines remain to be rendered by this kernel (5)
  • Branch out of kernel loop (3)
  • RTS to overscan (6)

Also, if the kernel performs any operation that isn't deterministic a STA WYSNC will be required to sync back up.

 

Regarding the question of why so much memory is required. The fast macro depends on padding each sprite out to a full page. The timing of COLUBK writes will require at least 6 kernels to scroll horizontally across the entire screen. Additional code is required when the kernel is scrolled off the top of the screen because the objects must be positioned correctly for the first visible line. Lastly, it takes a lot of time to utilize the rom space as efficiently as possible. So it seems more effective to me to just waste space everywhere and get the game working sooner. It's a lot easier to increase the size of the rom than to find more free time to work on a hobby. Oh, and arranging everything in consistent sized chunks should make the tools easier to build too.

Link to comment
Share on other sites

The Y register is used by the DRAW_PLAYER macro, so if the Y register is used anywhere else in the kernel it must be saved in memory and then restored after.

I never said anything different. :)

 

In this example it shouldn't be an issue since X can be used to index into the HMP1/HMM1 lookup table and to detect when the bottom of the hill has been reached.

Yup, that was my assumption too.

 

The reads for the COLUBK could probably be immediate to save a couple cycles there.

Are you going to use self modifying code? Else immediate loads prevent e.g. level color themes. Or require more redundancy.

 

Supporting vertical scrolling also needs to be taken into account. If the hill is scrolled off the bottom of the screen it must terminate early and jump to the overscan routine. That means those remaining 16 cycles will mostly be accounted for.

  • Decrement the variable that tracks how many lines remain to be rendered by this kernel (5)
  • Branch out of kernel loop (3)
  • RTS to overscan (6)
I think using Y for tracking the kernel lines is possible. Since you cannot simply count it down to 0 (which would save 5 cycles), you can still compare it with a RAM variable. That saves you 2 cycles.

 

Why do you account the 6 RTS cycles for the kernel? Also the branch out of the loop usually takes only 2 cycles because it is not taken. Only in case the jump is taken you have an extra cycle. I suppose new kernel setup code will follow in most cases, so that single cycle may or may not matter.

 

Also, if the kernel performs any operation that isn't deterministic a STA WYSNC will be required to sync back up.

True, but except for sprite repositioning with delay loops all other potential scenarios can often be avoided. Sometimes you have to waste a cycle or two, but you still save two or one cycle that way.

 

Regarding the question of why so much memory is required. The fast macro depends on padding each sprite out to a full page. The timing of COLUBK writes will require at least 6 kernels to scroll horizontally across the entire screen. Additional code is required when the kernel is scrolled off the top of the screen because the objects must be positioned correctly for the first visible line.

But you don't expect to really need 1MB or so, right?

 

Lastly, it takes a lot of time to utilize the rom space as efficiently as possible. So it seems more effective to me to just waste space everywhere and get the game working sooner. It's a lot easier to increase the size of the rom than to find more free time to work on a hobby. Oh, and arranging everything in consistent sized chunks should make the tools easier to build too.

That's a valid point. But you miss a part of the special Atari 2600 challenges then. :)
Link to comment
Share on other sites

Are you going to use self modifying code? Else immediate loads prevent e.g. level color themes. Or require more redundancy.

I wasn't planning on support for color themes. Now that you mention it, I think it would be a good idea. Maybe I'll expand the level data so the color scheme can be controlled per tile instead of per level. That would produce a more flexible level design.

I think using Y for tracking the kernel lines is possible. Since you cannot simply count it down to 0 (which would save 5 cycles), you can still compare it with a RAM variable. That saves you 2 cycles.

You're right. That should work nicely

Why do you account the 6 RTS cycles for the kernel? Also the branch out of the loop usually takes only 2 cycles because it is not taken. Only in case the jump is taken you have an extra cycle. I suppose new kernel setup code will follow in most cases, so that single cycle may or may not matter.

The demo chains the addresses of the 8 kernels and the overscan routine in memory and then uses RTS to transfer control from one to the next. I think this will end up being improved in the finished game.

But you don't expect to really need 1MB or so, right?

I don't need a ski boat with a 315hp V8, but I still bought one :) Realistically 1MB is overkill and I could make a nice game with much less space. It's just too tempting to not do it.

That's a valid point. But you miss a part of the special Atari 2600 challenges then. :)

It's the price you pay for greatness... Seriously though, thanks for all the great advice. Now how do I convince you to do all my code reviews from now on?
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...