Jump to content

Andrew Davie

  • Content Count

  • Joined

  • Last visited

  • Days Won


Posts posted by Andrew Davie

  1. 42 minutes ago, Karl G said:

    None of these really describe what I saw. Watching INTIM in the Stella debugger as I stepped through the code, the value would jump around seemingly randomly with each instruction executed, rather than decrementing every 64 cycles as expected. I've made enough changes to my other code that it no longer triggers even when I take out the "nop". If this is really an unknown issue, I can try to recreate it to see if anyone can tell what's going on for the purpose of documenting the strange behavior.


    I guess it's a testament to Stella that it never occurred to me that it could be a Stella bug, and I'm still guessing it is not.

    If your timer had already run out (i.e., INTIM had previously reached zero), then it would now be counting down in 1-cycle steps. So, for each instruction executed, you would have multiple counts of the timer.  In other words, if it reached 0, it would not count down in 64 cycles, but instead in 1-cycles. Are you sure that this isn't what stella is showing you?

    • Like 1
    • Thanks 1

  2. I got off my lazy hackity hack butt and did the math properly. Now the texture is locked onto the surface of the sphere possibly as good as it's going to get. It's not perfect, but I think it's now close to correct. Given it's a 20-pixel-across object onscreen, it's not doing too bad.  I've spent many hours on this, and now it's time to move on...




    • Like 7

  3. Also keep in mind the behaviour of the timer flag when timer reaches zero. It's rather odd.

    Here is my solution...


                        ldx #0
                        bit TIMINT
                        bmi .zeroTime                   ; already overtime!
                        lda INTIM
                        beq .zeroTime                   ; also time expired
                        bmi .zeroTime                   ; must have been just overtime and now counting down
    .xOSwait            sta WSYNC
                        bit TIMINT                      ; wait for the timer
                        bpl .xOSwait
    .zeroTime           stx TimeLeftOS                  ; x holds the "how much time left"

    Essentially, when the timer value reaches zero (INTIM), then the TIMINT flag is set and from that point onwards the timer (INTIM) counts down at 1-clock intervals (i.e., it counts down at your step value until it reaches 0 and then it counts down at 1-clock steps.  It doesn't stop at zero.).  So the above code is trying to put "how much time left" into a variable (TimeLeftOS).  But you can't do that just by reading INTIM, as that clears any flag indicating that time was zero. So the convolutions are to first see if we are overtime - zero left. Otherwise  if timer is 0 also zero left, otherwise we store that and then wait for timer to expire, and then store and return.

  4. 1 hour ago, littaum said:

    Not sure if it is related, but I ran into a similar strange timing issue (code took 1 extra cycle) while trying to make my game screen stable.  So far the only way I could fix it was to add a "align 256" in an earlier part of the code since it appeared that part of the timing critical code crossed a page boundary. 

    Branching across a page boundary incurs a +1 cycle "penalty".

  5. 11 minutes ago, Propane13 said:

    Hi Andrew,


    Looks great!  This may be a silly question, but it sounded like the game required extra hardware to handle all of the processing power.  Is that hardware support natively available in emulation already?  Just trying to get myself a bit smarter on how the technology works, and the best way to try out the binary.


    Thank you!


    It uses the CDFJ bankswitch scheme that is supported already by emulators such as Stella and Gopher2600.  Gopher2600 is the more accurate in terms of timing, and perhaps in terms of hardware emulation too.  But they are both excellent and suitable for playing/testing CDFJ games. The "extra hardware" is the ARM chip that lives on all CDFJ games/cartridges whose job is to "manage" the bankswitching. But as a bonus, it's possible to also use the ARM when it would otherwise be idle to do a bit of legwork that would otherwise be impossible on the 6507. It's a bit complex to get everything working in harmony, but when you do there are some pretty cool things you can make happen.





    • Like 5

  6. Well, here's the first WenHop binary. All it does at the moment is display the planets, as above.

    Hold SELECT to switch to the next planet. This version is slightly improved on the texture tracking than the video.

    Also, moon is brighter, and speed of rotation faster.  My next goal is to get the surface to track more convincingly as it rotates.




    • Like 4

  7. So, planet display. It's been a difficult bit of work, but I have the basic systems functioning now.

    This is a bit of a hack; I'll smooth out the movement soonish.  But still, pretty cool... if a bit artefact-y.  Husk likes artefacts.

    I have put in three demo "planets"...

    PS: Switch video to 720p;  it's too dim otherwise.



    • Like 8

  8. 8 minutes ago, Tidus79001 said:

    Ccrowd funding might be a an option if you need a bit more cash to work on this (I would be one on your first backers for sure).  If not completed still even what you have would be a great WIP game if for the community to play if you decide to share the ROM at some point here on the forums.

    I think the days of crowd funding have been and gone. Also fraught with danger for everyone. These days I think I'd go with finishing a product first, and then thinking about selling it. But thanks for the suggestion and vote of confidence.

    • Like 3

  9. 1 hour ago, Tidus79001 said:

    @Andrew Davie

    Wow, this game looks amazing!  You certainly have skilled talent making the 2600 do what you are with this game and a very creative to imagine such a game like this within the modest limit of the console.   I have never seen anything like this attempted on a Atari 2600.  How far along is the game and once it is finished are you going to be selling it in the AtariAge store (this game is one game that I am eager to add to my Atari 2600 games library).

    TY. I have a history of not completing stuff, so to be honest I wouldn't hold my breath if I were you. On the other hand, I definitely could use a bit of income so there's an incentive to get this one done. Most of the systems are complete, so finishing would be more a matter of working on the playability, level design, and transition.  There's still a lot of hard work. I have no idea if/how/when I might sell it. AtariAge would be one option. Another would be to just do it all myself in a very small run.  No real plans at this stage. 


    I might point out that "the modest limit of the console" is a bit misguiding, because the ARM processor on the cartridge provides a lot of processing power, memory, and ease-of-programming that makes these sorts of games possible.

  10. So, this video is a bit weird - but what you're looking at here is randomly generated moon textures. These textures will very soon be wrapped around a real-time-generated spinning planet/moon onscreen.  It's the intermission between planets; I hope to have a planet zooming towards you, spinning, shaded, with texture.  To do this, I've had to reorganise memory significantly. Rather than a 40x25 board, I'm now using a dynamically generated memory map/usage based on the board size. The textures generated here are two hemispheres, so one half of the texture (i.e., 40PF pixels) will be visible at any one time. And on top of that I'm planning a shadow/night area, and on top of that ... spinning... and on top of that... animation. Sounds tricky, but give me a week or so.  And because I'm reusing systems (the texture is generated and animated using the standard board draw, for example), it's not so expensive memory-wise.  Memory reorganisation has allowed me to effectively triple-buffer the graphics and still have room for the data streams.  Why triple-buffer?  Well, one to hold the texture and have it drawn/animating as per normal (described above). Then a double-buffer for the planet draw, which will take multiple frames.  I'll be mapping pixel-by-pixel from the texture map onto an effective sphere on the screen. This will take a fair bit of processing - so you'll be looking at one frame while the other is being drawn, and then it flips.  I won't have sprites, so the memory requirement will be 20 x 8 (board representing the texture) + 10 x 8 x 6 = 480 (visible part of texture, post-rendering by normal draw system) + 10 x 8 x 6 = visible screen + 10 x 8 x 6 = screen being rendered = 160 + 480 + 480 + 480 bytes.  So each complete frame of planet draw will be firstly to render the hemisphere texture based on planet rotation (to a buffer the size of the screen).  This gives a texture 40 pixels wide which is then wrapped around a smaller on-screen sphere (=circle but with correct perspective wrap) (of variable size). Once the texture is rendered (and remember it's a normal screen/board, so it can have animation/movement -- you could play the game on this spinning planet, in theory)... so anyway, once texture is rendered, then we are texture-mapping the texture onto the sphere. This is done with some magic math stuff, effectively picking pixels out of the texture and placing them in the right place on the sphere double-buffer buffer that we're using. Once that is finished, we apply a shadow/mask to the buffer to give the shading on the planet. Once that is done, we flip the double-buffer, and display what we've done. And repeat.  Scroll/rotate planet texture.  render texture with animations.... map texture to sphere... flip buffers. As you can see, it's quite complex but I have this vision and I want to see how it looks. 




    • Like 6

  11. 55 minutes ago, cbelcher said:

    Is it ok to update the PlusCart SD from the menu as a WiFI update?  - just making sure it's not pulling the regular PlusCart firmware that might break the SD card...



    No, not at this time -AFAIK. The SD variant is experimental and can only be self-programmed.

    If you update via WiFi, I believe that will install the latest normal version.

  12. This is my bizarre compile-time "bit reverse" table, generated through C #define statements.

    It produces a 256 byte table with each entry having the bits in the binary entry reversed left-right.

    Weird and yet somehow satisfying...


    #define RVS(a) ( \
          ((((a) >> 0) & 1) << 7) \
        | ((((a) >> 1) & 1) << 6) \
        | ((((a) >> 2) & 1) << 5) \
        | ((((a) >> 3) & 1) << 4) \
        | ((((a) >> 4) & 1) << 3) \
        | ((((a) >> 5) & 1) << 2) \
        | ((((a) >> 6) & 1) << 1) \
        | ((((a) >> 7) & 1) << 0) \
    #define P0(a) RVS(a)
    #define P1(a) P0(a), P0(a+1)
    #define P2(a) P1(a), P1(a+2)
    #define P3(a) P2(a), P2(a+4)
    #define P4(a) P3(a), P3(a+8)
    #define P5(a) P4(a), P4(a+16)
    #define P6(a) P5(a), P5(a+32)
    #define P7(a) P6(a), P6(a+64)
    #define P8(a) P7(a), P7(a+128)
    // Want to call RVS(n) for 0-255 values. The weird #defines above aloow a single-call
    // It's effectively a recursive power-of-two call of the base RVS macro
    const unsigned char BitRev[] = {


    • Like 2

  13. 4 minutes ago, cfarl said:

    What is this "cut out"?

    It gives access to the pins on the dev board for installation of a shunt (=bridge).  The shunt is removed for reprogramming manually, and installed when using the cart. The cart has auto-update, so you only need to remove the shunt if you're doing a self-build and for the very first programming/update. So, the small cutout is big enough to push the shunt through onto the underneath pins.

    • Like 1

  14. Well sometimes you can come back to heavily optimised code and see simple and obvious changes that optimise it even more. Such was the case with presenting the draw code above; I don't know why it was so complicated, as a bit of thinking and it's now much cleaner and even faster...


                unsigned char *pf0 = arenas[half] + scanline;
                unsigned char *pf1 = pf0 + _ARENA_SCANLINES;
                unsigned char *pf2 = pf1 + _ARENA_SCANLINES;
                for (int y = 0; scanline < SCANLINES && y < PIECE_DEPTH; y++) {
                    int p =   (*image[0]++ & 0b1111) << 20
                            | (*image[1]++ & 0b1111) << 16
                            | (*image[2]++ & 0b1111) << 12
                            | (*image[3]++ & 0b1111) << 8
                            | (*image[4]++ & 0b1111) << 4
                            | (*image[5]++ & 0b1111);
                    p >>= 4 - shift;
                    if (lcount++ >= 0 ) {
                        *pf0++ = BitRev[(p >> 16) & 0b1111];
                        *pf1++ = p >> 8;
                        *pf2++ = BitRev[p & 0b1111] | (BitRev[(p >> 4) & 0b1111] >> 4);

    A fair improvement, and much more readable/understandable - and speed is important in this particular section of the code. Each optimisation has the benefit of 198 times the optimisation amount because the code inside the loop there happens on every scanline. It's pretty neat, though, for fetching data from a character shape and mangling the bits into PF format and drawing into the video memory.


    • Like 2

  15. Here are some notes on how it all works, from a technical standpoint...


    Firstly, at the most fundamental level is the 6507 kernel.  All this kernel does is "stream" bytes from the ARM and write them to hardware registers. These streams consist of PF0 PF1 PF2 (left), PF0 PF1 PF2 (right), P0, P1, COLUPF, COLUBK.  There's just enough time do do immediate load/store for all these registers on a single scanline, so that's exactly what the kernel does.



    So streams are a CDFJ feature that allow you to load an immediate value, and get back the next entry in a "stream" of data. So there are multiple streams operating here, one for each hardware register. On the ARM/C side of things, then we have vertical "strips" of bytes -- the data that gets sent to the stream requestor.  Each strip is the height of the visible screen - say, 198 scanlines.  So that's effectively our screen video memory - vertical strips of bytes which, once it's all setup, automatically get drawn.


    So now we have a video buffer and have abstracted away the vertical position of sprites, or the playfield pixels.  Or the colours. All I have to do is makes sure that video memory is "drawn" to correctly, and I'll see the result on the screen.


    Next, we have the "board" -- this is a large chunk of memory representing our "character graphics" screen. The board is 40 x 24 characters in size.  That is, 40 characters across, and 24 characters deep. Each "character" is 4 pixels across and 21 scanlines deep.  So, a 40 x 24 board is actually 160 x 504 pixels in size -- and the screen being 198/21 = 9.4... character lines deep, and 40/4 = 10 characters wide.  So "drawing" a scrolling playfield consists of updating the aforementioned video memory "strips" with appropriate data from that large 160 x 504 pixel area.


    The draw is done in real-time, every frame.  It's done on a line-by-line basis, first the left side of a line (PF0, PF1, PF2) and then the right side of a line (again, PF0, PF1, PF2).  So to do a half of a line, we're looking at 20 pixels that we need to fill with correct data. A half-line can overlap 20/4 = 5 characters from the board, so we fetch the 5 characters first and use those to determine what the 20 pixels should be.  Adjusting (shifting) by up to 3 pixels based on the horizontal scroll.  So, firstly we've fetched a character NUMBER from the board. This is an index into the character set (the visible pixels). But first we do a neat trick.


    When we fetch a character from the board, we use that as an index into a TYPE table. For example, CH_DIRT is TYPE_DIRT.  But there are several different character visuals for dirt (CH_DIRT1, CH_DIRT2, etc.) They all have TYPE_DIRT. This has several advantages, most of all when we are doing creature logic, because we can very quickly determine WHAT a character is by just going "CharToType[*this]" and we know what type of creature is o the board.  Anyway, for drawing purposes, we have fetched the character name (shape #) from the board, first thing we do is convert it to a TYPE (as above) and then we look up an Animate table. This table has one entry for each TYPE (DIRT, ROCK, WATER, LAVA, etc.,) which either has zero (no animation), or points to a short animation program/list for that type.  For example, the entry for TYPE_DOGECOIN looks like this...


    const char AnimDogeCoin[] = {


    So, that's a simple "programming language" which is basically saying show the character CH_DOGE for 8 frames, then CH_DOGE_PULSE1 for 6 frames, etc., etc. And at the end that 255 is a "loop" command. So it's a little animation program for the dogecoin that effecively happens automatically for ALL objects of TYPE_DOGE on the screen. I don't have to think about animating; I just put the character in the board, and if it has an animation then it will animate automatically.  Of course ALL objects of that type will have the same frame of animation shown at any time. There are ways around this, but the fundamentals are thus.


    So with that little bit of indirection - we've fetched the character number from the board, converted it to the type of object, then looked up the correct character to display for this frame for that object type... now we can lookup the actual character shape data for the character we should see. This shape data is 4 pixels x 21 scanlines in the ChronoColour(TM) format (that is, triplets of R/G/B) lines, where R/G/B can be replaced with any colour trio that you like. Because COLUPF is written by the kernel on every scanline, you can in theory change the ChronoPixel R/G/B value on every pixel. This is how I get the gradated dirt colouring on the screen - it's just changing one of the R/G/B values.


    Here's an actual character shape definition...

    const unsigned char CHAR_ROCK1[] = {
        X_  _XX_    // 00 < A
        XX  _XX_    // 01   B
        XX  __X_    // 02   C
        __  __X_    // 03 < A
        XX  XX_X    // 04   B
        __  ___X    // 05   C
        _X  ___X    // 06 < A
        XX  XXXX    // 07   B
        __  ____    // 08   C
            ____    // 09 < A
            XXXX    // 10   B
            ____    // 11   C
            ____    // 12 < A
            XXXX    // 13   B
            ____    // 14   C
            __X_    // 15 < A
            _XXX    // 16   B
            ____    // 17   C
            ____    // 18 < A
            ____    // 19   B
            ____    // 20   C

    That rather odd-looking bit with the small block at left top, and larger block in the middle are the character definitions for overview screen (1 bit wide character) and the regular screen (4 pixel wide characters). I have a few macros setup so that I can type in character shapes like this. The comments show the A/B/C triplet lines, and the _ means "0" and "X" means "1" as far as shapes go. There are 2 shapes defined for the overview character definitions because I display either in a checkerboard pattern to make the shapes more distinct from each other when side-by-side.  Each line here resolves to just one byte (and there are still bits free), so it's 21 bytes to define each ICC character shape. I currently have about 130 characters, give or take.


    So after determining which line in the character shape we want (remember, we're drawing into the video buffer strips), then we copy the shape data for the 5 characters we're looking at in the current left/right half of the screen into a character shape "line".  And we shift it based on the scroll (up to 3 pixels, as with 4 or more we'd be looking at the next character ... so only shift up to 3 *inside* this characters shape data we just fetched, and note that it won't overflow.  The appropriate pixels from the character line shape data are then copied into a 20 bit buffer (for speed, one byte per bit) representing the 20 pixels onscreen as we see them (again, left side) in left-to-right order.  Once we have that, we are ready to mangle the bits into the weird PF order. This can be done very quickly, so that's what we do - we want to produce the 3 PF bytes PF0 (mirrored) PF1 PF2 (mirrored) that would be copied to the screen. But remember we're not copying them to the screen, we're just dumping them into the next scanline of the PF strips... which represent our video memory. The kernel will copy them to the screen at the appropriate scanline.  So, here's the code to fetch the 20 bits from our character line shape data and produce the PF register write values...


    unsigned char *pf0 = arenas[half] + scanline;
    for (int y = 0; scanline < SCANLINES && y < PIECE_DEPTH; y++) {
      int p = 0;
      for (int pix = 0, pshift=20; pix < 6; pix++, pshift -= 4)
        p |= ((*(image[pix])++) & 0b1111) << pshift;
      p >>= 4-shift;
      if (lcount >= 0 ) {
        *pf0 = ((BitRev[(p >> 16) & 0b1111]));
        *(pf0 + _ARENA_SCANLINES) = ((((p >> 12) & 0b1111) << 4) | (((p >> 8) & 0b1111)));
        *(pf0 + (_ARENA_SCANLINES << 1)) = ((
          BitRev[p & 0b1111] | (BitRev[(p >> 4) & 0b1111] >> 4)


    Now I acknowledge that this code is incomprehensible and rather nothing like what I just described - but trust me, it is the same process in operation just highly optimised. This code is retrieving character shape data, shifting it, then unpacking the bits and rearranging into PF format. Since the strips of video memory are consecutive in memory, I use "+ARENA_SCANLINES" to index into the next PF register.  So it's writing PF0, PF1, PF2 for the current scanline, based on the graphics content of the characters retrieved from the character shape number retrieved from the animation program for the character type retrieved from the character number retrieved from the board. Whew!


    The upshot of this; a smooth scrolling pixel-precision (vertical/horizontal) playfied using ChronoColour(TM) that can be fully drawn in a single frame and thus give 60Hz smooth playfield scrolling.


    So, this process is running ever frame. But asynchronously to that is the board processor. This packs as much work as it can into spare time -- typically processing many, many board "squares" every frame. It scans the board from top to bottom, left to right... and it either handles each "character" on the board based on the character number (e.g., CH_WATER0), or character type (e.g., CharToType[*this] is TYPE_WATER). There are some times you need the actual character and some times you need the character type. Both are possible/handled, depending on the object and its behaviour.


    There are an awful lot of potential interactions between the player and objects and objects and objects. Rather than code much of this as conditional code, it's all table driven, based on the character type. I have a bitfield table which indicates exactly what sort of interaction is available for each character type. Here's a subset of that table (in reality I have about 50+ types of objects)...


    const int Attribute[] = {
    // index via ObjectType value
    #define _ 0
    #define PSH ATT_PUSH
    #define WTR ATT_WATER
    #define LAV ATT_LAVA
    #define XIT ATT_EXIT
    #define HRD ATT_HARD
    #define ACT ATT_ACTIVE
    #define BNG ATT_EXPLODES
    #define GRB ATT_GRAB
    #define SPC ATT_BLANK
    #define FLY ATT_KILLS
    #define ROL ATT_ROLL
    #define DRP ATT_DRIP
    //                                         e           k
    //                                        s           n               m           e
    //           t               k           i           a               a       e   l
    //          r       y       n           o           l       s       i       l   b    
    //         i       d       a           n           B       e       D       b   a    
    //        D       r       l           k           h   e   d       h       a   d    
    //       d       e       b   r       c           s   v   o       s   k   e   o   s
    //      n   p   y   h   i   e   a   o   t   d   a   i   l   b   a   n   m   l   l   1
    //     u   i   a   s   m   t   v   R   i   r   u   t   p   a   u   a   r   p   l   l
    //    o   r   l   u   e   a   a   o   x   a   q   c   x   r   q   l   e   x   i   o
    //   R   D   P   P   S   W   L   N   E   H   S   A   E   G   S   B   P   E   K   R
    //   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
        RND| _ |PKF| _ | _ |WTR|LAV|QUI| _ | _ | _ | _ | _ | _ | _ |SPC|PER|XPD| _ | _  , // 00 BLANK_PARALLAX
        RND| _ |PKF| _ | _ |WTR|LAV|QUI| _ | _ | _ | _ | _ | _ | _ |SPC|PER|XPD| _ | _  , // 01 BLANK
         _ |DRP| _ | _ | _ |WTR|LAV| _ | _ | _ | _ | _ | _ | _ | _ | _ |PER|XPD| _ | _  , // 02 DIRT
         _ |DRP| _ | _ | _ | _ | _ | _ | _ |HRD| _ | _ |BNG| _ | _ | _ | _ |XPD| _ |ROL , // 03 BRICKWALL  
        RND| _ | _ | _ | _ | _ | _ |QUI| _ | _ | _ |ACT| _ |GRB| _ | _ | _ |XPD| _ | _  , // 09 DOGE  
        RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 10 EXPLODE_SPACE_0  
        RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 15 EXPLODE_DOGE_0
        RND| _ |PKF| _ | _ | _ |LAV| _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER| _ | _ | _  , // 23 DRIP 
        RND| _ |PKF| _ | _ | _ |LAV| _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER| _ | _ | _  , // 24 DRIP_SPLASH
        RND| _ | _ | _ | _ |WTR|LAV| _ | _ | _ | _ | _ | _ | _ | _ | _ |PER|XPD| _ | _  , // 28 RUBBLE
        RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC| _ |XPD| _ | _  , // 35 DOGE_GRAB  
        RND| _ |PKF| _ | _ | _ | _ |QUI| _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 37 DUST 
        RND| _ |PKF| _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 47 LAVA 
        RND| _ |PKF| _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 48 WATER
        RND| _ | _ | _ | _ | _ | _ | _ | _ | _ |SQB|ACT| _ | _ | _ | _ |PER| _ | _ | _  , // 49 EGG  
         _ | _ | _ |PSH| _ | _ | _ | _ | _ |HRD| _ |ACT|BNG| _ | _ | _ | _ |XPD| _ |ROL , // 52 ROCK  
         _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 53 DRILL
         _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 54 DRILLBODY
         _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 55 BELT
         _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 56 BELT2
    //   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
    //   R   D   P   P   S   W   L   N   E   H   S   A   E   G   S   B   P   E   K   R
    //    o   r   l   u   e   a   a   o   x   a   q   c   x   r   q   l   e   x   i   o
    //     u   i   a   s   m   t   v   R   i   r   u   t   p   a   u   a   r   p   l   l
    //      n   p   y   h   i   e   a   o   t   d   a   i   l   b   a   n   m   l   l   1
    //       d       e       b   r       c           s   v   o       s   k   e   o   s
    //        D       r       l           k           h   e   d       h       a   d
    //         i       d       a           n           B       e       D       b   a  
    //          r       y       n           o           l       s       i       l   b  
    //           t               k           i           a               a       e   l
    //                                        s           n               m           e
    //                                         e           k


    Each of the acronym entries is a unique bit value (bit 0 - bit31). So, for example...

         _ | _ | _ |PSH| _ | _ | _ | _ | _ |HRD| _ |ACT|BNG| _ | _ | _ | _ |XPD| _ |ROL , // 52 ROCK  

    The above entry is for TYPE_ROCK. So as I'm processing the board, I can switch the object on CharToType[*this] == TYPE_ROCK, Let's say I wanted to know if I could push the object (it might not be a rock...) so ALL pushable objects have the PSH bit set. So I go... "if (Attribute[CharToType[*this]] & ATT_PUSH)" and that will be non-zero (i.e., true) if the object -whatever it may be- is pushable. Or, if it should explode during an explosion (XPD), or if objects can roll off the top of it (ROL), etc., etc.  It makes for a very nice system that doesn't require much of any specific conditional handling for the basics. It lets me modify and change the behaviour of many objects in the world just by changing the attributes in the table above.


    Finally, "doing stuff" -- this is simply a matter of objects writing character numbers into the board. For example, to grab a coin, all the player's code does is something like -- if the object in the new square is grabbable, then blank the new square.  Of course there's a bit more finesse - animations, scoring, etc., but that's the fundamental. The board is scanned. The object there is processed based on character number and/or type. It checks squares around it (if required) and decides what to do. Move there. Blow up. Grab the object... whatver. This is the object behaviour that gives the game its look/feel. Once the decision is made, then modify the approrpiate surrounding squares on the board and exit. The board scanner will then process the next board character/square. If there's time available. If there isn't time, then the scanner will exit, and the code executes the normal overscan/vertical blank, screen draw (as described above), and then once again continue board scanning from where it left off.  So, the board scan is asynchronous occurring over (say) 10 frames. The draw is every frame. Animation is every frame (automatic) or at 10Hz (programmatic). Movement of objects on the board is only at 10Hz, but the player is a special-case which happens at 60Hz.


    Well that's about all I feel like describing at the moment. It's a very generic system that allows me to switch to new behaviour and visuals very quickly. It is not challenged by lack of memory or processing power at the moment, so i really can see using this system for lots of games/demos. Having an (effective) pixel-resolution (8 colours) bitmapped video buffer opens up a lot of possibilities.






    • Like 4

  16. Improving...




    So a bit of a "stock take", so to speak, and it appears I have about 8K left to make this a complete game.  


    I'm thinking - why not throw in a bit of "lunar lander" here, too... if you have to pilot the spaceship off the planet, why shouldn't you be responsible for landing on the planet, too.  See the above image with a bit of terrain, so to speak. So let's have the start of the level where you're in your ship, coming down to land...  then you jump out and collect doge coins.




    • Like 2

  17. The engine has an "overview" mode. This is a complete view of the whole playing area (not just what the screen shows) implemented as a one pixel per "character" display. In other words, it's challenging to make it look reasonable.  So in this video the overview mode is starting to look OK; most of the elements (but not the spaceship, yet) are shown in the overview... and most (after a bit of familarity) are distinct enough to be able to identify. In theory, you should be able to play the game in overview mode...



    • Like 4

  18. 5 hours ago, ZeroPage Homebrew said:

    Alongside Turbo Arcade and Zeviouz you guys have really ushered in a new era of full screen graphics display for the Atari 2600!

    All ARM-based games, AFAIK. But don't forget Boulder Dash which did full-screen coloured scrolling graphics a decade or two back.

    • Like 2

  19. 1 hour ago, Nathan Strum said:

    Fully appreciated. Until you pointed it out though, I assumed you'd been adding them in editing! 


    This is really spectacular. Playfield graphics rule!


    Here's the thing in slow-mo - noting the real-time calculation of the circular mask, so that gameplay can happen during the wipe/transition...





    • Like 4

  20. So, rocks.  The problem is now that because you can't dig, and you need navigable passages... how are these passages *defined*?  If we allow the rocks to fall, then as soon as a navigable passageway is opened ... the rocks will fall down and close it up.  So, I've been thinking about these things; how to maintain passageways, and how to open up passageways in the first place.  I think I've solved both of these, and found a use for the "alien eggs" in the process. We're going to switch the "eggs" to "soil eating mega-microbes".  You can see them in operation at the start of this video. Basically they eat dirt nearby, but die off pretty quickly. Husk may very well use these microbes as a tool to clear out passageways.  In any case, you'll notice from earlier versions that the rocks clump together in giant conglomerations of rocks. But now, there's quite a bit of behaviour differences; firstly the individual rocks will fall if unsupported. But the conglomerate rocks will be sturdy. How sturdy depends on what's happening; earthquakes and individual rocks can become loose (and you see them shaking before they fall).  Also, conglomerations can just spontaneously start to disintegrate - and I can vary this based on time, or difficulty, etc.  So although the passageways are maintained in esentially the same form, they do slowly disintegrate - but in a way that I consider "fair" and "avoidable" - in that you're not going to have a ton of rocks fall on you unfairly.  In the video you see the mega-microbes at first, clearing out some passageways. Then you see the earthquakes disrupting/destroying the dirt, and also the individual rocks falling as appropriate. Finally, you see the conglomerates slowly disintegrate over time.  I move around to show that there are still clear and distinct passages for most of the entire time.  Pretty happy with this.



    Edit: it's pretty clear Husk is going to need a grappling hook, so let's chalk that in.


    • Like 1
  • Create New...