Jump to content

Recommended Posts

2 hours ago, bsteux said:

One small remark for all the people who start coding with cc7800 : please use 'int' only when necessary, and use 'char' whenever possible (like with cc65). Succession of operations of 'int' (16 bits) have not been implemented in cc7800 and must be cut into several lines using intermediate results (it's not done automatically by cc7800. It will just tell you it's too complex). Shifting is also not implemented for 16 bits variables. Please also note that 'char' is unsigned by default (like cc65, but unlike gcc).

 

Good luck with your adventure engine. It looks cool. I liked these when I was young.

Yes, I realized all these restrictions yesterday. I am just testing if it would be feasible to mix small minigames in a text adventure. Like have a small map where you move, text to describe stuff, minigame when you reach a place of interest.

 

I am actually writing a small python converter for turning ScottKit adventure outputs into cc7800 C-code. In this way I have a fast toolchain for writing something fun for Xmas. It is about 90% implemented already. The ScottKit is written in ruby. It has a very simple syntax like:

 

room backstage "*The backstage is full of people warming up"
    exit north lobby
    exit east stage
    exit south dressingroom
 
occur 25% when at backstage
    print "I try to make a few pushups. -- nobody notice it..."
comment "pushups at backstage"
 
My script then turns these definitions into cc7800 C-program and also an executable program.

For those who want to better understand the difference between cc7800 and cc65, here is a simple test.c program :

 

unsigned char X;
char zone1[32], zone2[32];

void main()
{
    for (X = 0; X != 32; X++) {
        zone2[X] = zone1[X];
    }
}

 

Here is the result of cc65 -O -Or test.c :

.proc	_main: near

.segment	"CODE"

	lda     #$00
	sta     _X
L0012:	lda     _X
	cmp     #$20
	beq     L0005
	lda     #<(_zone2)
	ldx     #>(_zone2)
	clc
	adc     _X
	bcc     L000E
	inx
L000E:	sta     ptr1
	stx     ptr1+1
	ldy     _X
	lda     _zone1,y
	ldy     #$00
	sta     (ptr1),y
	inc     _X
	jmp     L0012
L0005:	rts

.endproc

 

and the result by cc7800 -g --insert-code test.c :

main	SUBROUTINE
;{
; for (X = 0; X != 32; X++) {
	LDX #0                 	; 2
.for1
; zone2[X] = zone1[X];
	LDA zone1,X            	; 4
	STA zone2,X            	; 4
.forupdate1
	INX	; 2
	CPX #32                	; 2
	BNE .for1              	; 2/3
.forend1
	RTS

 

The loop execution by cc7800 is 12 cycles vs ~48 cycles for cc65, a factor of 4 for this very simple case.

Is the code generated by cc7800 better ? Not really. The trick is that cc7800 directly maps C code to 6502 assembly, mapping the X and Y register to the X and Y global variables. cc7800 is just cheating.

On the other hand, cc65 uses code patterns that make use of X and Y registers, preventing the user to optimize his code as intended.

The advantage of cc65 is that by using code patterns, it's possible to implement on the 6502 a REAL full compliant C compiler, at the expense of efficiency.

cc7800 takes the opposite approach : by sacrificing full C compliance, it can generate optimized 6502 code. cc7800 uses some code patterns, but very simplistic ones, leaving X and Y registers to the user. As such, it can't cover all C language features (structs were discarded, as well as multi-dimensionnal arrays, 32 bits integers, floating points...). Only patterns that match easily to our poor old 6502 have been kept in.

But what do we want on the Atari 2600 and Atari 7800 ? Definitely optimized code, since the CPU is really the bottleneck : it must feed TIA or MARIA as fast as possible.

 

Don't worry, after this little advertisement, Dobkeratops is coming...

 

 

 

  • Like 3

Another thing in cc7800 is the simplicity. You can define where the code goes in your C-file. So there is no config files, no linker scripts. Everything is in one place. One problem with cc65 is that the same tool is supposed to fit all 8-bit computers. This means that there is very limited support for any hardware that has unique features like graphics chips, sound chips, timers...

  • Like 1

Hi,

It's Dobkeratop's day !

I've written some little code to integrate @Defender_2600's graphics. Here is the result in a separate code (examples/example_dobkeratops.c). Note that it suprisingly doesn't consume so much CPU (1/3 in NTSC and 60fps). It's a bit DMA intensive on the tail side (I've used the small_sprite trick to help a little bit on that side, since tail sprites are 10 or 12 pixels high, not 16), but that should be OK with the game logic... Let's see...

#include "prosystem.h"
#define _MS_DL_SIZE 96
#include "multisprite.h"

#include "example_dobkeratops_gfx.c"

const char dobkeratops_x[] = {0, 0, 6, 6, 37, 35, 37, 37, 40, 25, 0};
const char dobkeratops_y[] = {0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160};
const char dobkeratops_nbbytes[] = {37, 36, 24, 34, 16, 19, 18, 19, 17, 19, 37};
const char *dobkeratops_gfx[] = {dob1, dob2, dob3, dob4, dob5, dob6, dob7, dob8, dob9, dob10, dob11};

// Generated by 
// rustc misc/dobkeratops_tail.rs -o dokeratops_tail
// ./dobkeratops_tail > examples/example_dobkeratops_tail.c
#include "example_dobkeratops_tail.c"

void draw_dobkeratops(char xpos, char ypos, char anim)
{
    char x, y, c, *gfx;
    char *tailx = dobkeratops_tail_x[X = anim];
    char *taily = dobkeratops_tail_y[X];
    char margin = 16 - 12;
    // Draw tail
    gfx = tail1;
    for (c = 0; c != 17; c++) {
        x = tailx[Y = c] + xpos - 50;
        y = taily[Y] + ypos;
        if (c == 6) {
            gfx = tail2;
            margin = 16 - 10;
        } else if (c == 12) gfx = tail3;
        multisprite_display_small_sprite_ex(x, y, gfx, 2, 0, margin, 0);
    }
    x = tailx[Y = c] + xpos - 50;
    y = taily[Y] + ypos;
    multisprite_display_sprite_ex(x, y, tail4, 4, 0, 1);
    // Draw body
    for (c = 0; c != 11; c++) {
        x = dobkeratops_x[X = c] + xpos;
        y = dobkeratops_y[X] + ypos;
        gfx = dobkeratops_gfx[X];
        char nbbytes = dobkeratops_nbbytes[X];
        if (nbbytes > 31) { // Maria can't display more than 31 bytes at one
            multisprite_display_sprite_aligned(x, y, gfx, 31, 0, 1);
            x += 62;
            nbbytes -= 31;
            gfx += 31;
        }
        multisprite_display_sprite_aligned(x, y, gfx, nbbytes, 0, 1);
    }
}

void init()
{
    // Dobkeratops palette
    *P0C1 = multisprite_color(0x3c); 
    *P0C2 = multisprite_color(0x39); 
    *P0C3 = multisprite_color(0x36); 
    *P1C1 = multisprite_color(0x24); 
    *P1C2 = multisprite_color(0x22); 
    *P1C3 = 0x0e; 
    *P2C1 = 0x0a; 
    *P2C2 = 0x04; 
    *P2C3 = 0x02; 
    *P3C1 = multisprite_color(0xc9); 
    *P3C2 = multisprite_color(0xc6); 
    *P3C3 = multisprite_color(0x43); // Red (unused)
}

void main()
{
    char counter_tail = 0;
    char counter_move1 = 0, counter_move2 = 0;

    signed char tx = 0;
    multisprite_init();
    init();

    do {
        char x = 80 + tx;
        draw_dobkeratops(x, 16, counter_tail);
        counter_tail++;
        if (counter_tail == 60) counter_tail = 0;
        counter_move1++;
        if (counter_move1 == 5) {
            counter_move1 = 0;
            counter_move2++;
            if (counter_move2 < 30) {
               tx++;
            } else {
               tx--;
               if (counter_move2 > 60) counter_move2 = 0;
            }
        }
    
        multisprite_flip();
    } while(1);
}

The integration of this big ugly sprite is quite straightforward. The tail's trajectory computation can be found in misc/dobkeratops_tail.rs, that generates the C tables for the tail. It's written in Rust (well, Python would probably suit better for this. But why not using Rust is we can?) :

use std::f64::consts::PI;

fn main()
{
    let xstart = 70.0;
    let ystart = 153.0;
    const NB_ELTS: usize = 18;
    const NB_ANIM: usize = 60;
    let start_angle = 0.0;
    let elt_width = 6.0;
    let wave_amplitude = 30.0;

    let mut tx = [0.0; NB_ELTS];
    let mut ty = [0.0; NB_ELTS];
    for c in 0..NB_ANIM {
        for i in 0..NB_ELTS {
            let ii = i as f64;
            let wave = wave_amplitude * ((ii * (NB_ANIM as f64 / NB_ELTS as f64) - c as f64) * 2.0 * PI / NB_ANIM as f64).sin(); 
            let angle = (start_angle + 2.0 * ii + wave) * PI / 180.0;
            tx[i] = xstart - ii * angle.cos() * elt_width / 1.6; 
            ty[i] = ystart - ii * angle.sin() * elt_width;
        }
        // Evenly distribute sprites on the path
        let mut total_length = 0.0;
        for i in 1..NB_ELTS {
            let dx = tx[i] - tx[i - 1];
            let dy = ty[i] - ty[i - 1];
            total_length += (dx * dx + dy * dy).sqrt();
        }
        let output_point_spacing = total_length / (NB_ELTS - 1) as f64;
        let mut txx = [0.0; NB_ELTS];
        let mut tyy = [0.0; NB_ELTS];
        txx[0] = tx[0];
        tyy[0] = ty[0];
        txx[NB_ELTS - 1] = tx[NB_ELTS - 1];
        tyy[NB_ELTS - 1] = ty[NB_ELTS - 1];
        let mut j = 1; // j is the index of new points
        let mut travelled_so_far = 0.0;
        let mut travelled;
        for i in 1..NB_ELTS {
            travelled = travelled_so_far;
            let dx = tx[i] - tx[i - 1];
            let dy = ty[i] - ty[i - 1];
            travelled_so_far += (dx * dx + dy * dy).sqrt();
            while j < NB_ELTS - 1 && travelled_so_far > j as f64 * output_point_spacing {
                // We should put a new point
                let portion = ((j as f64 * output_point_spacing) - travelled) / (travelled_so_far - travelled); 
                txx[j] = tx[i - 1] + dx * portion;
                tyy[j] = ty[i - 1] + dy * portion;
                j += 1;
            }
        }
        print!("const char dobkeratops_tail{c}_x[{NB_ELTS}] = {{");
        for i in 0..NB_ELTS {
            if i != 0 { print!(", "); }
            print!("{}", txx[i].round() as u8);
        }
        println!("}};");
        print!("const char dobkeratops_tail{c}_y[{NB_ELTS}] = {{");
        for i in 0..NB_ELTS {
            if i != 0 { print!(", "); }
            print!("{}", tyy[i].round() as u8);
        }
        println!("}};");
    }
    print!("const char *dobkeratops_tail_x[{NB_ANIM}] = {{");
    for c in 0..NB_ANIM {
        if c != 0 { print!(", "); }
        print!("dobkeratops_tail{c}_x");
    }
    println!("}};");
    print!("const char *dobkeratops_tail_y[{NB_ANIM}] = {{");
    for c in 0..NB_ANIM {
        if c != 0 { print!(", "); }
        print!("dobkeratops_tail{c}_y");
    }
    println!("}};");
}

Next week : The dobkeratops integrated with the R-9 spaceship (in 160B) and the scrolling. Something that looks like R-Type.

dobkeratops.png

dobkeratops.yaml example_dobkeratops.a78

  • Like 7
  • Thanks 1

I have a question about finding the offending place in the code. Most of the time I get a line number that gives an approximate place of the problem. But sometimes I just get a line:

"Compiler error: Unimplemented feature - condition statement is partially implemented"

 

I wonder if you have a clue of how to find the spot. The offending part is somewhere in my messy adventure code. If you have time I would appreciate to learn how to spot the problem.

 

cc7800 -v -g -Iheaders -Iexamples -DMULTISPRITE_USE_VIDEO_MEMORY ./examples/cameltoe.c

 

Disabling part of the code and recompiling does find the spot. But it is a bit time consuming. Last time I had this warning it turned out to be a variable in ROM that I tried to change in the code.

 

cameltoe.c

19 minutes ago, karri said:

I have a question about finding the offending place in the code. Most of the time I get a line number that gives an approximate place of the problem. But sometimes I just get a line:

"Compiler error: Unimplemented feature - condition statement is partially implemented"

 

I wonder if you have a clue of how to find the spot. The offending part is somewhere in my messy adventure code. If you have time I would appreciate to learn how to spot the problem.

 

cc7800 -v -g -Iheaders -Iexamples -DMULTISPRITE_USE_VIDEO_MEMORY ./examples/cameltoe.c

 

Disabling part of the code and recompiling does find the spot. But it is a bit time consuming. Last time I had this warning it turned out to be a variable in ROM that I tried to change in the code.

 

cameltoe.c 50.91 kB · 0 downloads

I'll have a look ASAP at your code and go back to you. Most likely a bug of mine...

  • Like 1
  • Thanks 1
1 hour ago, bsteux said:

I'll have a look ASAP at your code and go back to you. Most likely a bug of mine...

I found the error:

 

ramchip int solution_index = -1;

 

I am not allowed to declare initial values at the time I define the variable.

 

When I removed the -1 assignment the cart compiles. It is still just work in progress... But perhaps around Xmas there could be a small adventure :) 

When I add the final main loop the compiler just freezes. I tried to check if it was out of memory or something.

 

The one I include here compiles nicely. But when I remove the comments in front of the routines the compiler appears to freeze completely. My guess is that most of these calls bring in tons of routines. So small stuff like first_look() and second_look() are easy. But the ones that call the adventure engine bring in more than it can chew...

	while (1) {
		switch (gamestate) {
		case FIRST_LOOK:
			first_look();
			break;
		case TIME_ADVANCE:
		//	time_advance();
			break;
		case SECOND_LOOK:
		//	second_look();
			break;
		case USER_INPUT:
		//	user_input();
			break;
		case USER_ACTION:
		//	user_action();
			break;
		case LIGHT_FADE:
		//	light_fade();
			break;
		}
	}

cameltoe.c

cameltoe.a78

14 hours ago, karri said:

I found the error:

 

ramchip int solution_index = -1;

 

I am not allowed to declare initial values at the time I define the variable.

 

When I removed the -1 assignment the cart compiles. It is still just work in progress... But perhaps around Xmas there could be a small adventure :) 

Yes, I've found this one. When you declare an initialized global variable, I set it as const to put it in ROM, and then comparisons between constants (-1 == -1) are not implemented. I'll generate an error when initializing a global variable in RAM instead...

For the second bug, I've got a problem in my building of the call tree in the recursive function cc7800::build::compute_function_level... I'll find it ASAP and make a new version...

  • Thanks 1
1 hour ago, karri said:

Thanks! I am using linux so I probably get the fixes from github. Today is pretty late so I will continue later.

I've modified build::compute_function_level to cope with recursion (Your function PerformActions is recursive and got cc7800 completely lost). Please beware that cc7800 doesn't use the stack for function calls, but "smart" allocated global variables, in order to speed up function calls. So recursion is really not recommended (it may work or not, depending on your use of the parameters, since the callee will share the parameter variables with the caller). Please check what is happening in your case. At least now your code is compiling.

25 minutes ago, bsteux said:

I've modified build::compute_function_level to cope with recursion (Your function PerformActions is recursive and got cc7800 completely lost). Please beware that cc7800 doesn't use the stack for function calls, but "smart" allocated global variables, in order to speed up function calls. So recursion is really not recommended (it may work or not, depending on your use of the parameters, since the callee will share the parameter variables with the caller). Please check what is happening in your case. At least now your code is compiling.

Thanks! I am going to remove the recursion anyway. I just forgot that it was still there.

I usually use some kind of real time counter for setting the speed in the user interface. For pal an unsigned int incremented at every VBL would be nice. For NTSC skipping each 6th increment would also give a 50Hz timer. Is this something that could by easily fetched from the rmt music player?

 

In order to keep the music flowing at the same tempo in PAL and NTSC I believe that 50Hz is easy to achieve as an update rate.

 

In cc65 we had a clock() function that returned timer ticks since startup.

 

I understand that it is not a good idea to bloat the system with functions that may not be useful. Do you have any thoughts of where to add a 50 Hz timer? System, music or in the game code.

15 hours ago, karri said:

I usually use some kind of real time counter for setting the speed in the user interface. For pal an unsigned int incremented at every VBL would be nice. For NTSC skipping each 6th increment would also give a 50Hz timer. Is this something that could by easily fetched from the rmt music player?

 

In order to keep the music flowing at the same tempo in PAL and NTSC I believe that 50Hz is easy to achieve as an update rate.

 

In cc65 we had a clock() function that returned timer ticks since startup.

 

I understand that it is not a good idea to bloat the system with functions that may not be useful. Do you have any thoughts of where to add a 50 Hz timer? System, music or in the game code.

I've never used the RIOT timer at the moment. Up to now, I've always used counters based on the vsync by "multisprite_flip()". If there is no slowdown (i.e. if the game is fast enough to cope with 50 or 60Hz), it's OK. In an adventure game (where I guess you don't use vsync...), maybe should you use the RIOT timer, no ? I'll add a support for it ASAP (I know that on the Atari 7800 it should be read twice due to pb of syncs between RIOT ans SALLY clocks).

a vsync counter is fine. I have to read through the multisprite_flip

 

but...

 

I can only find a single byte named _ms_pal_frame_skip_counter.

 

Is there a vsync counter available?

 

Or perhaps inrement a byte _ms_10hz_counter whenever the _ms_pal_frame_skip_counter resets?

 

I just need the counter for enough delay to allow the player to read a word when he scrolls through text.
A 10Hz counter is also fine for hysteresis detection to eliminate contact bounces.

On 11/8/2023 at 5:03 PM, bsteux said:

I've written some little code to integrate @Defender_2600's graphics. [...]

 

Note that it suprisingly doesn't consume so much CPU (1/3 in NTSC and 60fps). [...]

 

The integration of this big ugly sprite is quite straightforward. [...]

Impressive work, thank you! :)

 

On 11/8/2023 at 5:03 PM, bsteux said:

Next week : The dobkeratops integrated with the R-9 spaceship (in 160B) and the scrolling. Something that looks like R-Type.

Wait a moment :), I'm looking at the graphics of the first level of R-Type, and I'm thinking that instead of having a 'decent' background graphic in 160A, we could have a spectacular background graphic in 160B, using the tiles only in the upper and lower areas and leaving the central area almost empty as in the Amiga version (with the exception of a couple of central areas where we can have complete graphics in 160B as we only have the spaceship sprite).

 

 

 

 

In addition, the background color of the central area can progressively fade from black to blue and, by adding a few sprites in 160A here and there, we can obtain the illusion of a complete background, as in the Homebrew version for AMSTRAD CPC.

 

 

Seeing your skills, I am optimistic that you can achieve it and, if necessary, 60fps could possibly even be reduced to 30fps. I was already working on the background graphics of the first level in 160B, I should be able to share the tiles in the next days.

 

  • Like 3
On 11/10/2023 at 11:21 AM, karri said:

a vsync counter is fine. I have to read through the multisprite_flip

 

but...

 

I can only find a single byte named _ms_pal_frame_skip_counter.

 

Is there a vsync counter available?

 

Or perhaps inrement a byte _ms_10hz_counter whenever the _ms_pal_frame_skip_counter resets?

 

I just need the counter for enough delay to allow the player to read a word when he scrolls through text.
A 10Hz counter is also fine for hysteresis detection to eliminate contact bounces.

No, at the moment there is no vsync counter. _ms_pal_frame_skip_counter is only used on PAL systems to keep with a 60Hz tune (see example_sfx.c where I call sfx_play() twice when multisprite_pal_frames_skip()). The best is probably to add a counter yourself, either after multisprite_flip(), or better in an interrupt routine, so that even if multisprite_flip() is not called at every frame, your counter will be correct. Then by using a second variable, it's easy to derive a 10Hz counter using _ms_pal_detected flag. I don't want to put this by default, since I want to let the programmer free with the DLI implementation.

  • Like 1
On 11/11/2023 at 3:30 AM, Defender_2600 said:

 

Wait a moment :), I'm looking at the graphics of the first level of R-Type, and I'm thinking that instead of having a 'decent' background graphic in 160A, we could have a spectacular background graphic in 160B, using the tiles only in the upper and lower areas and leaving the central area almost empty as in the Amiga version (with the exception of a couple of central areas where we can have complete graphics in 160B as we only have the spaceship sprite).

 

OK. Let's try to do this. I think the problem is not really a matter of execution speed this time, but more a problem of ROM space (if the goal in the end is to make a full game).

By the way, if we are using my current routine, you will be limited to 64 different 160B 8x16 tiles, since I'm currently using indirect display mode, which limits the tiles memory to 4KB.

In order to move to immediate mode, I've got 2 options :

- Either use extra 16KB RAM and transfer in real time the tiles (removing the 4kb limit on the way) to the RAM for immediate display. Probably the best option, but the most complex to implement. This is what I did for vertical scrolling, but for horizontal scrolling, it's way more complex. But doable certainly. I have to think about it.

- Or prepare in ROM the data for immediate display. This is easy (I just have to modify tiles7800 to generate immediate mode data instead of indirect tiles), but gfx immediate data will be limited to 8 kB (4kB at 0x8000 and 4kB at 0xa000), due to the holey DMA constraint and using 1 bank per level of game. Another option is to use more ROM and another bankswitching technique (like banksets or anything else, for instance bankswitching memory at 0x4000, which is out of Holey DMA). Another option would be to cheat a little by removing holey DMA in the upper and lower zone, preventing the spaceship to go there (but allowing to use more ROM for the background). Many options.

Maybe should we first try to see what we can fit into 8kB of ROM by reusing tiles... Ok, let's try this...

 

 

 

  • Like 1

I will gladly help in any way I can. The graphics work and the coding here are phenomenal. The 7800 really shines with the talented devs/artists/musicians/fans here! I'm always impressed.

 

  • Like 5
10 hours ago, bsteux said:

OK. Let's try to do this. I think the problem is not really a matter of execution speed this time, but more a problem of ROM space (if the goal in the end is to make a full game).

The full game looks like an ambitious plan, but even a mini R-Type game would be great, with the complete first level or maybe even with the second level.

 

10 hours ago, bsteux said:

By the way, if we are using my current routine, you will be limited to 64 different 160B 8x16 tiles [...]

To get the first level accurate, with all the tiles of the arcade version, we need more space than this, I haven't done an exact calculation but I think that 128 tiles are not enough either. I'm assuming that the tiles cannot be flipped and/or mirrored. And I'm not including the space needed for the sprites and Boss enemy.

 

Just a note, to get the best possible result, I'm considering the playing field to be 208 pixels high + 8 pixels for the score and the 'wave cannon' (beam), for a total of 224 vertical pixels.

 

Here are some other tiles I've been working on, but I still need to fine tune the palette tones a bit. All of these tiles have flipped and some mirrored versions.

 

 

7800R-Typevsarcadeb.thumb.PNG.dfbe05f053b5f32c1ebcca18bf22f743.PNG

7800R-Typevsarcade.thumb.PNG.10c3dc174fb3bbcfc22e43700b590168.PNG

  • Like 4
5 hours ago, Defender_2600 said:

The full game looks like an ambitious plan, but even a mini R-Type game would be great, with the complete first level or maybe even with the second level.

 

To get the first level accurate, with all the tiles of the arcade version, we need more space than this, I haven't done an exact calculation but I think that 128 tiles are not enough either. I'm assuming that the tiles cannot be flipped and/or mirrored. And I'm not including the space needed for the sprites and Boss enemy.

 

Just a note, to get the best possible result, I'm considering the playing field to be 208 pixels high + 8 pixels for the score and the 'wave cannon' (beam), for a total of 224 vertical pixels.

 

I have no plan to make the full game, but at least choose a memory setup that would make the full game feasible. My idea was also to make a simple mock-up of the first level.

Concerning the resolution, this is exactly what I have chosen : 208 pixels high horizontal scrolling display + 8 320A/C line for the beam + 8 320A/C for the score / high score.

I also agree that that you will probably need more than 128 tiles. I'll try to find a simple solution to use 16Kb RAM as video memory. That would enable to use more ROM for the tiles (up to 16KB, i.e. 256 tiles) AND enable mirroring (at least vertical, possibly both) if I implement it for the ROM to RAM transfer. I'll think about it for this week's work...

  • Like 2

The SN cart has a 4k buffer at #d000 that can point anywhere in a 512k rom. So it is easy to copy graphics It also has a bit that can flip sprites left/right. But unfortunally not flip then up/down as required by R-Type.

 

One interesting feature of the SN cart is a 32k ram that can be toggled between two 16k banks. One bank could hold the 16k of graphics while drawing. The other bank could hold the music and sounds to be updated during blanking time.

 

The problem in the SN development is lack of emulators.

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...