Jump to content

Photo

Parallax Starfield - demo released

assembler cartridge

31 replies to this topic

#1 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • 3,922 posts
  • Location:Denmark

Posted Fri Dec 2, 2016 3:07 AM

Cartridge to download in post #25.
 
- - - - -
 
 
In the footsteps of managing 32 sprites, without more than 4 sprites per horizontal line, as explored in Bubbles (demo).
 
If you keep a number of sprites stacked vertically (no vertical overlap), they will only occupy 1 of the 4 sprites allowed horizontally.
 
In Bubbles I made each stack move up with their own individual speed. This time I will try and move the 4 stacks in any direction.
 
Here's a quick setup of 4 stacks.
 
parallax.starfield.0001.png
 
Oh, and instead of having 8 sprites in each stack (or plane), it's 6 + 7 + 9 + 10 = 32 sprites. The number of stars close to you are less than those far away.  ;)


Edited by sometimes99er, Thu Dec 22, 2016 12:34 AM.


#2 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Sat Dec 3, 2016 2:06 AM

parallax.starfield.0002.png
 
This has the vertical position slightly adjusted to get a better initial distribution.
 
The different sized stars are evenly distributed vertically. The 6 biggest stars separated by 192 / 6 = 32 pixels. The smallest by 192 / 10 = roughly 19 pixels.
 
parallax.starfield.0003.png
 
Tursi supplied me with an excellent random number routine here. And I've been using it ever since. Distributing stars horizontally and randomly did however expose a weakness. Or maybe I'm doing something wrong.
 
The numbers may seem random, but the bit manipulation appear weak.
 
Generally I'd like to use fixed seeds to populate scenarios ( procedural level generation *) ). Then I know what is presented instead of getting perhaps somewhat "strange effects" from more random generation.
 
Manually I changed the fixed seed about 20 times, and did find one with a good initial appearance. It still has exponential growth telltale though.
 
parallax.starfield.0004.png
 
*)
1982 Pitfall for the Atari 2600: Innovative techniques were used to keep the code space within the 4K limit, including a polynomial counter to create 256 screens within 50 bytes of code.

 


Edited by sometimes99er, Thu Dec 8, 2016 5:16 AM.


#3 Tursi OFFLINE  

Tursi

    River Patroller

  • 4,773 posts
  • HarmlessLion
  • Location:BUR

Posted Sat Dec 3, 2016 4:09 AM

Yeah, I've had issues with the randomness of that algorithm at times as well. But I've often got better results when I don't use the entire word by taking bits from the middle of the word instead of the least significant ones. Might have been my imagination but I liked the results more. ;)

#4 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Mon Dec 5, 2016 3:22 AM

Stars

 

Apart from the 32 hardware sprites, I'm adding 16 software sprites. That's 48 sprites in 5 planes.

 

Randomness

 

I tried to shift the random number 3, 4 and 5 places to the left, but still there's traces of exponential growth. More or less. Tried up to 10 different initial seeds without being fully satisfied. Now, I simply preset all sprites.

 

parallax.starfield.0005.png

 

To do list

 

Splash screen.

Music.

Joystick input and movement of planes.

Adjustments. Colors etc.

 

;)

 


Edited by sometimes99er, Thu Dec 8, 2016 5:17 AM.


#5 GroovyBee OFFLINE  

GroovyBee

    Games Developer

  • 9,736 posts
  • Busy bee!
  • Location:North, England

Posted Mon Dec 5, 2016 5:00 AM

You might have better luck using the modulo operation on the values from your PRNG to determine x/y positions. Its more complex that a simple shift and bitwise AND, but you should get better results.

#6 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Tue Dec 6, 2016 1:11 AM

You might have better luck using the modulo operation on the values from your PRNG to determine x/y positions. Its more complex that a simple shift and bitwise AND, but you should get better results.

 

I did not use modulo operations (DIV), since y-positions are not random and x-positions use a width of 256 pixels. Using DIV with the random routine to get a range is however creme de la creme.

 

Dividing the random word (range >0000 to >FFFF) by 256 returns the low byte as remainder, so no gain in some cases (power of 2).

 

DIV may be a key ingredient to better random number generation. Simply adding contents from console ROM (same for all TI-99/4A) may be a quick and dirty fix.

 

Somehow I think I may have to control randomness in my Restless II game to avoid a screen full of aliens all starting out in the same corner. I did some controlling in my Snake Plissken game. With my Minesweeper game you could get all the bombs in one corner, though the random routine is probably not that random.

 

;)


Edited by sometimes99er, Tue Dec 6, 2016 1:52 AM.


#7 Lee Stewart OFFLINE  

Lee Stewart

    River Patroller

  • 3,341 posts
  • Location:Silver Run, Maryland

Posted Tue Dec 6, 2016 12:48 PM

I do not know whether the PRNG I use in fbForth (same as TI Forth) is any better than what you are using, but here it is:
 
The _RNDW function first retrieves the seed from >83C0.  Then the result of seed * 6FE5h + 7AB9h is shifted right circularly by 5 bits.  This result is stored at >83C0 as the new seed and also returned to the user:
 
_RNDW  LI   R0,>6FE5       calculate new pseudo-random 
       MPY  @>83C0,R0      seed * >6FE5
       AI   R1,>7AB9       seed * >6FE5 + >7AB9
       SRC  R1,5           (seed * >6FE5 + >7AB9) SRC 5
       MOV  R1,@>83C0      store new seed
       B    *R11           return to caller
 
The _RND function returns a positive integer less than the number supplied to it, which includes 0.  _RND invokes _RNDW , takes the absolute value of the result and divides by the user-supplied number and returns the remainder.  Obviously, for your case, retrieving only the LSB would be a much more economical operation than division:
 
_RND   MOV  R11,R15        save return
       BL   @_RNDW         get random number into R1
       ABS  R1             force positive
       CLR  R0             set up for division
       DIV  *R9,R0         divide number in R0--R1 by num on stack
       MOV  R1,*R9         return remainder on stack
       B    *R15
 
The above done your way:
 
RND256 MOV  R11,R15        save return
       BL   @_RNDW         get random number into R1
       ANDI R1,255         extract right byte (MOD 256)
       MOV  R1,*R9         return remainder on stack
       B    *R15
 
I have puzzled over changing RND for fbForth 2.0 to not do the ABS and to just return all but the MSb, i.e., leftmost bit.  I should compare the distribution of pseudo-random numbers done both ways, I suppose.
 
One final note, randomizing the seed in both TI Forth and fbForth counts from zero until a VDP interrupt is detected.  Though it sets up a race condition, I have never had any trouble with it:
 
_RNDMZ MOVB @>8802,R0    get VDP status byte
       CLR  R0           discard it
       CLR  R1           clear counter
RZLOOP INC  R1           increment counter
       MOVB @>8802,R0    get VDP status byte
       ANDI R0,>8000     VDP interrupt?
       JEQ  RZLOOP       no, increment counter
       MOV  R1,@>83C0    yes, store new seed
       B    *R11
 
...lee


#8 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Wed Dec 7, 2016 12:02 AM

The _RNDW function first retrieves the seed from >83C0. Then the result of seed * 6FE5h + 7AB9h is shifted right circularly by 5 bits.

The _RND function returns a positive integer less than the number supplied to it, which includes 0.  _RND invokes _RNDW , takes the absolute value of the result and divides by the user-supplied number and returns the remainder.

  
Excellent. At least I can switch to something else if there's a problem with patterns. Also found that Tursi's random function is the same as in Spectra2 from retroclouds. retroclouds avoids zero by adding one, while Tursi requires the seed to be anything but zero.
   

I have puzzled over changing RND for fbForth 2.0 to not do the ABS and to just return all but the MSb, i.e., leftmost bit. I should compare the distribution of pseudo-random numbers done both ways, I suppose.

   
Interesting. I have to get hold of my old homemade random routine, document it and run all through a few obvious tests.
 

One final note, randomizing the seed in both TI Forth and fbForth counts from zero until a VDP interrupt is detected. Though it sets up a race condition, I have never had any trouble with it:

 

Nice one. I'm a bit worried about emulators that could fail somewhat on that (with whatever conditions you have there). Adding it to the "system seed" (>83C0) should be more safe. And perhaps adding or solely relying on a race at a splash screen until the user hits Enter (insuring that the user is not already holding Enter before the race begins).

 

:)


Edited by sometimes99er, Wed Dec 7, 2016 12:04 AM.


#9 Tursi OFFLINE  

Tursi

    River Patroller

  • 4,773 posts
  • HarmlessLion
  • Location:BUR

Posted Wed Dec 7, 2016 12:56 AM

For what it's worth, my routine is not mine, but borrowed from a fellow named Albert Veli who shared it with the Dreamcast homebrew group (and I thought he attributed some book before him ;) ). One of its properties is that it never returns 0 (and also you can never seed with 0, it gets stuck)... another is that it returns /all/ values in the sequence before repeating. So for 16 bit numbers, you will see each value from 1 to 65535 in a pseudo-random order before they repeat -- but this is obviously not the same as each element being a random /number/ - for that you'd expect a duplicate to at least be possible. :)

Psuedo-randomness is of course a huge field of research, but I've always been happy with simple enough, so I can't say much more about it. ;)

Edited by Tursi, Wed Dec 7, 2016 12:56 AM.


#10 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Wed Dec 7, 2016 1:36 AM

And perhaps adding or solely relying on a race at a splash screen until the user hits Enter (insuring that the user is not already holding Enter before the race begins).

 

And then I'm worried about testing Enter directly (CRU) and constantly in a race on real hardware.

 

The rather long time delay in the SCAN is invoked every time a key is pressed or released. If you hold a key, this time delay is not called. SCAN is the console ROM keyboard scanning routine. The loop counter in the delay (@>0498) is a staggering 1250.

 

The interesting part is, that even though SCAN is run and all the lines are tested, the delay only runs when there's a new key state. I guess that's how they thought one could debounce. Since Enter is not used on the menu selection screen, and it's on different lines and columns, I think direct hits without delays could be okay.

 

Within my games and demos, I test all keys of interest (CRU) once per frame update.
  

:)


Edited by sometimes99er, Wed Dec 7, 2016 6:46 AM.


#11 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Wed Dec 7, 2016 2:28 AM

For what it's worth, my routine is not mine, but borrowed from a fellow named Albert Veli who shared it with the Dreamcast homebrew group (and I thought he attributed some book before him ;) ).

 

Thought so. We could call it the B4 for now. ;)
  

One of its properties is that it never returns 0 (and also you can never seed with 0, it gets stuck)... another is that it returns /all/ values in the sequence before repeating.

 

Excellent properties. It's short. It's quick (no DIV). Takes almost no space. With the long insured range, you can seed at a certain point and do "procedural level generation". You can separate level progress and other random stuff (like aliens attacking) with the same routine but handling different seeds. Later the same level could have another attack formation.
  

So for 16 bit numbers, you will see each value from 1 to 65535 in a pseudo-random order before they repeat -- but this is obviously not the same as each element being a random /number/ - for that you'd expect a duplicate to at least be possible. :)

 

With most use cases the return range would probably not be the full range. Rolling a dice should be safe / random. Wider ranges like screen height and width is something I'll include in future tests.
  

Psuedo-randomness is of course a huge field of research, but I've always been happy with simple enough, so I can't say much more about it. ;)

 

Yeah, it's interesting but perhaps also a time consuming detour.
 
;)



#12 Tursi OFFLINE  

Tursi

    River Patroller

  • 4,773 posts
  • HarmlessLion
  • Location:BUR

Posted Wed Dec 7, 2016 11:09 AM

Thought so. We could call it the B4 for now. ;)


Hehe, but that would be limiting - the mask depends on how many bits you want out of it. I found the original message, the source was the book "Graphic Gems".

For different widths just use a different mask. Below is a table of masks
and widths.

Mask (hex)	Width (bits)
03		2 
06		3
0C		4
14		5
30		6
60		7
B8		8
110		9
240		10
500		11
CA0		12
1B00		13
3500		14
6000		15
B400		16
12000		17
20400		18
72000		19
90000		20
140000		21
300000		22
400000		23
D80000		24
1200000		25
3880000		26
7200000		27
9000000		28
14000000	29
32800000	30
48000000	31
A3000000	32
Very handy little function :)

#13 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Thu Dec 8, 2016 3:59 AM

Hehe, but that would be limiting - the mask depends on how many bits you want out of it. I found the original message, the source was the book "Graphic Gems".

 

Yes, that might be handy. Repeating series of random numbers with a length of the power of 2 minus 1. Also you can reset seed after a certain count (to have any length of random numbers).
 
;)

 



#14 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Thu Dec 8, 2016 4:18 AM

To do list
  
Splash screen.
Music.
Joystick input.

5 plane vector control.

32 sprite (top planes) display handling.

16 character (bottom plane) display handling.

Adjustments. Colors etc.
 

:)

 



#15 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Sat Dec 10, 2016 2:17 AM

To do list
  
Music.
16 character (bottom plane) display handling.
Adjustments. Colors etc.
 
:)

  



#16 Asmusr OFFLINE  

Asmusr

    River Patroller

  • 2,437 posts
  • Location:Denmark

Posted Wed Dec 14, 2016 4:11 PM

I tried plotting random points on a 256x192 screen using Tursi's and Lee's algorithms in Java. I plotted 1000 points, then cleared the screen and looped.

 

Tursi's "Graphic Gems" algorithm is just displaying two tilted lines across the screen, which is not very random unless I programmed it wrong in Java (which is very likely).

    public int randTursi() {
        boolean carry = (randNo & 1) != 0;
        randNo = (randNo >> 1) & 0xFFFF;
        if (carry) {
            randNo ^= 0xB400;
        }
        return randNo;
    } 

Lee's is a lot better, but changing the shift from 5 to 7 makes it look truly random, like static on a TV screen, and very similar to Java's own algorithm. 

 

These are just screenshots of a single frame. It's better to judge the randomness from multiple frames, but it should be clear what I mean.

 

Attached File  tursi.png   620bytes   1 downloads

Tursi

 

Attached File  lee5.png   1.29KB   1 downloads

Lee shift 5

 

Attached File  lee7.png   1.46KB   1 downloads

Lee shift 7

 

Attached File  java.png   1.48KB   1 downloads

Java

 

EDIT: Note that I only used the lower 8 bits to plot the points.



#17 GroovyBee OFFLINE  

GroovyBee

    Games Developer

  • 9,736 posts
  • Busy bee!
  • Location:North, England

Posted Wed Dec 14, 2016 4:30 PM

Shouldn't this line:

        randNo = (randNo >> 1) & 0xFFFF;


Be this:
        randNo = (randNo >> 1) & 0x7FFF;
To correctly emulate a logical shift right instruction. Out of curiosity what did you use for the starting value of randNo?

#18 Tursi OFFLINE  

Tursi

    River Patroller

  • 4,773 posts
  • HarmlessLion
  • Location:BUR

Posted Wed Dec 14, 2016 7:18 PM

Since the routine was originally (supposedly) used to do a pseudo-random pixel dissolve, I suspected you did have an error in your conversion. The shift should be handled as unsigned, so the 0xffff mask is correct (although I don't understand why it was necessary? Right shift shouldn't be adding any higher bits...)

I ported the routine to Blassic as a quick test... and I get much the same result as you - pretty much a line. I've never really tested the output of that function before, I find that pretty interesting. ;)

I may need to switch over to your tweaked version of Lee's myself... ;)

#19 Asmusr OFFLINE  

Asmusr

    River Patroller

  • 2,437 posts
  • Location:Denmark

Posted Wed Dec 14, 2016 11:32 PM

Java has both signed (>>) and unsigned (>>>) shift operators, but since I'm working on 32 bit ints and the upper 16 bits are zero it doesn't matter which one I use. The 0xffff mask is just a precaution to ensure I stay within 16 bits, but it's not necessary.



#20 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Thu Dec 15, 2016 2:59 AM

Tursi's "Graphic Gems" algorithm is just displaying two tilted lines across the screen, which is not very random ...

 

Very nice.
 
I thought something wrong since I previously got lines of exponential growth instead of straight lines. But I do get the same result (lines but not exactly the same). This is pretty revealing. Major blow. Good catch. Not saying that this random routine is no good, but one has to be careful and watch out for unwanted patterns. And filling the screen with random dots is something we tend to do from time to time.

 

.setPixel(rand(256), rand(192),
 

parallax.starfield.0006.gif

 

My distribution was not alternating the use of the random numbers for x- and y-coordinates. I only used it for the x-coordinates.

 

.setPixel(rand(256), i++,

 

parallax.starfield.0007.gif

 

Exponential growth is evident, but not as significant as when I tried multiple seeds to get something that looked like random stars. Some seeds were better than others.

Again, I can't believe the difference. Maybe I'm doing something wrong.

  

:)


Edited by sometimes99er, Thu Dec 15, 2016 11:30 AM.


#21 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Fri Dec 16, 2016 12:51 AM

I double-checked Tursi's implementation (outputting and filling 24K RAM from 8K ROM and dumping RAM (F10)) against my and Rasmus' implementation, and they're all returning the same numbers. So that's not explaining why I get more lines than Rasmus. I tried different variations, and ended up tilting the lines to get the same result.

 

.setPixel(rand(256), rand(256)*256/341,

 

parallax.starfield.0009.png

  

 



#22 Tursi OFFLINE  

Tursi

    River Patroller

  • 4,773 posts
  • HarmlessLion
  • Location:BUR

Posted Sat Dec 17, 2016 1:29 AM

My lines were slightly different too, but the fact they were lines at all was enough for me. Interesting results though!

#23 Asmusr OFFLINE  

Asmusr

    River Patroller

  • 2,437 posts
  • Location:Denmark

Posted Sat Dec 17, 2016 8:17 AM

I used: plot(rand() & 0xff, ((rand() & 0xff) * 192) >> 8 )



#24 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Sun Dec 18, 2016 8:51 PM

I used: plot(rand() & 0xff, ((rand() & 0xff) * 192) >> 8 )

 

Excellent. Thanks.
 
192 / 256 = 0.75 ≈ 256 / 341
 
What I used is like
 
{>0001, …, >ffff} % 192 → {0, …, 191)
 
;)

  



#25 sometimes99er OFFLINE  

sometimes99er

    River Patroller

  • Topic Starter
  • 3,922 posts
  • Location:Denmark

Posted Thu Dec 22, 2016 12:33 AM

And here's the demo.
 
You're looking out into space from your little spaceship. Turn the ship in any direction using your joystick controller.
 
:)

Attached File  parac.bin   8KB   58 downloads
Attached File  para.rpk   3.23KB   23 downloads

  

Stars move in 5 planes. 4 planes of stacked hardware sprites and 1 plane of software sprites. The planes has 6, 7, 9, 10 and 16 stars.

 

    

Also ...

Respecting the original TMS9918A limitation of only 4 sprites per horizontal raster line.

No need for 32K RAM Memory Expansion.

Fits the FlashROM99.

Label in post #29.


Edited by sometimes99er, Sat Dec 24, 2016 12:23 AM.






Also tagged with one or more of these keywords: assembler, cartridge

0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users