+bsteux Posted November 13, 2023 Author Share Posted November 13, 2023 29 minutes ago, karri said: 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. This is exactly what I'd need. Well, as I have "only" a Concerto, I'll have to do without... But sure, if we wanted to get the best out of Maria, the best would be to have more flexible bankswitching schemes than then commercial ones from the 80s... And support for it in the emulators ! Quote Link to comment Share on other sites More sharing options...
Trebor Posted November 13, 2023 Share Posted November 13, 2023 9 minutes ago, bsteux said: This is exactly what I'd need. Well, as I have "only" a Concerto, I'll have to do without... But sure, if we wanted to get the best out of Maria, the best would be to have more flexible bankswitching schemes than then commercial ones from the 80s... And support for it in the emulators ! It may be of value to look at bankset bankswitching. 2 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 13, 2023 Author Share Posted November 13, 2023 29 minutes ago, Trebor said: It may be of value to look at bankset bankswitching. I was thinking of adding support for this to cc7800, since it's supported by a7800 (and thus at least testable). In the future... There is still enough meat on the "standard" setting for a couple of months... Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 13, 2023 Author Share Posted November 13, 2023 Here's an intermediate progress on R-Type, where : - I've added the 160B R-9 spaceship and missile firing (nothing special) - I show how to redefine the screen layout so that we have 2 lines of 320A/C at the bottom of the screen to display the score (display_init() function and dli interrupt) - I introduce the new function sparse_tiling_collision, which computes the collision of sprites with scrolling background (this one was a BIG headache. Not sure it's fully correct. At least it looks like it's working...). - The horizontal sparse tiling routine is now way better balanced over frames. This leaves tons of GFlops for processing sprites and logic. In the end, it should look like R-Type Final on PS4. Note that when the R-9 flies at the bottom, I sometimes run out of DMA... (that wasn't expected. 160A background + 160A full tiling + 1 160B sprite ? ) Background: 10 + 20 * 3 + 8 + 24 * 3 = 150 cycles Tiling: 23 * 9 = 207 cycles + sometimes 4 headers on the line = 247 9-B 160B sprite: 10 + 8 * 3 = 34 cycles This sums to 150 + 247 + 34 = 431 cycles... Wheee, yes, I'm running out of DMA... Nervous breakdown ! Let's go to immediate mode quickly for the tiling ! (and hide a little bit on background at the bottom of the screen if we want to use 160B tiles...) No video from the real Atari 7800 today : my Concerto is apparently sick (blue / green screen alternating). Here is the code example_rtype.c : #include "string.h" #define _MS_DL_SIZE 96 #define HORIZONTAL_SCROLLING #define _MS_BOTTOM_SCROLLING_ZONE 1 #include "sparse_tiling.h" #include "joystick.h" // Generated from sprites7800 RType_tiles.yaml #include "example_RType_tiles.c" // Generated from sprites7800 RType_sprites.yaml #include "example_RType_sprites.c" // Generated from tiles7800 --sparse RType_tiles.yaml --varname tilemap_level1 RType_level1.tmx #include "example_RType_level1.c" // Generated from sprites7800 RType_font.yaml #include "example_RType_font.c" // DLI management ramchip char save_acc, save_x, save_y; void interrupt dli() { store(save_acc); save_x = X; save_y = Y; multisprite_set_charbase(alphabet); *CTRL = 0x43; // DMA on, 320A/C mode, One (1) byte characters mode X = save_x; Y = save_y; load(save_acc); } // Game state management #define MISSILES_SPEED 4 #define MISSILES_NB_MAX 5 ramchip char missile_xpos[MISSILES_NB_MAX], missile_ypos[MISSILES_NB_MAX]; ramchip char missile_first, missile_last; ramchip char button_pressed; ramchip char R9_xpos, R9_ypos, R9_state, R9_state_counter; ramchip unsigned int score, high_score; ramchip char update_score; ramchip char display_score_str[5]; ramchip char display_high_score_str[5]; void game_init() { score = 0; high_score = 0; update_score = 1; // Init game state variables missile_first = 0; missile_last = 0; // Initialize spaceship state R9_xpos = 20; R9_ypos = 80; R9_state = 1; R9_state_counter = 100; } void step() { char x, y, i; // Draw missiles for (i = missile_first; i != missile_last; i++) { if (i == MISSILES_NB_MAX) { i = 0; if (missile_last == 0) break; } if (missile_xpos[X] != -1) { y = missile_ypos[X = i]; x = missile_xpos[X] + MISSILES_SPEED; if (x >= 160 || sparse_tiling_collision(y + 1, x, x + 15) != -1 ) { // Out of screen missile_xpos[X = i] = -1; // Removed do { X++; if (X == MISSILES_NB_MAX) X = 0; } while (X != missile_last && missile_xpos[X] == -1); missile_first = X; } else { missile_xpos[X = i] = x; // Draw missile multisprite_display_small_sprite_ex(x, y, missile, 2, 6, 13, 0); } } } char draw_R9; if (R9_state == 0) { draw_R9 = 1; // Check collision with background char c = sparse_tiling_collision(R9_ypos + 6, R9_xpos, R9_xpos + 15); if (c != -1) { R9_state = 1; R9_state_counter = 50; score = c; update_score = 1; } } else if (R9_state == 1) { // Blinking returning R9 draw_R9 = R9_state_counter & 8; R9_state_counter--; if (R9_state_counter == 0) { R9_state = 0; } } else if (R9_state == 2) { draw_R9 = 0; } if (draw_R9) { multisprite_display_small_sprite_ex(R9_xpos, R9_ypos, R9, 8, 4, 4, 1); } } void fire() { X = missile_last++; if (missile_last == MISSILES_NB_MAX) missile_last = 0; if (missile_last != missile_first) { missile_xpos[X] = R9_xpos + 8; missile_ypos[X] = R9_ypos + 6; } else missile_last = X; } void joystick_input() { joystick_update(); if (joystick[0] & JOYSTICK_LEFT) { if (R9_xpos) R9_xpos--; } else if (joystick[0] & JOYSTICK_RIGHT) { if (R9_xpos < 160 - 16) R9_xpos++; } if (joystick[0] & JOYSTICK_UP) { if (R9_ypos > 2) R9_ypos -= 2; } else { if (joystick[0] & JOYSTICK_DOWN) { if (R9_ypos < 208 - 14) R9_ypos += 2; } } if (joystick[0] & JOYSTICK_BUTTON1) { if (!button_pressed) { button_pressed = 1; if (R9_state != 2) fire(); } } else button_pressed = 0; } // Background scrolling char scroll_background_counter; void scroll_background() { char c; signed char pos1, pos2, pos3, pos4; pos1 = -scroll_background_counter; pos2 = pos1 + 80; pos3 = pos1 - 8; if (pos3 < -16) pos3 += 16; pos4 = pos3 + 80; if (_ms_buffer) { X = _MS_DLL_ARRAY_SIZE + 1; scroll_background_counter++; if (scroll_background_counter == 16) scroll_background_counter = 0; } else X = 1; _ms_tmpptr = _ms_dls[X]; for (c = 0; c != 3; c++) { // Modify bytes 4 and 8 of the DLL entries (x position of background sprites= _ms_tmpptr[Y = 4] = pos1; _ms_tmpptr[Y = 8] = pos2; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos1; _ms_tmpptr[Y = 8] = pos2; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos3; _ms_tmpptr[Y = 8] = pos4; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos3; _ms_tmpptr[Y = 8] = pos4; _ms_tmpptr = _ms_dls[++X]; } } void display_score_update(char *score_str) { char display_score_ascii[6]; itoa(score, display_score_ascii, 10); X = strlen(display_score_ascii); for (Y = 0; Y != 5 - X; Y++) { score_str[Y] = 26; // '0' } Y = 4; do { score_str[Y--] = 26 + (display_score_ascii[--X] - '0'); } while (X); } void display_init() { *BACKGRND = 0x0; multisprite_get_tv(); multisprite_clear(); multisprite_save(); _ms_tmpptr = _ms_b0_dll; for (X = 0, _ms_tmp = 0; _ms_tmp <= 1; _ms_tmp++) { // Build DLL // 69 blank lines for PAL // 19 blank lines for NTSC if (_ms_pal_detected) { // 16 blank lines _ms_tmpptr[Y = 0] = 0x0f; // 16 lines _ms_tmpptr[++Y] = _ms_set_wm_dl >> 8; _ms_tmpptr[++Y] = _ms_set_wm_dl; // 16 blank lines _ms_tmpptr[++Y] = 0x0f; // 16 lines _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; } else { _ms_tmpptr[Y = 0] = 0x08; // 9 lines _ms_tmpptr[++Y] = _ms_set_wm_dl >> 8; _ms_tmpptr[++Y] = _ms_set_wm_dl; } // 16 pixel high regions for (_ms_tmp2 = 0; _ms_tmp2 != _MS_DLL_ARRAY_SIZE - 2; X++, _ms_tmp2++) { _ms_tmpptr[++Y] = 0x4f; // 16 lines _ms_tmpptr[++Y] = _ms_dls[X] >> 8; // High address _ms_tmpptr[++Y] = _ms_dls[X]; // Low address } // 1 pixel high region to separate from 320A scoreboard. This gives some little room for the DLI to execute _ms_tmpptr[++Y] = 0x00; // 1 line _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; // 8 pixel high regions (320A) for (_ms_tmp2 = 0; _ms_tmp2 != 2; X++, _ms_tmp2++) { _ms_tmpptr[++Y] = 0x07; // 8 lines _ms_tmpptr[++Y] = _ms_dls[X] >> 8; // High address _ms_tmpptr[++Y] = _ms_dls[X]; // Low address } if (_ms_pal_detected) { // 16 blank lines _ms_tmpptr[++Y] = 0x0f; // 16 lines _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; // 16 blank lines _ms_tmpptr[++Y] = 0x0f; // 16 lines _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; // 4 blank lines _ms_tmpptr[++Y] = 0x03; // 4 lines _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; } else { _ms_tmpptr[++Y] = 0x08; // 9 lines _ms_tmpptr[++Y] = _ms_blank_dl >> 8; _ms_tmpptr[++Y] = _ms_blank_dl; } _ms_tmpptr = _ms_b1_dll; } multisprite_start(); } const char oneup[3] = {27, 'U' - 'A', 'P' - 'A'}; const char high[4] = {'H' - 'A', 'I' - 'A', 'G' - 'A', 'H' - 'A'}; const char beam[4] = {'B' - 'A', 'E' - 'A', 'A' - 'A', 'M' - 'A'}; const char gauge_out[17] = {45, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 47}; const char gauge_in[15] = { 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48}; void rtype_init() { sparse_tiling_init(tilemap_level1_data_ptrs); multisprite_set_charbase(brown_tiles1); // Green (background) color *P3C1 = multisprite_color(0xd0); *P3C2 = multisprite_color(0xd2); *P3C3 = multisprite_color(0xd1); // Beige palette *P4C1 = multisprite_color(0x12); *P4C2 = multisprite_color(0x14); *P4C3 = multisprite_color(0x16); // Blue palette *P5C1 = multisprite_color(0x84); // Dark blue *P5C2 = multisprite_color(0x87); // Light blue *P5C3 = multisprite_color(0xac); // Turquoise // Rose palette *P6C1 = multisprite_color(0x34); // Dark Rose *P6C2 = multisprite_color(0x38); // Rose *P6C3 = multisprite_color(0x3c); // Light Rose // Grey palette *P7C1 = 0x04; // Dark gray *P7C2 = 0x08; // Medium gray *P7C3 = 0x0c; // Dark gray multisprite_display_tiles(3 * 4, 14, oneup, 3, 5); multisprite_display_tiles(7 * 4, 14, display_score_str, 5, 7); multisprite_display_tiles(16 * 4, 14, high, 4, 5); multisprite_display_tiles(21 * 4, 14, display_high_score_str, 5, 7); multisprite_display_tiles(8 * 4, 13, beam, 4, 5); multisprite_display_tiles(13 * 4, 13, gauge_out, 17, 7); multisprite_display_tiles(14 * 4, 13, gauge_in, 1, 5); // Background display char c, y = 0; for (c = 0; c != 3; c++) { y += 16; multisprite_display_sprite_ex(0, y, background_level1, 20, 3, 0); multisprite_display_sprite_fast(80, y, background_level1, 24, 3); y += 16; multisprite_display_sprite_ex(0, y, background_level1_1, 20, 3, 0); multisprite_display_sprite_fast(80, y, background_level1_1, 24, 3); y += 16; multisprite_display_sprite_ex(-8, y, background_level1, 20, 3, 0); multisprite_display_sprite_fast(72, y, background_level1, 24, 3); y += 16; multisprite_display_sprite_ex(-8, y, background_level1_1, 20, 3, 0); multisprite_display_sprite_fast(72, y, background_level1_1, 24, 3); } // Save it multisprite_save(); multisprite_enable_dli(13); sparse_tiling_display(); multisprite_flip(); sparse_tiling_scroll(1); // Offset of 1 compared to previous screen sparse_tiling_display(); multisprite_flip(); } void main() { scroll_background_counter = 0; button_pressed = 0; joystick_init(); display_init(); rtype_init(); game_init(); do { scroll_background(); sparse_tiling_scroll(2); // Offset of 2 compared to previous same buffer (1 pixel scrolling due to double buffering) joystick_input(); step(); if (update_score) { display_score_update(display_score_str); if (score >= high_score) { high_score = score; display_score_update(display_high_score_str); } update_score = 0; } multisprite_flip(); multisprite_set_charbase(brown_tiles1); *CTRL = 0x50; // DMA on, 160A/B mode, Two (2) byte characters mode } while (1); } I'll now work on using video memory (16Kb extra RAM) to put the tiles and use immediate mode. If I succeed and survive, I will integrate the dobkeratops' code into this example and add explosions and gameover... RType_sprites.yaml example_RType.a78 example_RType.mp4 8 Quote Link to comment Share on other sites More sharing options...
KrunchyTC Posted November 13, 2023 Share Posted November 13, 2023 (edited) Good stuff bsteux. I really think that R-Type should be fully developed for the 7800, it's an awesome game! Edited November 16, 2023 by KrunchyTC 2 Quote Link to comment Share on other sites More sharing options...
+karri Posted November 16, 2023 Share Posted November 16, 2023 I have some interesting results when I tried to replace the font in conio.h with my own. Obviously the keywords reversed scattered(8,1) font[1024] do a lot of interesting things. It turned out that I only needed to add mode: 320A to my yaml file. Now I have my own coloured mono fonts working Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 17, 2023 Author Share Posted November 17, 2023 On 11/16/2023 at 6:55 PM, karri said: I have some interesting results when I tried to replace the font in conio.h with my own. Obviously the keywords reversed scattered(8,1) font[1024] do a lot of interesting things. It turned out that I only needed to add mode: 320A to my yaml file. Now I have my own coloured mono fonts working Yes, cc7800 uses a few keywords to map the data to ROM. "reversed" automatically reverses the array line by line, as Maria displays everything upside down (the line counter in MARIA starts from OFFSET down to 0 for each Display List). "Scattered" applies the right scattered memory layout. Again, Maria separates every line of gfx by 256 bytes (the most significant byte of an address if basically the line of the gfx, and the least significant byte is the gfx id or character number). So reversed and scattered takes a usual array (typically your font) and applies the right layout so that Maria can find its babies. "Holeydma" is another keyword that will select an holeydma-enabled ROM zone or not. Don't forget that Holeydma-enabled memory is scarse (only 3 4kb memory zones : 0xa000, 0xc000 and 0e000). And yes, in sprites7800, mode 160A is the default. You have to specify 320A mode somewhere if your gfx is a font. Have fun ! 1 Quote Link to comment Share on other sites More sharing options...
+karri Posted November 18, 2023 Share Posted November 18, 2023 Here is a first example of using 320A font with colours to make the text easier to understand. The font is the vga-font. I did a pull request to add it... It is a nice feature in your conio.h implementation to be able to request the text colour and the system then allocates the actual display lists to match the request. 3 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 18, 2023 Author Share Posted November 18, 2023 7 hours ago, karri said: Here is a first example of using 320A font with colours to make the text easier to understand. The font is the vga-font. I did a pull request to add it... Pull request accepted ! Your screenshot is looking cool. 1 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 18, 2023 Author Share Posted November 18, 2023 Alleluia ! The Atari 7800 is coming back in all its glory ! You can throw your PS5 through the window. It will look soon so 2020... I've implemented horizontal sparse tiling scrolling using extra RAM video memory. It's a bit miracle that it's working, but it's working... What exactly does it do ? - It transfers in real time the tiles in ROM to RAM, so that all the display is done only using immediate (or direct, call it like you want) display mode, paving the way to 160B R-Type background - This really saves a lot of CPU ! Roughly, transferring 16 tiles to RAM costs approx. 10000 6502 cycles, but this saves 160 pixels * 16 tiles * 16 lines * 1 CPU DMA cycle ~ 40000 cycles of CPU blocked by DMA. So the yielding is 4 to 1, for a scrolling of 1 pixel per frame. And 8 to 1 for a scrolling of 0.5 pixel per frame (like in the example), more realistic for R-Type. - I've also implemented vertical tiles mirroring, both in the ROM to RAM transfer routine, but also in Tiles7800. This doubles the number of tiles actually usable. - It opens the way to using 256 different 160B tiles, i.e. 16kb of tiles, since the tiles don't need to be packed into 4kb. - And last be not least, the ROM to RAM routine uses a massive 105 bytes of zeropage memory ! But with cc7800 automatic zeropage registers allocation (which shares the zeropage registers among the routines according to the call tree), it just costs 0 zeropage memory in the end. - It uses 12kb of RAM as video memory ring buffer. There are 4kb still available for some other funny stuff ! - ASM code free - The bad news is that the transfer is not balanced among frames. It can consume up to 1/3 frame of CPU when a tileset of 16 tiles is transformed into a 32 bytes of immediate mode display... But 60fps game keeps being achievable (maybe with some rare frame skipping we can deal with). Mirrored tiles are signaled in the YAML file (describing the tiles for C code generation) by the "mirror: !Vertical" YAML entry. ... sprite_sheets: - image: RType_tiles_mirror.png bank: 1 mirror: !Vertical sprites: - name: brown_tiles1 top: 0 left: 0 ... With Tiled, vertically mirrored tiles can then be used just as normal tiles. The routine itself that transfers from ROM to RAM, which looks ugly (extract from headers/sparse_tiling.h) : #ifdef MULTISPRITE_USE_VIDEO_MEMORY void _sparse_tiling_ROM_to_RAM(char *sptr, char w, char mode) { char low, high, len, len2, tmp; len2 = (mode)?(w << 1):w; // Number of entries in chptr len = len2 << 1; // Number of actual bytes low = _sparse_tiling_vmem_ptr_low; high = _sparse_tiling_vmem_ptr_high; // Allocate n bytes in vmem tmp = low - len; if (tmp < 0) { low = -len; high += 16; if (high == 0x70) high = 0x40; } else low = tmp; _sparse_tiling_vmem_ptr_low = low; _sparse_tiling_vmem_ptr_high = high; char *vmemptr0, *vmemptr1, *vmemptr2, *vmemptr3, *vmemptr4, *vmemptr5, *vmemptr6, *vmemptr7, *vmemptr8, *vmemptr9, *vmemptr10, *vmemptr11, *vmemptr12, *vmemptr13, *vmemptr14, *vmemptr15; char *chptr0, *chptr1, *chptr2, *chptr3, *chptr4, *chptr5, *chptr6, *chptr7, *chptr8, *chptr9, *chptr10, *chptr11, *chptr12, *chptr13, *chptr14, *chptr15; char vtmp1[16], vtmp2[16]; Y = low; X = high; vmemptr0 = Y | (X++ << 8); vmemptr1 = Y | (X++ << 8); vmemptr2 = Y | (X++ << 8); vmemptr3 = Y | (X++ << 8); vmemptr4 = Y | (X++ << 8); vmemptr5 = Y | (X++ << 8); vmemptr6 = Y | (X++ << 8); vmemptr7 = Y | (X++ << 8); vmemptr8 = Y | (X++ << 8); vmemptr9 = Y | (X++ << 8); vmemptr10 = Y | (X++ << 8); vmemptr11 = Y | (X++ << 8); vmemptr12 = Y | (X++ << 8); vmemptr13 = Y | (X++ << 8); vmemptr14 = Y | (X++ << 8); vmemptr15 = Y | (X++ << 8); X = _sparse_tiling_charbase; chptr0 = X++ << 8; chptr1 = X++ << 8; chptr2 = X++ << 8; chptr3 = X++ << 8; chptr4 = X++ << 8; chptr5 = X++ << 8; chptr6 = X++ << 8; chptr7 = X++ << 8; chptr8 = X++ << 8; chptr9 = X++ << 8; chptr10 = X++ << 8; chptr11 = X++ << 8; chptr12 = X++ << 8; chptr13 = X++ << 8; chptr14 = X++ << 8; chptr15 = X++ << 8; for (Y = 0; Y != len2; Y++) { tmp = Y; Y = sptr[Y]; X = Y & 1; if (X) { // Vertical mirroring) Y--; } vtmp1[0] = chptr0[Y]; vtmp1[1] = chptr1[Y]; vtmp1[2] = chptr2[Y]; vtmp1[3] = chptr3[Y]; vtmp1[4] = chptr4[Y]; vtmp1[5] = chptr5[Y]; vtmp1[6] = chptr6[Y]; vtmp1[7] = chptr7[Y]; vtmp1[8] = chptr8[Y]; vtmp1[9] = chptr9[Y]; vtmp1[10] = chptr10[Y]; vtmp1[11] = chptr11[Y]; vtmp1[12] = chptr12[Y]; vtmp1[13] = chptr13[Y]; vtmp1[14] = chptr14[Y]; vtmp1[15] = chptr15[Y]; Y++; vtmp2[0] = chptr0[Y]; vtmp2[1] = chptr1[Y]; vtmp2[2] = chptr2[Y]; vtmp2[3] = chptr3[Y]; vtmp2[4] = chptr4[Y]; vtmp2[5] = chptr5[Y]; vtmp2[6] = chptr6[Y]; vtmp2[7] = chptr7[Y]; vtmp2[8] = chptr8[Y]; vtmp2[9] = chptr9[Y]; vtmp2[10] = chptr10[Y]; vtmp2[11] = chptr11[Y]; vtmp2[12] = chptr12[Y]; vtmp2[13] = chptr13[Y]; vtmp2[14] = chptr14[Y]; vtmp2[15] = chptr15[Y]; Y = tmp << 1; if (X) { vmemptr0[Y] = vtmp1[15]; vmemptr1[Y] = vtmp1[14]; vmemptr2[Y] = vtmp1[13]; vmemptr3[Y] = vtmp1[12]; vmemptr4[Y] = vtmp1[11]; vmemptr5[Y] = vtmp1[10]; vmemptr6[Y] = vtmp1[9]; vmemptr7[Y] = vtmp1[8]; vmemptr8[Y] = vtmp1[7]; vmemptr9[Y] = vtmp1[6]; vmemptr10[Y] = vtmp1[5]; vmemptr11[Y] = vtmp1[4]; vmemptr12[Y] = vtmp1[3]; vmemptr13[Y] = vtmp1[2]; vmemptr14[Y] = vtmp1[1]; vmemptr15[Y] = vtmp1[0]; Y++; vmemptr0[Y] = vtmp2[15]; vmemptr1[Y] = vtmp2[14]; vmemptr2[Y] = vtmp2[13]; vmemptr3[Y] = vtmp2[12]; vmemptr4[Y] = vtmp2[11]; vmemptr5[Y] = vtmp2[10]; vmemptr6[Y] = vtmp2[9]; vmemptr7[Y] = vtmp2[8]; vmemptr8[Y] = vtmp2[7]; vmemptr9[Y] = vtmp2[6]; vmemptr10[Y] = vtmp2[5]; vmemptr11[Y] = vtmp2[4]; vmemptr12[Y] = vtmp2[3]; vmemptr13[Y] = vtmp2[2]; vmemptr14[Y] = vtmp2[1]; vmemptr15[Y] = vtmp2[0]; } else { vmemptr0[Y] = vtmp1[0]; vmemptr1[Y] = vtmp1[1]; vmemptr2[Y] = vtmp1[2]; vmemptr3[Y] = vtmp1[3]; vmemptr4[Y] = vtmp1[4]; vmemptr5[Y] = vtmp1[5]; vmemptr6[Y] = vtmp1[6]; vmemptr7[Y] = vtmp1[7]; vmemptr8[Y] = vtmp1[8]; vmemptr9[Y] = vtmp1[9]; vmemptr10[Y] = vtmp1[10]; vmemptr11[Y] = vtmp1[11]; vmemptr12[Y] = vtmp1[12]; vmemptr13[Y] = vtmp1[13]; vmemptr14[Y] = vtmp1[14]; vmemptr15[Y] = vtmp1[15]; Y++; vmemptr0[Y] = vtmp2[0]; vmemptr1[Y] = vtmp2[1]; vmemptr2[Y] = vtmp2[2]; vmemptr3[Y] = vtmp2[3]; vmemptr4[Y] = vtmp2[4]; vmemptr5[Y] = vtmp2[5]; vmemptr6[Y] = vtmp2[6]; vmemptr7[Y] = vtmp2[7]; vmemptr8[Y] = vtmp2[8]; vmemptr9[Y] = vtmp2[9]; vmemptr10[Y] = vtmp2[10]; vmemptr11[Y] = vtmp2[11]; vmemptr12[Y] = vtmp2[12]; vmemptr13[Y] = vtmp2[13]; vmemptr14[Y] = vtmp2[14]; vmemptr15[Y] = vtmp2[15]; } Y = tmp; } // ~600 cycles x 16 (max) = ~10000 cycles = 5.6ms (out of 16ms per frame). } #endif And the example that's working with vertical mirroring (examples/example_horizontal_scrolling.c) : #include "string.h" #define HORIZONTAL_SCROLLING #define _MS_BOTTOM_SCROLLING_ZONE 1 #define MULTISPRITE_USE_VIDEO_MEMORY #include "sparse_tiling.h" #ifdef MULTISPRITE_USE_VIDEO_MEMORY // Generated from sprites7800 RType_tiles_mirror.yaml #include "example_RType_tiles_mirror.c" // Generated from tiles7800 --sparse RType_tiles_mirror.yaml --varname tilemap_level1 RType_level1_mirror.tmx -m 16 #include "example_RType_level1_mirror.c" #else // Generated from sprites7800 RType_tiles.yaml #include "example_RType_tiles.c" // Generated from tiles7800 --sparse RType_tiles.yaml --varname tilemap_level1 RType_level1.tmx -m 16 #include "example_RType_level1.c" #endif char scroll_background_counter1, scroll_background_counter2; void scroll_background(char speed) { char c; signed char pos1, pos2, pos3, pos4; scroll_background_counter1++; if (scroll_background_counter1 == speed) { scroll_background_counter1 = 0; scroll_background_counter2++; if (scroll_background_counter2 == 16) scroll_background_counter2 = 0; } pos1 = -scroll_background_counter2; pos2 = pos1 + 80; pos3 = pos1 - 8; if (pos3 < -16) pos3 += 16; pos4 = pos3 + 80; if (_ms_buffer) { X = _MS_DLL_ARRAY_SIZE + 1; } else X = 1; _ms_tmpptr = _ms_dls[X]; for (c = 0; c != 3; c++) { // Modify bytes 4 and 8 of the DLL entries (x position of background sprites= _ms_tmpptr[Y = 4] = pos1; _ms_tmpptr[Y = 8] = pos2; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos1; _ms_tmpptr[Y = 8] = pos2; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos3; _ms_tmpptr[Y = 8] = pos4; _ms_tmpptr = _ms_dls[++X]; _ms_tmpptr[Y = 4] = pos3; _ms_tmpptr[Y = 8] = pos4; _ms_tmpptr = _ms_dls[++X]; } } void main() { scroll_background_counter1 = 0; scroll_background_counter2 = 0; multisprite_init(); #ifdef MULTISPRITE_USE_VIDEO_MEMORY sparse_tiling_init_vmem(tilemap_level1_data_ptrs, brown_tiles1); #else sparse_tiling_init(tilemap_level1_data_ptrs); #endif multisprite_set_charbase(brown_tiles1); // Green (background) color *P3C1 = multisprite_color(0xd0); *P3C3 = multisprite_color(0xd1); *P3C2 = multisprite_color(0xd2); // Beige palette *P4C1 = multisprite_color(0x12); *P4C2 = multisprite_color(0x14); *P4C3 = multisprite_color(0x16); // Blue palette *P5C1 = multisprite_color(0x84); // Dark blue *P5C2 = multisprite_color(0x87); // Light blue *P5C3 = multisprite_color(0xac); // Turquoise // Rose palette *P6C1 = multisprite_color(0x34); // Dark Rose *P6C2 = multisprite_color(0x38); // Rose *P6C3 = multisprite_color(0x3c); // Light Rose // Grey palette *P7C1 = 0x04; // Dark gray *P7C2 = 0x08; // Medium gray *P7C3 = 0x0c; // Dark gray // Background display char c, y = 0; for (c = 0; c != 3; c++) { y += 16; multisprite_display_sprite_ex(0, y, background_level1, 20, 3, 0); multisprite_display_sprite_fast(80, y, background_level1, 24, 3); y += 16; multisprite_display_sprite_ex(0, y, background_level1_1, 20, 3, 0); multisprite_display_sprite_fast(80, y, background_level1_1, 24, 3); y += 16; multisprite_display_sprite_ex(-8, y, background_level1, 20, 3, 0); multisprite_display_sprite_fast(72, y, background_level1, 24, 3); y += 16; multisprite_display_sprite_ex(-8, y, background_level1_1, 20, 3, 0); multisprite_display_sprite_fast(72, y, background_level1_1, 24, 3); } // Save it multisprite_save(); sparse_tiling_display(); multisprite_flip(); sparse_tiling_scroll(1); // One pixel offset between the 2 buffers sparse_tiling_display(); multisprite_flip(); do { scroll_background(4); sparse_tiling_scroll(1); // Scroll 1 pixels to the right for this buffer (so 0.5 pixel from frame to frame due to double buffering) multisprite_flip(); } while (1); } @Defender_2600, if you have some little time ahead, please send me some 160B tiles (with the color id, please, like you have done for the dobkeratops). I'll try to fit them in ASAP. And yes, the eyes and mouthes of Dokberatops are also welcome ! Meanwhile, I'll try to integrate some more features to make a R-Type mockup (the boss, explosions, gameover) Btw, attached below are the latest versions of cc7800, sprites7800 and tiles7800 for Windows, as well as the resources I've used to build the scrolling example. example_horizontal_scrolling_vmem_mirroring.mp4 sprites7800.exe tiles7800.exe cc7800-0.2.17-x86_64.msi RType_level1_mirror.tmx RType_tiles_mirror.tsx RType_tiles_mirror.yaml example_horizontal_scrolling_vmem_mirroring.a78 10 Quote Link to comment Share on other sites More sharing options...
+Defender_2600 Posted November 21, 2023 Share Posted November 21, 2023 Hey @bsteux, glad to see your great progress! So, I looked in depth at the graphics of the first two levels in order to better organize my work in perspective and I think that, with the necessary attention, we can obtain a rather impressive result. I'm currently busy with two old 7800 projects but, if you're not in a hurry, I might be able to get you the complete graphics of the first level (sprites, tiles and palettes), progressing step by step. I have almost all the tiles of the first level (85% complete), then we can continue with some sprites (player spaceship, red enemy spaceships, explosions...). The first palette 160B will be completely used for the tiles, the second palette 160B (and 160A *4) for all the sprites, the third palette 160B is already used for the Dobkeratops in the 11 horizontal zones, therefore leaving the first palette 160B (tiles) only for the upper and lower area, for a total of 36 colors + background color. And yes, I saw that you cut some pixels in the lower area of the Dobkeratops so that it could fit into 11 zones (well done!), I made that graphic just as a demonstration, I didn't think anyone would bring it to life. I'll get back to you as soon as I can. 3 1 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 23, 2023 Author Share Posted November 23, 2023 On 11/21/2023 at 9:47 PM, Defender_2600 said: Hey @bsteux, glad to see your great progress! So, I looked in depth at the graphics of the first two levels in order to better organize my work in perspective and I think that, with the necessary attention, we can obtain a rather impressive result. I'm currently busy with two old 7800 projects but, if you're not in a hurry, I might be able to get you the complete graphics of the first level (sprites, tiles and palettes), progressing step by step. I have almost all the tiles of the first level (85% complete), then we can continue with some sprites (player spaceship, red enemy spaceships, explosions...). The first palette 160B will be completely used for the tiles, the second palette 160B (and 160A *4) for all the sprites, the third palette 160B is already used for the Dobkeratops in the 11 horizontal zones, therefore leaving the first palette 160B (tiles) only for the upper and lower area, for a total of 36 colors + background color. And yes, I saw that you cut some pixels in the lower area of the Dobkeratops so that it could fit into 11 zones (well done!), I made that graphic just as a demonstration, I didn't think anyone would bring it to life. I'll get back to you as soon as I can. Great. I'm eager to see the result in action. Just a few notes to guide your work : - Tiles in the same tileset (horizontal set of continuous tiles) must be in the same 4KB zone, i.e. 64 tiles group (due to 6502 8-bit addressing limit). - For the R-Type "Circle Fire" I've already implemented (see the video attached), Red and Blue must be in the same 160A palette, because "Circle Fire" make some big sprites. I've tested it in 160A, and it still works with the Dobkeratops (by removing the background). example_RType_circle_shots.mp4 - I must switch from 160B tiles palettes to 160B dobkeratops palette (and keep the second 12 colors palette for the sprites), as well as switch from Bank1 where I put the tiles to Bank2 where I put the Dobkeratops... The transition is not nice at the moment (I fade out the background and clear the screen...). I need to find a way to display some tiles along with the dobkeratops, but at the moment, I have no idea how to proceed... Not the same bank, not the same palette... This is work in progress. See examples/example_RType2.c on github for the code. 5 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted November 27, 2023 Author Share Posted November 27, 2023 Hi, Some news about the progress on the R-Type mockup (example_RType2.c) : - Finally, I've used the remaining 4Kb of RAM to store the dobkeratops - which was a little stripped down to fit (12kb were already use as video buffer for spare tiling scrolling). The big dobkeratops sprites is moved from bank2 to RAM so that it can now be used from bank0 and 1. - I switch back and forth from tiles 160B palette to dobkeratops 160B palette in the interrupt routine (DLI). This effectively makes a screen with 37 different colors displayed at once (or not far from it)... All this smoothess the transition to the boss, which looks more natural than before. I've also refactored a little bit headers/multisprite.h (now v0.4) to allow for partially off-screen sprites display (at no cost, but only for 16 or less pixels high sprites, not for big sprites). It doesn't work when vertical scrolling is on (sprites have to be in bounds in that case). Attached is a video of the current state, filmed by my 9 years old son Paul (so don't be too critic. It's his first official shooting). My Concerto is back ! It had lost its firmware during holidays... Flashed it with a USB cable, and it's back as new ! See you later. Still have to add explosions and gameover, and also the beam meter... Ah yes, @Defender_2600, would it be possible to fit the tiles in 8kb i.e. 128 8x16 tiles (+ free mirroring) ? I'm afraid it will be complicated to use more memory (I have to put the enemy sprites + the dobkeratops tail animation + the level definition in the same bank... These 8 bits machine are so tight !) example_RType.mp4 example_RType.a78 10 Quote Link to comment Share on other sites More sharing options...
+karri Posted November 27, 2023 Share Posted November 27, 2023 Wow! I am amazed. Really great and smooth. The filming by Paul. I mean.😁 The game demo is also great! 1 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted December 4, 2023 Author Share Posted December 4, 2023 Hi, Here is a new update on R-Type. Still no explosions nor gameover, but I've added some sound ! I've grabbed a song from Atari SAP archive by @em_kay : R-Type Deep Mix, and I've tried to fit it in... Well, it was not so easy... The song is big (6kb), and the RMT player code also (4kb). So I moved all this (along with the sfx) to bank7, in order to relax a little on bank0 (the main bank in cc7800, the one that stays at 0xc000 whatever the weather is). The problem was how to switch from bank1 (which stores R-Type level 1 data and gfx) to bank7 at every frame ? Well, the scoreboard ! I've put all the sound machinery in the scoreboard display DLI, and moved all the scoreboard gfx (the R-Type font and the beam meter) to bank7, so that everything is OK. Then I've used the RIOT timer for the first time to check that I stay long enough in bank7 for the scoreboard to display entirely... Whoo. My bank0 is now available for more code. A glimpse at the DLI function (extract from example_RType2.c) : void interrupt dli() { if (dli_counter != -1) { scoreboard_and_music = 1; if (level_progress_high >= DOBKERATOPS_GETS_IN) { if (dli_counter == 0) { // Switch to dobkeratops palette if (_ms_pal_detected) { *P4C1 = 0x4c; ... *P7C3 = 0x53; // Red (unused) } else { *P4C1 = 0x3c; ... *P7C3 = 0x43; // Red (unused) } *P5C3 = 0x0e; *P6C1 = 0x0a; *P6C2 = 0x04; *P6C3 = 0x02; scoreboard_and_music = 0; dli_counter = 1; } else if (dli_counter == 1) { // Switch to level1 palette if (_ms_pal_detected) { // Beige palette *P4C1 = 0x22; ... *P6C3 = 0x4c; // Light Rose } else { // Beige palette *P4C1 = 0x12; ... *P6C3 = 0x3c; // Light Rose } // Grey palette *P7C1 = 0x04; // Dark gray *P7C2 = 0x08; // Medium gray *P7C3 = 0x0c; // Dark gray scoreboard_and_music = 0; dli_counter = 2; } } if (scoreboard_and_music) { // Set the RIOT timer to 32 do { *TIM64T = 32 + 10; } while (*INTIM != 31 + 10); *CTRL = 0x43; // DMA on, 320A/C mode, One (1) byte characters mode // Play the music if (sfx_to_play) { sfx_schedule(sfx_to_play); sfx_to_play = NULL; } sfx_play(); #ifdef POKEY_MUSIC // This will switch to bank7 pokey_play(); #endif // Wait for the end of timer while (*INTIM >= 10); // We may miss the 0 due to DMA. Take a 10 margin // Go back to the right rombank *ROM_SELECT = rom_bank; dli_counter = -1; } } } Second novelty this week, a new utility in tools7800 : rmt2cc7800, which generates the C code (well, the C array) for a given RMT file (instead of going through assembly). rmt2cc7800 is written in Rust (obviously) and the windows binary is provided in attachment. Third novelty is the support for display lists of different max size depending on the zone, through the _MS_DL_MALLOC macro. For instance, in the R-Type example, _MS_DL_MALLOC is defined as : #define _MS_DL_MALLOC(line) ((line < 8)?64:(line < 13)?128:32) which allocates 64 bytes for DLs in the upper part of the screen, and 128 bytes for the DLs in the lower part of screen (where the dobkeratops tail lies), and 32 for the scoreboard display. This allows to adjust memory usage to the game, as RAM resources are limited. Note that _MS_DL_MALLOC is resolved at compiled time and used to reserve static arrays. Fourth novelty is the support by cc7800 0.2.18 of bank bracketing, i.e. you can specify quickly the destination bank by simple brackets, as in the example : bank7 { #include "sfx.h" #ifdef POKEY_MUSIC #define POKEY_AT_450 #include "rmtplayer.h" #include "RMT_RType.c" #endif } This puts all the sfx and RMT code, as well as the music in bank7. You don't have to specify the bank for each statement. This is non standard C, but frankly I don't care : bankswitching is a the hearth of Atari 7800 development, and it should be quickly setup. No video this time, as my concerto doesn't support Pokey at 0x450 (0x4000 is used for video RAM). The demo is only working in a7800. Listen to this music, it's so cool ! Next time, no way to avoid it: explosions and gameover. Before moving probably back to cc2600 for a while, as it needs its multisprite.h header. R-Type.a78 cc7800-0.2.18-x86_64.msi rmt2cc7800.exe 7 Quote Link to comment Share on other sites More sharing options...
fluxit Posted December 4, 2023 Share Posted December 4, 2023 Wow. Quote Link to comment Share on other sites More sharing options...
+saxmeister Posted December 4, 2023 Share Posted December 4, 2023 This is insane! I love it. Great work... All I have to say is... look at this port of R-Type on the NES for a comparison. I don't think this is an official port, but it appears to be a recent port. With the modern tools we have, this should have looked better. But, @bsteux this should make you feel even better about your work. Quote Link to comment Share on other sites More sharing options...
+bsteux Posted December 18, 2023 Author Share Posted December 18, 2023 On 12/4/2023 at 9:44 PM, saxmeister said: This is insane! I love it. Great work... All I have to say is... look at this port of R-Type on the NES for a comparison. I don't think this is an official port, but it appears to be a recent port. With the modern tools we have, this should have looked better. But, @bsteux this should make you feel even better about your work. Yes, compared to the 1986 NES Gradius port which is quite good, this one is surprisingly very ugly... Should be called R-Puke. Quote Link to comment Share on other sites More sharing options...
+bsteux Posted December 18, 2023 Author Share Posted December 18, 2023 Hi, Here's my last cc7800 post for 2023 : "R-Type Christmas" POC (Proof of Concept). The code for a R-Type proto is quite complete now : you have the enemy spawning, the explosions, the music, the powerup, the boss, the gameover, and a "Merry Christmas" to all AtariAge members ! There's still a lot of room for adding graphics (160B background by @Defender_2600) and code (1.5Kb free in bank 0). Don't hesitate to have a look at the source code on Github and enhance it. For 2024 (after a little bit of cc2600 workout), I plan to add the following features : - Support for TIATracker songs (in parallel to cc2600) - Support for paddles (Revenge of Doh POC ?) - Support for 160A & 160B bitmaps (Qix or Tempest POC ?) - Support for 3d projections (Rotating cube?) - And some documentation for all of this... The Atari 7800 is really a fantastic machine and I've had lot of pleasure writing and sharing this code along this 2023 year. I wish to all of you a Merry Christmas, and see you very soon ! P.S: I've attached the latest version of sprites7800, which had a bug with 320C sprites (the lives on the bottom left of R-Type are 320Csprites... you can't compile the code if you don't use this latest version of sprites7800) example_RType_christmas.mp4 example_RType_christmas.a78 sprites7800.exe 11 4 Quote Link to comment Share on other sites More sharing options...
+saxmeister Posted December 19, 2023 Share Posted December 19, 2023 Wow. Just... wow. This is what I knew the little machine could do. This is beautiful. There are so many great arcade conversions on this system and this one looks better than any other home conversion I've seen until you get to the TG-16/PC Engine. A great present for us all... Thanks! 1 Quote Link to comment Share on other sites More sharing options...
+Stephen Posted December 19, 2023 Share Posted December 19, 2023 9 hours ago, bsteux said: Hi, Here's my last cc7800 post for 2023 : "R-Type Christmas" POC (Proof of Concept). example_RType_christmas.mp4 39.96 MB · 0 downloads example_RType_christmas.a78 128.13 kB · 8 downloads sprites7800.exe 2.6 MB · 0 downloads That's amazing! 1 Quote Link to comment Share on other sites More sharing options...
KrunchyTC Posted December 19, 2023 Share Posted December 19, 2023 13 hours ago, bsteux said: Hi, Here's my last cc7800 post for 2023 : "R-Type Christmas" POC (Proof of Concept). The code for a R-Type proto is quite complete now : you have the enemy spawning, the explosions, the music, the powerup, the boss, the gameover, and a "Merry Christmas" to all AtariAge members ! There's still a lot of room for adding graphics (160B background by @Defender_2600) and code (1.5Kb free in bank 0). Don't hesitate to have a look at the source code on Github and enhance it. For 2024 (after a little bit of cc2600 workout), I plan to add the following features : - Support for TIATracker songs (in parallel to cc2600) - Support for paddles (Revenge of Doh POC ?) - Support for 160A & 160B bitmaps (Qix or Tempest POC ?) - Support for 3d projections (Rotating cube?) - And some documentation for all of this... The Atari 7800 is really a fantastic machine and I've had lot of pleasure writing and sharing this code along this 2023 year. I wish to all of you a Merry Christmas, and see you very soon ! P.S: I've attached the latest version of sprites7800, which had a bug with 320C sprites (the lives on the bottom left of R-Type are 320Csprites... you can't compile the code if you don't use this latest version of sprites7800) example_RType_christmas.mp4 39.96 MB · 0 downloads example_RType_christmas.a78 128.13 kB · 11 downloads sprites7800.exe 2.6 MB · 1 download Oh man, this combined with defender_2600's graphics would be EPIC! 1 1 Quote Link to comment Share on other sites More sharing options...
+karri Posted December 23, 2023 Share Posted December 23, 2023 My adventure gets close to the 32K cart setup. Would it be possible to change the allowed size to 48K? This is closer to the cart designs I have today... I patched the cc7800 compiler for my own needs. But it conflicts with other allocations at $4000 so I won't make a pull request. build.rs.48kdiff 1 Quote Link to comment Share on other sites More sharing options...
+bsteux Posted January 3 Author Share Posted January 3 On 12/23/2023 at 11:44 AM, karri said: My adventure gets close to the 32K cart setup. Would it be possible to change the allowed size to 48K? This is closer to the cart designs I have today... I patched the cc7800 compiler for my own needs. But it conflicts with other allocations at $4000 so I won't make a pull request. build.rs.48kdiff 2.07 kB · 3 downloads OK. I will add 48ko ROM support to the upcoming version of cc7800 (ASAP). Happy new year to all Atarians ! 1 Quote Link to comment Share on other sites More sharing options...
KrunchyTC Posted January 13 Share Posted January 13 (edited) I really think that a full 7800 port of R-Type is sorely needed. R-Type did not get ported to the NES, and was mostly ported to other consoles that were not super popular, like the TG-16. I think a good port of R-Type would give the 7800 some legitimacy. Edited January 13 by KrunchyTC Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.