Jump to content

Andrew Davie

  • Content Count

  • Joined

  • Last visited

  • Days Won


Posts posted by Andrew Davie

  1. I've had a report of one of the new PlusCart shells with a few problems.  Primarily it was not a good fit and needed to be pressed very hard to insert to the VCS. However, this treatment apparently caused the logo (which is a new, welded, design) to separate.  So, that's not good... not good at all.  Please be aware that if your PlusCart shell is faulty in any way, I stand behind the products I produce and will of course offer you a full refund or replacement at my cost.  As to further developments, I'm now inclined to abandon offering any shells, and instead require anyone wanting one to get their own printed. It's just not worth the hassle/stress.

    • Sad 1

  2. On 8/20/2021 at 9:44 AM, vitoco said:

    Hi! I always wanted to mod a Flashback 2 console, but I couldn't get one locally, 'till now! But the console was already modded with a cartridge port, so I missed the fun.


    Anyway, I went directly to the second step: try the PlusCart and the Harmony carts on this console. Both worked, but not very well. Both of them have glitches that corrupt the menu display. ROMs can still be selected and they load as expected, but a couple of the ROMs I tried also got display glitches.


    Might it be just me or is this common for modded FB2?





    The menu displays use very precise timing and knowledge about how a real '2600 TIA chip actually works.

    If the FB2 is not emulating the TIA chip almost exactly, then this is the source of the display glitches/corruption.

    In other words, nearly certainly a FB2 fault, in my opinion.



  3. 12 minutes ago, WicoKid said:

    Well...got the soldering all done and flashed....the card boots and looks for wifi but doesn't find any. When you look under system info the WiFi firmware is just blank...no numbers or symbols at all. The ESP8266 flashes blue once on power then nothing. The SD card part works fine. A second unit assembled does the same thing. Maybe just a bad batch of ESP8266? 



    Can you show some views of your board so that I can check the basics?

  4. I seem to prefer doing demos than completing projects. I like pushing the envelope, not sealing it up and posting the letter.

    Nonetheless, here's a thread where I hope to post some demos of my Chronocolour(TM) Software Sprite System.

    I developed the basic playfield-based software sprites for my "WenHop?" project, but first as a simple test-bed sub-project where I could put some animating playfield/software sprites onscreen.  So, see the attached binary. It is probably overtime on hardware, so best to view in Stella only at this stage.


    My immediate plans are to write a simple tool to convert from an image file to the format used for the software sprites. That should be quick and easy, but I just don't quite feel like doing that right yet. Soon.  Here's the format...


    const unsigned char rocketShipFlame2[] = {
        1,                  // width in BYTES (=8 pix/byte) (MAX =4)
        21,                 // height in SCANLINES (pref multiples of 3 -- TRIPIXs)
        3,0,               // center point (PIXELS) from 0,0 top left

    So, a basic byte array with a simple header


    1) width in bytes (each byte being 8 PF pixels), so maximum sprite width 32 pixels

    2) Height in ChronoColour(TM) scanlines. Note this should be a multiple of 3

    3) pixel x,y offset of centerpoint of sprite.

    4) the shape data


    Sprites are drawn using a simple call...


    void drawBitmap(const unsigned char *bmp, int x, int y, bool relativeToScreen

    The clipping to the screen window is automatic.

    The 'relativeToScreen' allows for scrolling windows into a larger playing area


    The shape data is just a series of byte triplets (ChronoColour) for each line.  Red/Green/Blue or whatever colours you have chosen.

    For shapes > 1 byte wide (i.e., >= 8 pixels) you just define the shapes side by side... for example...


    const unsigned char flagUSAandUSSR[] = {
        X_______ ________
        X_______ X_______
        _XX_____ XXX_____
        X_______ X_______
        X_______ X_______
        _XX_____ XXX_____
        X_______ ________
        ________ ________
        XXX_____ XXX_____
        ________ ________
        ________ ________
        XXX_____ XXX_____
        ________ ________
        ________ ________
        ________ ________


    Believe it or not, that last one looks like two flags - USA flag and USSR flag - side by side.

    Low-resolution ChronoColour(TM) PF graphics are a bit of a black art...



    As the binary shows, the basic draw systems are functional.  This particular version is just drawing both those ships every single frame (i.e., 60Hz) but since starting work on this I have implemented a double-buffered graphics testbed. That is where we have two graphics buffers - one being displayed on the screen, and an invisible one into which we draw stuff. When drawing is finished, we swap the buffers around. That allows you to draw things over more than one frame, and get lots, lots more onto the screen.  I'll get that in soon.  I hope to release the source code and conversion tool rather soon, too.


    So, the ships above - they are actually each more than one sprite. The Flag, for example, is a separate sprite which is drawn after the ship.  This allows parts of the sprites to be animated separately. It's an optional thing -- they are conjoined, so to speak, because they share a common origin point, so all parts of an object are drawn at the same x,y position - they just happen to have centerpoint offsets.


    Now my immediate plans after I've written the aforementioned graphics conversion tool.. is to put in a whole bunch of animation frames, to see how animation look in ChunkyColour :P.  I'm not entirely sure it will work, but I'm keen to give it a go and see. For the first test, I'm thinking of working on some fighter sprites... and I've gone through the basics of creating a few frames from online source sprite sheets just to see how it looks "on paper"...


    One of the neat things about the system is that it automatically creates a "mask", so that each triplet ChronoColour pixel is actually drawn over the top of any background rather than merged with it. Another way of saying this; if a ChronoColour pixel is non-zero (black), then first the background pixel is erased, so that whatever colour the new pixel is (even if one or two scanlines of that pixel are blank)... will be preserved. The upshot is that things are drawn with priority, so the last thing drawn will always be on top of earlier stuff, with no blending. This means, though, that you lose black as a valid colour. On the other hand, the system also allows you to include your own mask, so it's possible to have actually 8 colours plus transparency... at the cost of the extra data to define the mask.


    I'd love to do a fighting game, or maybe a wrestling game with many large wrestlers going at it at once in a scrolling arena.  Something like this....





    Of course on the '2600 the screen would be a small window into all that mayhem.  The frame rate may be a bit low with that much going on :) and we'd only have 7 colours + black.... but with the double-buffered draw it would not flicker.  Thinking about it, though, you only draw stuff that's visible, so it wouldn't be that slow after all.  The system already pre-culls the draw of offscreen stuff, so perhaps that many wrestlers at a relatively high frame rate might work. But first, it's going to be interesting to see how well the really low resolution works for animating people-sprites. A static image isn't going to be the greatest representation, but here's an idea of pixel chunkiness...


    test2.png.9672a7f9320ed74ee5d86b0120c86bea.png test2.png.18a99a234accafe99b9cd3f79649f13f.png


    The actual experience would be halfway between these two examples.  Why halfway?  It's to do with the odd triple-line colouring that ChronoColour uses, and the difficulty of showing it correctly with a simple quick-n-dirty conversion. That was a hand-converted sample.  I took the original, reduced X (only) to 30% (aliased), then downsampled the colours to the preset 8-colour palette (dithered), and then scaled up X (only) to 330%.  That's roughly what the tool would do, but with adaptive numbers.  I imagine some hand-pixel-pushing would make the frames much nicer. Another option I'm thinking about is using a sprite overlay for the head area, of much higher horizontal resolution.  For a later version, maybe, but an interesting idea.


    Well that's it for a start. Basically a system to draw an arbitrary number of sprites into a double-buffered bitmapped buffer with inbuilt pixel shifting, masking, and clipping. In the long run I'd love to see it as a sort of template which could be used by others to write software sprite ChronoColour(TM) games.


    If I find the motivation, I'll post more updates here... and of course hopefully a conversion tool and source code soon.



    • Like 3

  5. 4 hours ago, Bomberman94 said:

    Any plans for a demo for „the real machine“ - maybe a PAL60 version, too? 🤗

    Yes, sometime soon. The planet spinner works on the real thing now. I want to get some gameplay going before releasing a demo for that. But it's really close and although it might jump occasionally because the timing isn't as refined as it needs to be, the fundamentals are working fine on hardware now. I have long-term plans for PAL, but that will be late in the piece.

    • Like 2

  6. 8 hours ago, Albert said:

    Unfortunately, that's not really viable for the quantity of carts I need.  Injection molding is the way to go.  3D printing is good to test designs, though.  :)



    Noted. In any case, shoot me a mailing address and I'll send you a 3D shell so you can evaluate the basic design concepts in actual physical form to assist with your own shell design. I believe the design I have is pretty good.

    • Like 1

  7. I've made a start at tying the player sprite to the background. This is rudimentary stuff right now - in fact all it's doing is checking the character the player is over, and then 1) moving down if it's blank, otherwise moving up.  That's it. In the video all I am doing is moving left or right, and we see very rudimentary falling, and positioning upwards when "in something".  It's doing OK for such a simple start. The scroll is locking nicely to the player position, too. Bonus crash at the end!



    • Like 4

  8. 1 minute ago, rbairos said:

    This means, besides being easier to make and half the price,  it should fit inside a standard combat cartridge without all the modifications the first one needed.

    Maybe consider a 3D-printed cartridge shell?  I'd be happy to assist with the design.

    • Like 1

  9. Here's a new binary (Stella only, for now) with zooming/scanline, and a bunch of planets.

    Use hold-SELECT to switch to next planet.  LEFT/RIGHT to change rotation speed.  UP/DOWN to zoom in and out.



    So, how is it done?


    Well firstly, it uses the engine that I'd previously shown - that one for drawing the Wen Hop game screen with all the flashy character animations and scrolling. It's exactly the same to start with - a "level" is unpacked to a screen buffer which is just a bunch of character #s used to define that screen. Whereas the wenhop playfield was 40 x 24 characters, with a scrolling 10 x 8 (ish) window viewport onto that, for the planets we have a 40 x 12 character area with a scrolling 10 x 12 (invisible) window onto that.  I copy the leftmost 10 character columns to column 30 onwards, so I get a "wrap-around" in terms of when I'm scrolling into that 40 x 12 area.


    So now I have the planet texture unpacked into that 40x12 area ("the board") it behaves exactly as for WenHop itself.  That is, all the objects on the board animate and are processed, participate in collisions etc.  This is why you see dogecoin on some of the planets as they rotate, and the dogecoin are animating. It's just the original system in action.


    So the "texture" is the same as the original WenHop screen that you play on, in technical terms. But in this case, it's hidden and just used as source data. So, based on the X scroll, I first "draw" the texture as a 10x12 window into that 40x12 "board" representing the planet surface. It's a rectangular, flat, scrolling window.


    The next part is wrapping that texture onto the surface of a sphere. So, for this I wrote a small python utiltiy to generate some mathematically correct data.  All it is really doing is rendering a selected subsection of the rectangular texture map and showing it on the screen. That sounds difficult, but the technique is quite straightforward.


    Let's cover verticals, first.  If you think about a sphere, and as you step down each scanline in a sphere, then the textured surface wraps around - of course. But the mapping of the scanline position to the texture pixel is actually quite straightforward. On a line-by-line basis, we know that ALL pixels on the line come from the same horizontal line in the texture map. So the real task here is just to find which horizontal line of the texture we're looking at, for each scanline. It comes down to some simple trigonometry; based on the curve of a sphere we can use pythagoras and trig to figure out the distance down from the top of the texture. That's done for each scanline, and so we effectively now ONLY display those texture lines which each scanline points to. And with that, we how have a horizontal cylinder onscreen.


    Now it gets tricky. But not too much more so. The screen is drawn in two halves (left/right).  Each half is effectively a mirror in terms of the calculations, but with just a different texture offset (mirrored). So we'll look at only the right-side.  Now the earlier scrolling code took the rendered window-into-the-board to create the correct PF0 PF1 PF2 bytes.  In the planet code this is done slightly differently.  Rather than unpacking the potential 6 characters (catering for scroll) that we might see in this half, and copying 20 of those 24 pixels (with scroll 0..3 dropping off some) directly to the PF bytes (taking account of the weird PF format)... instead we only copy SOME of those 20 pixels. Consider the texture itself being a whole screen wide (i.e., 40 pixels for the sphere). We want to wrap this rendered 40 pixels onto the sphere itself, which varies in width from maybe 0 up to 30 or so pixels.  In other words, SOME of those pixels need to be used, and some discarded.


    So, for each scanline I first calculate the width (in screen pixels) of that half of the scanline from the center. I know that I need to map those 20 pixels of the texture into that many pixels in the final screen display, and I'll have wrapped the texture around the sphere - for that line.  Trigonometry comes to the rescue again, and if you consider a single scanline half, but instead of looking head-on, we look top-down at it. We see half a hemisphere, i.e., a 90 degree sweep across the sphere. And we know that the 20 pixels of the texture map evenly onto the curved outer edge of that half-hemisphere.  So if for each pixel onscreen we know where it intersects that curved outer edge, we get a distance (horizontally) into the texture/surface map. We already know the line, from the vertical calcs, so now we are just getting the correct pixel in that line.


    So, the python program aforementioned also creates a table for each scanline which is effectively a mask. It starts from the center, and specifies on/off for 20 bits horizontally. If a bit is ON, then we add a pixel from the texture. If OFF, then we skip that pixel.  And so, we get an effective gradated selection of pixels being dense towards center-screen, and sparser as it gets to the edge.


    Put these two together - first the wrapping to a horizontal cylinder as described, and then on each line of that cylinder wrapping around a vertical cylinder (effectively) we get the combined effect of the both of them together forming a sphere. So, horizontal cylinder x vertical cylinder = sphere, I guess :). Anyway, that's how I did it. The actual calculations on the ARM are simple enough that i can scroll/animate/render the entire planet every single frame.


    The rotation is achieved simply by scrolling over the original board, and letting the draw system draw from a window into the (wrapped) board as described earlier. When the scroll position becomes < 0 then I just flip it to the right edge (30). When it goes past the right edge, I flip it to the left edge (0).  It's seamless because earlier I'd copied the left 10 character columns to the right side.


    To add to the explanation above, the scaling is achieved by first doing all of the above, but then after we've wrapped the texture onto each scanline half we post-process those 20 pixels with a simple linear scale.  Step through the pixels not one-by one, but by a scale factor and generate a new 20 pixels to use instead.  Similarly for the scanline->texture masking. So the joystick now controls a simple scale/multiplier and thus you get your zooming.


    The principle here, in the whole engine basically, is to build layer upon layer of capability and use those to abstract away the hardware and difficult things by writing engines/systems, and then work at a higher level which is simpler and more capable.




    • Like 9

  10. I've possibly taken this as far as I can go now. Here is a short test of scaling. I wanted to see how objects would look at various resolutions. Surprisingly good and recognisable, actually! Rotation/movement is the key to keeping it looking good, I think.





    • Like 6

  11. As you can see from the above, the math is pre-calculated (by a Python tool) and table-driven. I tested to see if the calculations are correct by trying with different sized planets, and as the video shows, all is good. I could install (say) 10 pre-calculated sizes - but since the math is actually quite simple ... maybe I can port it to the game code itself and have realtime calcs instead of pre-calculated.  Of note in this video is how well you can read "MOON" on the small one, even though the resolution is super-low.  In fact, when I slow it down you see what's happening.  We're getting pixel blending and "super-resolution" when I rotate at speed. Nice.



    For those interested, here's the python code that does the precalculations...


    import math
    print("Pixel selection...")
    desiredPlanetRadiusPixels = 20
    textureWidth = 20
    pixelRatio = 6.25
    full180 = desiredPlanetRadiusPixels * 2
    for line in range(3, full180, 3):
        v = line - desiredPlanetRadiusPixels
        offsetText = math.sqrt(desiredPlanetRadiusPixels * desiredPlanetRadiusPixels - (v * v)) / pixelRatio
        offset = int(offsetText + 0.5)
        bits = 0
        for subPix in range(0, offset):
            realAngle = math.degrees(math.asin((subPix + 0.5)/offset))
            texturePix = realAngle * (textureWidth) / 90
            bits |= (1 << (19-int(texturePix + 0.5)))
        print(format(bits, '#032b') + ', //', line)
    print("Line selection...")
    planetHeight = 20 * 24
    halfHeight = planetHeight / 2
    for line in range(3, full180, 3):
        v = line - desiredPlanetRadiusPixels
        angle = math.degrees(math.acos(-v/desiredPlanetRadiusPixels))
        length = int(angle * planetHeight / 180)
        length = int(length / 3) * 3
        row = int(length/24)
        col = (length - row * 24) / 3
        col = int(col) * 3
        length = (row << 5) + col
        print(int(length), ', //', line)


    • Like 3

  12. Here's the complete source code for texture mapping a surface bitmap onto the rotating sphere...


    #include "main.h"
    #include "drawplanet.h"
    #include "defines_from_dasm_for_c.h"
    #include "characterset.h"
    #include "defines.h"
    // duplicate from defines_cdfj.h
    // Raw queue pointers
    extern void* DDR;
    #ifndef RAM
    #define RAM ((unsigned char*)DDR)
    int planetX = 20 << 16;
    int planetY = (_ARENA_SCANLINES << 16) / 3 / 2;
    const int masker[] = {
    0b000000000000100100010000100000, // 3
    0b000000000001001001001000100000, // 6
    0b000000000001010100100100010000, // 9
    0b000000000001010101010010010000, // 12
    0b000000000001101010101010010000, // 15
    0b000000000001101101101001001000, // 18
    0b000000000001101101101001001000, // 21
    0b000000000001110111010101001000, // 24
    0b000000000001110111010101001000, // 27
    0b000000000001111101101101001000, // 30
    0b000000000001111101101101001000, // 33
    0b000000000001111101101101001000, // 36
    0b000000000001111111011010101000, // 39
    0b000000000001111111011010101000, // 42
    0b000000000001111111011010101000, // 45
    0b000000000011110111111010101000, // 48
    0b000000000011110111111010101000, // 51
    0b000000000011110111111010101000, // 54
    0b000000000011110111111010101000, // 57
    0b000000000011110111111010101000, // 60
    0b000000000011111111110110100100, // 63
    0b000000000011111111110110100100, // 66
    0b000000000011111111110110100100, // 69
    0b000000000011111111110110100100, // 72
    0b000000000011111111110110100100, // 75
    0b000000000011111111110110100100, // 78
    0b000000000011111111110110100100, // 81
    0b000000000011111111110110100100, // 84
    0b000000000011111111110110100100, // 87
    0b000000000011111111110110100100, // 90
    0b000000000011111111110110100100, // 93
    0b000000000011111111110110100100, // 96
    0b000000000011111111110110100100, // 99
    0b000000000011111111110110100100, // 102
    0b000000000011111111110110100100, // 105
    0b000000000011110111111010101000, // 108
    0b000000000011110111111010101000, // 111
    0b000000000011110111111010101000, // 114
    0b000000000011110111111010101000, // 117
    0b000000000011110111111010101000, // 120
    0b000000000001111111011010101000, // 123
    0b000000000001111111011010101000, // 126
    0b000000000001111111011010101000, // 129
    0b000000000001111101101101001000, // 132
    0b000000000001111101101101001000, // 135
    0b000000000001111101101101001000, // 138
    0b000000000001110111010101001000, // 141
    0b000000000001110111010101001000, // 144
    0b000000000001101101101001001000, // 147
    0b000000000001101101101001001000, // 150
    0b000000000001101010101010010000, // 153
    0b000000000001010101010010010000, // 156
    0b000000000001010100100100010000, // 159
    0b000000000001001001001000100000, // 162
    0b000000000000100100010000100000, // 165
    const int lineOffset[] = {
    47 , // 3
    73 , // 6
    85 , // 9
    105 , // 12
    114 , // 15
    131 , // 18
    140 , // 21
    149 , // 24
    166 , // 27
    172 , // 30
    178 , // 33
    195 , // 36
    201 , // 39
    207 , // 42
    213 , // 45
    227 , // 48
    233 , // 51
    239 , // 54
    245 , // 57
    259 , // 60
    265 , // 63
    271 , // 66
    274 , // 69
    288 , // 72
    294 , // 75
    300 , // 78
    306 , // 81
    320 , // 84
    323 , // 87
    329 , // 90
    335 , // 93
    341 , // 96
    355 , // 99
    358 , // 102
    364 , // 105
    370 , // 108
    384 , // 111
    390 , // 114
    396 , // 117
    402 , // 120
    416 , // 123
    422 , // 126
    428 , // 129
    434 , // 132
    451 , // 135
    457 , // 138
    463 , // 141
    480 , // 144
    489 , // 147
    498 , // 150
    515 , // 153
    524 , // 156
    544 , // 159
    556 , // 162
    582 , // 165
    int pscrollSpeed = -500;
    void drawPlanet() {
        if (SWCHA & 0x40)
            pscrollSpeed += 100;
        if (SWCHA & 0x80)
            pscrollSpeed -= 100;
        scrollX += pscrollSpeed;
        if (scrollX >= (30 << 16))
            scrollX -= (30 << 16);
        if (scrollX < 0)
            scrollX += 30 << 16; //+= (20 << 16);
        int shift = 4- ((scrollX >> 14 ) & 3);
        int frac = scrollX >> 16;
        unsigned char *xchar;
        const unsigned char *image[6];
        for (int half = 0; half < 10; half += 5) { 
            int base = half ? VIDBUF_PF0_RIGHT : VIDBUF_PF0_LEFT;
            unsigned char *pf0 = RAM + buf[base];
            unsigned char *pf1 = RAM + buf[base + 1];
            unsigned char *pf2 = RAM + buf[base + 2];
            for (int scanline = 0; lineOffset[scanline] >= 0 && scanline * 3 < SCANLINES; scanline++) {
                xchar = RAM + _BOARD + (half + frac) + boardWidth * (lineOffset[scanline] >> 5);
                int charow = lineOffset[scanline] & 31;
                for (int i = 0; i < 6; i++) {
                    unsigned char piece = *xchar++;
                    unsigned int type = CharToType[piece];
                    if (Animate[type])
                        piece = (*Animate[type])[AnimIdx[type].index];
                    image[i] = *charSet[piece] + charow;
                int p2;
                int mask = masker[scanline];
                for (int icc = 0; icc < 3; icc++) {
                    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)) >> shift;
                    p2 = 0;
                    if (!half) {
                        for (int i = 0; i <20; i++)
                            if (mask & (1 << i))
                                p2 = (p2 << 1) | ((p >> (19-i)) & 1);
                    else {
                        for (int i = 0; i < 20; i++)
                            if (mask & (1 << i))
                                p2 = (p2 >> 1) | (((p >> i) & 1) << 19);
                    *pf0++ = BitRev[p2 >> 16];
                    *pf1++ = p2 >> 8;
                    *pf2++ = BitRev[p2 & 0xFF];


    • Like 3

  13. One of the parameters I have is the pixel ratio; as I adjust this, I do get other side-effects. Although I'd spent quite some time trying to get that front curvature just slightly more curved, I had a hard time achieving that.  Well, funny enough - I did a minor tweak to the pixel ratio (from 5.5 to 6) and as an unexpected (and welcome) side effect, I get more curvature on the front face. And it looks pretty good to me.


    I've also added bands of colour - darker at the top, lighter at the equator, darker at the bottom. This works really nicely to emphasise the 3D effect, and now those planets where I have done that look pretty good. The doge star, of course, but also the plain old moon... with a bit of a tone, too. It's all come together nicely.




    ... and... that may be about it for a week or two. I have things to do.




    • Like 2

  14. So, moon with the works, please...


    Most of the basic systems (animation, logic) are still running for the planet display. So, for example, here's a "moon" with a diagonal seam of animating dogecoin, with some added... green stuff



    • Like 2
  • Create New...