Jump to content

ggn

Members
  • Content Count

    1,684
  • Joined

  • Last visited

Posts posted by ggn


  1. 30 minutes ago, DrTypo said:

    Now this is evil :) 

    I had a PowerPC machine a long time ago but I didn't do much assembly with it, mostly C++.

     

    @ggn Thank you for looking into this. Actually I did use the VS debugger :D. But the various arrays (kwtab, kwaccept, kwcheck) confuse me.

     

    Oh, that's mr Dyer's magic hash tables :). Those are generated using the supporting build tools like kwgen, 68kgen etc. and I too haven't looked too deep in them. My mind just goes "oh, that's clever hash stuff, let's breakpoint past that" :D.


  2. Hi,

    31 minutes ago, 42bs said:

    Or you may add support for an optional ":" after symbols to differ them from reserved words.

     

    IIRC the ":" is handled as a separate token, so by the time the parser kicks in (which would probably be the appropriate place to check for this) the damage has already been done. i.e. the "x" is classified as a register. However, since we can pretty much exclude that a valid instruction starts with a register name I think it's simpler to just add that small check to the tokenizer.

     

    9 minutes ago, cubanismo said:

    FYI, @ggn, this is what I ran into and reported here http://jlhconsulting.gotdns.com/bugs/show_bug.cgi?id=159, though I like your idea for a fix a lot better than the patch I proposed.

    Yeah I was wondering who submitted bug reports/fixes to the tracker and wasn't me :). Thanks very much for taking the time to do all this work. I'll check the reports you added in detail after work and will comment on them :).

     

    Oh, and welcome to rmac!


  3. 12 hours ago, DrTypo said:

    It seems I've found a bug in the way rmac handles registers. It looks like rmac doesn't care about the "r" in the register name.

    "r15" and "15" are the same.

    For example, rmac doesn't complain in the following cases:

    add 7,1   => assembled to add r7,r1

    store 5,(2) => assembled to store r5,(r2)

    addq #r31,3 => assembled to addq #31,r3

    movei #r8,2 => assembled to movei #8,r2

     

    I'm looking at the rmac source code but this whole tokenizer/parser thing is a bit black magic for me :)

     

     

    Interesting, thanks for the report, I'll take a look at this as I personally haven't given the Jaguar side of rmac a lot of testing.

     

    The tokenizer is easy, it just scans for bits of text inside the line and tries to match them to existing keywords or registers, and then creates tokens for them ;). But, I see your point, it's not easy to dig into the specifics by just browsing the code. I usually step through that thing using a debugger attached.

    12 hours ago, JagMod said:

    If you're looking at the parser...

     

    This does not work:

    x        REGEQU    r1
    y        REGEQU    r2
    z        REGEQU    r3

     

    Error: syntax error; expected symbol

     

     

    however, this does work:

    xx        REGEQU    r1
    yy        REGEQU    r2
    zz        REGEQU    r3

     

    This is a side effect of the tokenizer matching x and y as registers (the 6502 and 56001 CPUs have those). It's a known issue but it hasn't bothered us too much to look into yet. A quick look into the tokeniser shows

    			// If the symbol is small, check to see if it's really the name of
    			// a register.

    So if we add a small check to omit this step in the first line token it should be fixed. Stay tuned!

     

     

    Thanks to everyone for the feedback so far!

     


  4. Just to clarify: Braid has a level where in order to get an optional puzzle piece hidden in plain sight you literally had to wait for 1 hour in one specific point and then rewind the whole thing. So it was a joke aimed at if @ilmenit was going to do a 1:1 port of Braid. I think it'd be better to use the various game mechanics without copying the levels 1:1. There's a lot of space to be explored in those mechanics I think :).

    • Like 1

  5. 6 minutes ago, ilmenit said:

    Thanks! I was thinking more about "Braid" by Jonathan Blow next but ""Stephen's sausage roll" looks interesting too ;-) We will see, but first things first, lets release this game first.

    But do you have enough RAM to store 1 hour long of undo for braid? ;)

     

    (BTW Mr Blow himself hails Stephen's sausage roll as the best puzzle game ever!)


  6. On 4/2/2020 at 8:42 PM, abbotkinneydude said:

    For instance, the code of Desert Falcon (ATARI 7800 version; development name: SPHINX) is now on GitHub but it's broken down into several sub-programs, how do you merge all that stuff?

     

    So if I understand correctly you ask how to assemble all files into one? I'd expect that either one of the files includes the rest or you'd have to create such a file yourself which just includes all the sources.

     

    As for git, I'm using it, as well as other source control management systems. It mostly doesn't make much sense when you're working by yourself in projects, the overhead is a bit more than other programs like subversion. But that's mostly down to the individual. Just try it out and see how it works out for you, don't take our words for granted :)


  7. I don't have much to contribute to the discussion other than to say that "Baba is you" is one of my favorite games of 2019, and @ilmenit and the rest of the team has my deepest respect for making an a8 version :)

     

    Last year the Atari 800 had a Snakebird inspired game, now Baba is you... I'm now half expecting someone to announce a "Stephen's sausage roll" port and "The witness" and the Atari 800 will have 4 of the best puzzle games ever! What grand times we live in :)

    • Like 1

  8. All right, I've got some rest and can probably make more sense than last night, so let's dive straight in.

     

    Computers have no idea what languages are

     

    What the title means is that when you ask a computer to print a message on screen, what really happens is that you call a routine, point it to a series of bytes and a series of images. Then the print routine reads each byte value, takes the corresponding images and draws them to the screen.

     

    Here is the default font used in raptor:

     

    image.thumb.png.1a82fadd91b288767b72d4a4fd2c5dc0.png

     

    The leftmost character, space, corresponds to ASCII value 32 and the rightmost character 'Δ' corresponds to 127. So there. That's all the characters we have at our disposal. Raptor doesn't support any ASCII values bigger than 127 or smaller than 32.

     

    (Small grumble there: this is one of the times where using a closed source library bites us. It would have been fairly simple to extend the limit at least up to 250 or so, rebuild the library and ship it. But it is what it is)

     

    However, we're not out of options! If we absolutely must keep the upper and lower case letters and numbers, that leaves us with quite a few glyphs we probably won't use. So we can replace them with others. Hooray!

    image.thumb.png.145b5a7bc1e45358b6339d08a14f8efd.png

     

    So, for example, to print a "é" character with this modified font we will enter "#" in our string. So for example, to print "gagnée" we would type "gagn#e".

     

    This is, of course, really painful because now one has to remember where all the funny symbols and use the translated characters accordingly. Can we do something better?

     

    The tragic tale of character encodings

     

    Actually this is a very long story so I'll cut to the chase. Interested people can probably look this up for a more accurate version.

     

    So when computers were younger, memory was at a premium, and computer standarisation was still in its infancy, people only used a byte per character as we said. But with so many languages and special glyphs, there's only so many you can cram in 128 slots. (Generally computers had up to 256 characters but the lower 128 was usually well defined by the ASCII standard. So people were left with 128 free).

     

    Because there was no centralised standard for non-English characters each company that imported their computer to a country pretty much rolled their own version for each language. For example Greek was a nightmare: one simply couldn't copy a text in Greek (for example) from an Atari to an Amstrad, the text would come out pretty much garbled at the other end.

     

    So, fast forward a couple of decades and these problems were solved with standards like UTF-8. But at a small cost: the extended characters are not a byte each any more.

     

    Let's check out a modern editor that a lot of people use on Windows: Notepad++. Suppose we try to enter the following string into our rb+ source: "êâîôûù". By default this is what we'll see:

     

    image.png.d0684b4e9cde2484c7e811c374248355.png

     

    That.... didn't go as expected. Why is that?

     

    image.png.32e62b48b1c04e75cd4ed92e94000812.png

     

    Oh, it's set to the old ANSI mode, i.e. 1 byte per character. But there's an option to convert to UTF-8. Let's try it out:

     

    image.png.69eeba30991dfe5c36e80815dd3492fd.png

     

    Nice! Let's save out source and run it, right? Except nope, because once we enable UTF-8 this is what actually gets written to the file:

     

    image.thumb.png.1839c2e00e0cea4f6b0f17942f2001b7.png

     

    Hmm, for each accented character 2 bytes are written to our file. And each one starts with hex $c3. Which is 195 in decimal. Which is over 128. Feed that into raptor and it'll go boom! So, is that another dead end?

     

    I'm going through changes

     

    Well, what if we were to convert the multi-byte characters to our desired glyphs we know before printing the string?

    function convert_utf8$(string$)
        local c as UBYTE
        local d as UBYTE
        local i as short
        local j as short
        local converted$
        j=0
        
        for i=0 to len(string$)
            c=peek(((int)string$)+i)
            if c=0xc3 then
                d=peek(((int)string$)+i+1)
                select case d
                    case 0xaa 'ê
                        c=41
                    case 0xa2 'â
                        c=36
                    case 0xae 'î
                        c=60
                    case 0xb4 'ô
                        c=43
                    case 0xbb 'û
                        c=125
                    case 0xb9 'ù
                        c=126
                    case else
                        'We don't know this character. Just turn it into A?
                        c=65
                end select
                i++     'move past $c3
            endif
            poke strptr(converted$)+j,c
            j++
        next i
        poke (strptr(converted$)+j),0
        function=converted$
    end function

    This is a very dumb routine that goes over each byte of a string. If the character is not a multi-byte one (i.e. doesn't start with $c3) then we're fine. But if a $c3 is encountered then it checks to see which character it is and converts it to the value we know is correct. So "ê" is 41 (which is the ")" character originally, but repurposed). Write that into a new string, pass it back to the caller, done.

     

    So before we print our string with accented characters we call convert_utf8 and get the proper string back. In the updated localisation example we can see the following line that demonstrates its use:

    rprint convert_utf8("Special characters can work... êâîôûù")
    

    And that's all there's to it.

     

    Closing words

     

    There's still some work to be done here as not all the characters are handled in that routine above. Firstly, one will have to decide where to place the characters that have to be converted (I just did it at random and pretty much sacrificed all punctuation). Then, all the UTF-8 characters have to be identified in a hex editor and then entered into the subroutine, as well as the corresponding single byte characters they will get mapped to. But it really is not very difficult, and the results justify it in my opinion: especially if there's a lot of text to be written, this will greatly simplify the writing.

     

    One final trick here: if you absolutely need both the original font and the localised, you can keep the first in the upper characters (white in the font bitmap) and place the accented font in the lower characters (red). The colours are of course user defined as well, so you can make them all white, red, or whatever you wish. Use basic_r_indx to switch fonts before printring.

     

    That's all, have fun!

    • Like 2

  9. Seems like yesterday's post sprang more questions to my friend who is too shy to post on the forum so he sends me private messages! Don't be shy folks, ask away publicly instead of PM, nobody will judge.

     

    Anyway, the question was how would one tackle localisation in a game. I'll cover the string printing part here, but if your project has the text drawn as graphics you could also pick up a couple of ideas as well.

     

    Easy (dumb) mode

    The first obvious thing to do is to have a variable that holds which language we're using.

    ' Global variable which will hold the language
    dim language%
    

    Now, we could be very dumb about this and just remember in our head which language is which number and move on. So, something like

    ' 0=English, 1=French, 2=Italian

    And when it's time to print a string we can do something equally simple like

    if language=0 then
        print "(Our message in English)"
    elseif language=1 then
        print "(Our message in French)"
     elseif language=2 then
        print"(Our message in Italian")
    endif

    And this is really ok. It'll work nicely if you only have 3-4 messages in the game and you don't need to worry about it.

     

    Let's get organised

     

    But say you suddenly have 30 text strings in your game instead of 3. Now you have to go to each place you print messages and change or add the "if" statements. Or wait for your translator to translate all the strings, and hope they find all the places that need to change.

     

    And then, you decide to add a fourth language - argh! Well ok, go back, change all ifs, bish bash bosh. And then you decide that the second language is to be removed - double argh!

     

    Do this a few times, tinkering things around the source and you're going to be in a bad place mentally and won't feel like doing it at all.

     

    So let's try to leverage some of the power high level languages provide us in order to organise things better!

     

    Enumerations

     

    As a first step, let's get rid of the hardcoded values for languages. We could do something like:

    const English=0
    const French=1
    const Italian=2
    

    Again, perfectly fine for most cases, but we can also use an enum for that:

    enum
        English
        French
        Italian
    end enum

    This is pretty much equivalent to the above, but does the numbering automatically starting from zero. So if you for some reason choose to insert German language between French and Italian, then the enum will equate German to 2 and Italian to 3. Not a biggie, but it's one thing less off your mind!

     

    Dimensioning strings

     

    Now let's try to get rid of all that if/endif block. We do this by using our old friend, DIM:

    dim message_pressfire$[3,40] as char
    message_pressfire$[English]="Press fire to start              "
    message_pressfire$[French] ="Appuyez sur le feu pour commencer"
    message_pressfire$[Italian]="Premi il fuoco per iniziare      "
    

    What we did here is to tell the compiler to create an array of strings. We have 3 languages, we know that each message is less than 40 characters, so [3,40] it is.

     

    One small detail to notice is the as char at the end of that line. This actually tells the compiler to not use the default string size that DIM uses, which is 2048. So if we actually wrote "dim message_pressfire$[3]" it would still create an array of 3 strings, but each would be 2048 bytes. This wastes a lot of RAM as you understand!

     

    Finally, we can replace that if/endif block with

    PRINT message_pressfire$[language]
    

    language of course must be initialised first to one of the values from our enum, for example "language=English".

     

    Entering the multiverse

     

    The above isn't too bad and can again suit your needs. But even this can get messy since you have to have multiple named arrays, remember their names. Depending on your programming style it can be a problem.

     

    So, let's throw more dimensions and MOAR enums at the problem!

     

    First of all, let's enum all the messages we're going to use:

    enum                ' define some constants for our numbers instead of using 0,1,2,3,4
        msg_hi=0
        msg_bye
        msg_win
        msg_lose
        msg_ready
        num_messages    ' This must be at the end!
    end enum
    

    num_messages will always auto update to hold the actual number of messages. So if you add 3 more messages it'll be equal to 8!

     

    So let's now DIM an array that will hold all our messages for all languages:

    dim messages$[3,5,48] as char

    So compared to our DIM above this has added a third dimension that will hold all our messages.

     

    So let's populate the array (localisation provided by Google translate so it's probably terrible!):

    messages$[English][msg_hi]="Hi   "
    messages$[French] [msg_hi]="Salut"
    messages$[Italian][msg_hi]="Ciao "
    messages$[English][msg_bye]="Bye      "
    messages$[French] [msg_bye]="Au revoir"
    messages$[Italian][msg_bye]="Addio    "
    messages$[English][msg_win]="You win!    "
    messages$[French] [msg_win]="Vous gagnez!"
    messages$[Italian][msg_win]="Hai vinto!  "
    messages$[English][msg_lose]="You lose   "
    messages$[French] [msg_lose]="Tu as perdu"
    messages$[Italian][msg_lose]="Hai presso "
    messages$[English][msg_ready]="Get ready"
    messages$[French] [msg_ready]="Sois pret"
    messages$[Italian][msg_ready]="Preparati"
    

    A few small details here:

    • The "48" is hardcoded as the maximum string length for all the messages. Take care not to overshoot this or you might overwrite other things in memory!
    • Each message is padded with spaces to match the longest language for each message. This is not necessary, but a good practice: if you switch languages and want to re-print the new language's messages on top of the old ones, you might get some of the old message leftovers.
    • I didn't use num_messages instead of "5" above because of some limitation with BCX. I couldn't find a way to overcome it for now, but if anyone has an issue with this in practice we can certainly revisit the issue.

    So if we want to print out our "lose" message in Italian, it's as easy as typing

    print message$[Italian][msg_lose]

    Or, hey, let's print all the messages and have a language selection!

    do
        local i as short
    
        vsync
        ZEROPAD()
        if zero_left_pad band Input_Pad_C then
            language=English
        elseif zero_left_pad band Input_Pad_B then
            language=French
        elseif zero_left_pad band Input_Pad_A then
            language=Italian
        endif
    
        for i=0 to num_messages
            rlocate 0,32+8*i
            rprint messages$[language][i]
        next i
    loop

    So this will change all the printed messages as you press A,B,C buttons. Not too bad, right? Try doing that with if/endif blocks, see how fast the source will clutter! (of course this isn't a really practical example per se, with a little more code it could be modified to print various strings at various positions on screen)

     

    Closing time!

     

    Hopefully this helps people out. As usual, the code is up on Github and you can build it yourself by downloading/cloning/pulling the latest repostitory and building project "localisation". Study the source, use it at will, come back with questions if you have them. But have fun regardless!

    • Like 2

  10. So a friend sent me a private message asking me questions about the three topics in the subject. Instead of answering him in private I thought I'd just make a post about it so other people can also learn a couple of things!

     

    The message described a map of 16x16 pixel tiles forming a 16 by 8 tile grid. So, 288x160 pixels.

     

    As usual, the code and assets is up on github and you can download it using the usual ways.

     

    (Standard disclaimer: if the rest of this post seems alien to you then please go and read the pinned tutorials in this subforum. Hopefully things will become more clear then. If you're still confused, drop a line below!)

     

    Firstly, I needed some tile assets and instead of drawing my own hideous things and get depressed I just went into opengameart.org and grabbed a random 16x16 tileset. Originally it had way too many colours so I decreased them to 256 and saved the result as a 8-bit BMP file. I could have gone down to 16 but I didn't want to degrade the quality too much. Also I extracted a 16x16 tile to be used as our main sprite. Because I obviously ran out of paletted colours since they were spent in the background I just made the sprite 16-bit on the grounds that a) this is a demonstration, b) it shouldn't burn too much bandwidth anyway.

     

    Then I created a new project from the console

     

    build gridsprite new
      ___           _             ___          _      _
     | _ \__ _ _ __| |_ ___ _ _  | _ ) __ _ __(_)__ _| |_
     |   / _` | '_ \  _/ _ \ '_| | _ \/ _` (_-< / _|_   _|
     |_|_\__,_| .__/\__\___/_|   |___/\__,_/__/_\__| |_|
              |_|
    C:\svn\raptor\RB_~1\include\template\assets.txt
    C:\svn\raptor\RB_~1\include\template\rapapp.s
    C:\svn\raptor\RB_~1\include\template\rapinit.s
    C:\svn\raptor\RB_~1\include\template\rapu235.s
    C:\svn\raptor\RB_~1\include\template\template.bas
    C:\svn\raptor\RB_~1\include\template\ASSETS\partipal.bmp
    C:\svn\raptor\RB_~1\include\template\ASSETS\fonts\f_16x16.bmp
    C:\svn\raptor\RB_~1\include\template\ASSETS\fonts\f_8x16.bmp
    C:\svn\raptor\RB_~1\include\template\ASSETS\fonts\f_8x8.bmp
    9 File(s) copied
    Project gridsprite created successfully!

    Great! Let's start butchering it up. First stop is rapapp.s where we change sound engine to Zerosquare's:

     

    player equ 0      ;0=Zerosquare's player, 1=U-235 player

    Next stop: assets.txt - we need to tell the system to import our graphics:

     

    abs,tiles,gfx_clut,ASSETS\GFX\hyptosis_tile-art-batch-1.bmp
    abs,scrbuf,gfx_noclut,ASSETS\GFX\playfield.bmp
    abs,sprite,gfx_noclut16,ASSETS\GFX\sprite.bmp

    First is our huge spritesheet, with palette export. Then comes a placeholder image that will serve as our screen buffer (where we'll draw the tiles). This could have been blank but sometimes it helps having a non-blank image (for debugging). Lastly, our small 16bit sprite.

     

    Now we need to define our graphics objects for raptor, which means editing the dreaded rapinit.s file. Ugh! So we need to add two objects to the existing file, one will display the screen buffer and the other will display the sprite. We do that by copying the template (the block of text which is commented out) and adjusting only, a few parameters, namely the sprite_gfxbase field to point to the addresses of our graphics (scrbuf and sprite from above), the BIT DEPTH (8 and 16 respectively), sprite_bytewid (288 and 16*2 respectively), sprite_width (288/16), sprite_height (160/16), sprite_gwidth (288/16). The other stuff, don't know, don't care, leave as is, it's "fine"!

     

    After the unpleasant stuff we're now ready to start codin' - woohoo!

     

    Drawing the tiles

    First of all, the tile map. This was mostly reused code from another project (drawmap). It has been however modified for the parameters of our problem here and the code slightly modified to be more readable (and faster!). The original post for drawmap is here: https://atariage.com/forums/topic/263485-rb-manic-miner/?do=findComment&comment=3724346

     

    To begin, we need an array to store our grid tiles. This will hold numeric values.

    dim map[map_height][map_width] as short

    Notice how we need to define height*width. In most cases it won't hurt to define that the other way, but if we then would like to fill in the indices as a huge stream of values it can get quite weird. Just trust me on this one, I don't like it either as I'm used to think of width*height when defining arrays :).

     

    The routine also needs a few definitions:

     

    ' The screen to draw the map's width in bytes.
    ' Think of this as <width in pixels>*<bits per pixel>/8
    const dest_screen_width_in_bytes=288
    ' Our tile's height
    const tile_height=16
    ' Our tile's width in bytes.
    ' Think of this as <width in pixels>*<bits per pixel>/8
    const tile_width_in_bytes=16
    ' The screen to read the tile's width in bytes.
    ' Think of this as <width in pixels>*<bits per pixel>/8
    const src_screen_width_in_bytes=960
    ' Map width inside the map array
    const map_width=18
    ' Map height inside the map array
    const map_height=10
    
    dim x as short
    dim y as short
    dim c as short
    dim tilex as short
    dim tiley as short
    

    These are really definitions coming from our map size as a grid, our spritesheet dimensions, our tile dimensions and our screen buffer dimensions. I think it's pretty straightforward. (by all means, shout if something is not clear!)

     

    To populate the map I didn't want to spend ages drawing an actual map and exporting the values, so I opted to draw the tiles in the order they appear on the spritesheet. So we fill our map using a simple loop.

     

    ' Fill map with some tiles
    for y=0 to map_height-1
        for x=0 to map_width-1
            map[y][x]=60*y+x
        next x
    next y

     

    There we go, our map is now ready to be displayed. This could be filled using proper values from a tile editor but this is out of scope for this post. (Perhaps in another I'll get something like Tiled to export maps.) This loop will display the map to our screen buffer:

     

    ' Draw map
    for y=0 to map_height-1
        for x=0 to map_width-1
            c=map[y][x]
            tilex=(c % (src_screen_width_in_bytes/tile_width_in_bytes))
            tiley=(c/(src_screen_width_in_bytes/tile_width_in_bytes))
            drawtile(x,y)
        next x
    next y

    Easy, just loop through all possible x and y values for width and height, find the coordinates of the tile from our spritesheet and the screen coordinates the tile will be shown and tell drawtile to go draw the thing!

     

    So the only thing missing is drawtile routine. Brace yourselves:

    ' Draws a 16 x map_height tile on screen.
    ' x is multiplied by map_width and y is multiplied by map_height
    
    sub drawtile(x as SHORT, y as SHORT)
        local i as short
        local screen_y_offset as LONG
        local screen_x_offset as LONG
        local screen_address as LONG
    
        local tile_y_offset as LONG
        local tile_x_offset as LONG
        local tile_address as LONG
    
        tile_y_offset=tiley*(src_screen_width_in_bytes*tile_height)
        tile_x_offset=tilex*tile_width_in_bytes
        tile_address=(LONG)strptr(tiles)+tile_x_offset+tile_y_offset
    
        screen_y_offset=y*(dest_screen_width_in_bytes*tile_height)
        screen_x_offset=x*tile_width_in_bytes
        screen_address=(LONG)strptr(scrbuf)+screen_x_offset+screen_y_offset
    
        for i=0 to tile_height-1
            ' Our tiles are 16 pixels wide in 8bpp mode, which means they are 16 bytes wide in RAM. Hence we need 4 LPOKEs per line.
            lpoke screen_address,lpeek(tile_address)
            lpoke screen_address+4,lpeek(tile_address+4)
            lpoke screen_address+8,lpeek(tile_address+8)
            lpoke screen_address+12,lpeek(tile_address+12)
            screen_address+=dest_screen_width_in_bytes
            tile_address+=src_screen_width_in_bytes
        next i
    end sub

    Just think of it as something that takes the values we filled above and turns it into pixels on screen. The only hardcoded bit here is that it assumes that the tile width is 16 pixels and we're using 8bpp mode, otherwise it's quite generic. Again, if people want me to remove that restriction, ask!


    Randomness

     

    This has been discussed in various threads on this subforum and people have been reporting various degrees of success, so let's clear the air a bit with some theory and code. First of all, there is no easy way to get pure randomness out of deterministic systems like the Jaguar - boot time is pretty much the same, there's no easy register to sample and get something random, and even then who knows how "random" it will be. So let's throw that problem to the user!

     

    ' Initialise our random values
    ' The trick here is to keep re-runninf randomize with a running counter
    ' and wait till the user presses something. So in essence the user is generating
    ' the randomness
    RLOCATE 0,180
    RPRINT "Press any button to begin countdown"
    do
        randomize(c)
        c+=539
        ZEROPAD()
    loop until zero_left_pad BAND (~(Input_Pad_C1|Input_Pad_C2|Input_Pad_C3))

    What does this dumb loop do? Very simple, it just waits for someone to press a button on the joypad.

     

    ....of course while doing that it keeps a running counter which is used to change the random function's initial seed :). Unless we're dealing with a person in possession of extraordinary abilities, there's probably no way they can hit the button at the exact moment twice. And even then our counter will not start from 0 each time this is called. So let's say we have a pretty good random seed going on here!

     

    Finding our coordinates

    Now we want to know if we have a character running around the screen on top of the tile map where they are. Remember, our sprite's coordinates are from 0 to 288-16 for x and 0 to 160-16 for y, but our map is 16x10. How can we convert from one system to another?

     

    do
        vsync
        ZEROPAD()
        if (zero_left_pad band Input_Pad_Up) and sprite_y>0 then
            sprite_y--
        endif
        if (zero_left_pad band Input_Pad_Down) and sprite_y<160-16 then
            sprite_y++
        endif
        if (zero_left_pad band Input_Pad_Left) and sprite_x>0 then
            sprite_x--
        endif
        if (zero_left_pad band Input_Pad_Right) and sprite_x<288-16 then
            sprite_x++
        endif
        rlist[2].x=(16+sprite_x)<<16
        rlist[2].y=(16+sprite_y)<<16
        RLOCATE 0,192
        print "x=";sprite_x;", y=";sprite_y;", map x=";int((sprite_x+8)/16);", map y=";int((sprite_y+8)/16)
        RLOCATE 0,180
    loop

    This is a sort of main loop. We check the joypad for up/down/left/right and update the coordinates of the sprite (rlist[2] means the second raptor object, which we defined as our sprite). The print statement then converts the sprite_x and sprite_y coordinates into grid coordinates simply by dividing by the grid width and height respectively (which is conveniently 16 in both cases). If we do that calculation we will notice something strange. Here is a screenshot from the binary which has our character (the heart if you haven't figured it out!) within a grid square. Let's say that the grid coordinates are x=3, y=2.

     

    image.png.4e873ae21b33c7851bdaf1e63a79b720.png

    There is no problem here, when we do the divisions described above we will get our x=3,y=2 nicely. Now let's consider another example:

    image.png.25b21e5ad34fece39dcc504527aacdb2.png

    So now we're just passed right of the x=3, y=2 grid. So then we'd want our coordinates to be x=4, y=3. The thing is - we'll still get x=3, y=2! Why this is happening? Well, let's have a look at the sprite itself:

    image.png.b629a0c6a293c10306c45688ef64271c.png

    Our sprite_x and sprite_y coordinates actually point to the top left corner of the sprite. This means that if we place our sprite at coordinates (0,0) and keep moving the sprite to the right, sprite_x/16 will transition from 0.something to 1.something only when sprite_x is larger than 16. This means that the top left corner will be at (16,0), so graphically our heart will be at the middle of the tile. So in order to fix this, we have to add half the sprite width and height to sprite_x and sprite_y before we perform the divisions. Easy! Now our coordinates will map a bit better to the grid coordinates!

     

    Getting a countdown timer for PAL/NTSC

    One last thing before this post comes to a finish, is how to create a countdown timer.

     

    The Jaguar hardware has no easy way to count the time unless you start using DSP timers or something equally bizarre. And you probably don't need that much precision or want to mess around with those timers. But at least we do have the Vertical BLank interrupt, or VBL for short. This is an interrupt that fires at a fixed rate when the screen is ready to be drawn from the beginning. This happens every 50 times for PAL and 60 times for NTSC. So if we can count the VBL ticks then we have a sort of clock! rb+ has a command which waits until such an interrupt is generated:

    VSYNC

    so let's use that! But first we need to know if our system is PAL or NTSC. Turns out there's a hardware register that gets set with the correct value. So we can use that to see how many VBL ticks equals one second:

    ' Determine screen frequency
    dim ticks_per_second as short
    ticks_per_second=50     ' assume pal
    if (dpeek(CONFIG) band VIDTYPE)<>0 then
        ticks_per_second=60 ' nope, it's ntsc
    endif
    

    Now we can set a countdown timer:

     

    ' Initialise our countdown timer
    dim countdown_ticks as short
    countdown_ticks=10*ticks_per_second
    

    Then it's a simple matter of having a loop that VSYNCs and ticks our timer variable down until it's zero:

    do
        vsync
        if countdown_ticks>0 then
            print "Seconds left: ";(float)countdown_ticks/(float)ticks_per_second
            countdown_ticks--
        else
            RPRINT "It's over!!!!!!!!!!!!!!!!!!!!!!!!!!"
        endif
    loop
    

     

    And that's it!

     

    Hopefully this has been helpful to people, I really tried to make it as accessible as possible for people that are learning things. Let me know what you think about it, and more importantly: ask if something is not clear. See you around!

    image.png

    • Like 3

  11. -flto is such a pain. Because of that I doubt rln will ever fully support linking with ELF objects. I mean, we can add ELF object files easily but if people link against any gcc compiled library which is compiled with -flto then who knows how much work it would need. My estimation is that we essentially would be rewriting ld, including bugs and undocumented stuff.


  12. Long story short, this is just the tip of the iceberg for that issue. The fix I have provided you there is just a kludge in a long series of kludges on that part of the assembler. Thus, it has grown organically to a point that nobody fully grasps how it exactly works. Therefore nobody wants to touch it. So our next big task is to burn everything to the ground and rewrite that function (we opened a ticket for it here). We want to make it happen but we simply lack the time/courage for now.

     

    So unfortunately you either have to patch the offending sources in the library or path the assembler itself (personally I'd go for the first option).

    • Like 1

  13. Wow, that was a bizarre trip for me.

     

    I'll really spare you the details as it was mostly me fumbling at random and remembering bits of what I did a couple of months ago, so here it is: another tentative rmac fix!

     

    In a freshly checked out rmac copy, open file object.s and go to line 176. There should be an "else" in that line, followed by a switch/case construct. Simply delete the "else" and rebuild. I've only tested this with sound.o and not a full rebuild of the removers' lib, but I'm hoping it should be fine :).

     

    As for what sound.o I originally used - I can't really say! I was copying object files around from your zip files and probably got lucky :).

     

     

    So anyway, here's hoping that this fixes everything and we can put this thread into the history closet :)

    • Like 1

  14. Right, so I spent some time on visual studio and diffing the object files as they're linked in, comparing them with the aln binary. Apart from objects being generated using different gcc I couldn't find anything bad (although it was hard to judge). So then out of curiosity I dragged the produced .cof file to virtualjaguar.

     

    And that... worked? I then dragged the aln binary to compare, and sure enough, that also works the same way as the rln linked one. I then tried with both modExample.o variants, and they also seem to work fine.

     

    So... I'm not sure what to assume here! At least I'm glad rln works fine, but why doesn't it work for you?

     

    To try and figure out what's happening, I uploaded my build folder here. It contains the aln and rln coff files so you can firstly check out and see if they work for you. Then you can try building the coff yourself. To build it here I used:

     

    rln -a 4000 x x -rq -v -v -v -w -e -o modExample.cof crt0.o modExample.o  gfx_data.o data_def.o sound_data.o display.o interrupt.o sound.o rmvlib.a fb2d.o lz77.o jlibc.a libgcc.a

     

    Try that out and see if it produces an identical binary as the one in the .zip file. To switch between the two modExample.o files I keep them both under another filename and use something like "cp modExample_new_gcc.o modExample.o" or "modExample_old_gcc.o" and then re-invoke rln to save me some time.

     

    I am now very curious to read your findings!

     

    build.zip


  15. Okay, there's something really screwy happening here.

     

    I started comparing your aln linked binary with the rln one, going object file by object file. When modExample.o is linked in, the .cof files start to diverge massively. It's not just some offsets plugged wrong, we're talking about whole instructions inserted and code shifted around. I even spotted a NOP instruction in there (if it's not data, hard to tell at the moment). Looking at the command lines you use to build each binary I see you're using different builds of gcc (m68k-atari-mint-gcc vs m68k-aout) which is probably responsible for this. In order to have an apples-to-apples comparison you will need to use the same compiler otherwise there's so many different things between the two .cof files that the issues will be lost inside the noise.

     

    Also I see that you use the same compiler (Vincent's) for both rln and jlinker binaries. So it could be that this is related.

     

    I guess you can use the exact same .o files that you use on your aln binary with rln and jlinker and see what happens. If that works then we can focus on the compiler doing something weird.

     

    [EDIT] Just for the record I ran your script that installs and patches all the tools necessary, but I think that didn't include mk68-aout gcc

    • Like 1

  16. Ok, first of all sorry to @lachoneus for such a huge time gap with this issue. I thought you'd sorted the problem out so I didn't pursuit the matter further :(. If I knew it was an issue with the linker I'd have looked into it much earlier. Anyway!

     

    I must say I'm really not in touch with the rln source code but I gave it a shot anyway. I think I fixed the issue but it remains to be verified by you for correctness. If that works for you we'll check it into the rln source tree properly. So without further ado:

     

    Open rln.c, go to line 2460. It should read:

     

            if (objName[0] == 0x20)

    Change that into:

     

            if (objName[0] == 0x20 || objName[0]=='/')

     

    Recompile and try linking with rln. I'm sure it should link without errors now, but if this a) will be the case for you too, b) works as advertised.... it remains to be seen.

     

    So give this one a try and let us know what happens!

     

    (for the curious: rln was skipping object files whose filename was bigger than 16 characters. Anyone from the linux camp who mocks Bill Gates for the infamous 640k quote..... I call that pot, kettle, black!)

    • Like 1
    • Thanks 1

  17. 23 hours ago, lachoneus said:

    @ggn I don't know if you have been following this, but does the bug that Seb mentions in the above post apply to rln as well?

    I don't think so. At least I use rln to link Raptor Basic+ binaries which consist of files output by gcc, plus plain assembly files without any issues that we've come across lately. Did you switch to Seb's linker because you came across some bug in rln? (sorry, I kind of lost the discussion after one point!)


  18. 1 hour ago, Marius said:

    Or listen to your 'No Good' song on soundcloud (excellent title again). The first 38 seconds or so, there is definitely some intonation-problem going on there.

    Then again, many would argue that Prodigy do produce music altogether so that part of the argument might be void there...


  19. 1 hour ago, Mr Robot said:

    Deliberately downsampled to make it look like the Atari is doing the decoding

    Nope, quoting from the readme page on the github link:

     

    While the program is running, it might look (or sound) like the Atari generates the audio, which was the whole point, but the audio only passes through the Atari from the device connected on the peripheral/SIO port, a port that also happens to have an audio in pin which we can use to connect an audio source to it. The audio then goes through the Atari and out to the RF modulator out to the analog TV.

×
×
  • Create New...