Brain Fart: Make this code work in 8bit?

TheouAegis

Member
I thought about putting this in Off-Topic, but it's still technically a programming issue and Off-Topic tends to be waaaaay off-topic. I'm trying to code something that would still be doable on an NES and I'm also trying to restrict myself to a few particular formulas to retain the feels of the inspirations for my projects. First, let me give some foundation for my issue. I apologize for how long-winded this will be.

As a couple of you may perhaps know, for some of my little projects, instead of using the trigonometric functions for wave patterns, I use a dynamic deceleration code used by coders at Konami (I'm sure other programmers have used it as well). Admittedly, it's quite limited in its applications, but the code is basically
Code:
``````vspd += (ystart - y) / 256;

//or...

hspd += (xstart - y) / 256;``````
The wave pattern can be modified by setting an initial speed, shifting the starting position, modifying the divisor (256 by default because the code was intended for an 8bit system), or even performing additional operations inside the parentheses.This code worked flawlessly to reproduce the same results as the source material when using the following code to actually handle movement
Code:
``````yfr += vspd;
y += floor(yfr);
yfr -= floor(yfr);
xfr += vspd;
x += floor(xfr);
xfr -= floor(xfr);``````
This was already discussed in another topic, so it's of no concern to me here. However, it is significant to my primary issue:

I got to thinking, what would happen if I applied both horizontal and vertical components to the object (essentially removing the "//or..." from the first code block); and after some fiddling with the starting coordinates and speeds, I deduced how to form ellipses using that code structure. However, I came across a significant, persistent problem: gradually, very slowly unless I set the room_speed to 999, the ellipse would traverse across its x-axis almost as though it were rotating about it. After some exhausting debugging, I traced it to one basic aspect -- shifting the starting coordinate. Just to clarify, here are a couple Create Event settings:
Code:
``````xfr = 0;
yfr = 0;

//The following settings would create a r32 circle
x += 32;
vspd = 2;``````
Code:
``````xfr = 0;
yfr = 0;

//The following settings would create 128x64 ellipse
x += 64;
vspd = 2;``````
Code:
``````xfr = 0;
yfr = 0;

//The following settings would create a 71x65 SKEWED ellipse
x += 32;
hspd = 1;
vspd = 2;``````
Now, it's that x+=32 (or y+=32 if we change orientation) that ultimately was giving me headaches, as it was causing deviations which shifted -- I'm assuming -- hspd or vspd gradually. I went back to the source material, modified the source code so it shifted the starting y-coordinate with a vspd of 0, then tested the code on an NES emulator. Sure enough, that shift was present there as well.

I narrowed down the source of my problem, so I tried to find a fix for it. In my GM code, I came up with two "solutions". The first was to simply remove the integer movement from the formula.
Code:
``````x += hspd;
y += vspd;``````
That... just wasn't acceptable for me, however. It got me thinking, too, why does it work with that movement and not with integer movement. I was like, if the source code only used integer coordinates to calculate 16bit reals, shouldn't the same hold true with integer coordinates in GM? Then I remembered the source code was flawed as well and it had nothing to do with my integer movement code. This then lead me to my alternate solution and the one I need advice on:
Code:
``````vspd += (ystart - (y + yfr) )/256;
hspd += (xstart - (x + xfr) )/256;``````
This worked with my integer movement code, but it was clearly a significant deviation from the source code. It's a significant step from (64-32)/256 to (64-32.125)/256, since we're now including even more bytes in the calculation. Brainstorming, I considered an ASM equivalent of something like
Code:
``````LDA object_index
BEQ new_movement_subroutine
CMP obj_spikeball
BEQ new_movement_subroutine
SEC
LDA #00
SBC yfr    //included
TAY
LDA ystart    //created @ 0x0606
SBC y    //included
STA temp[0]
STY temp[1]    //maybe create @ 0x06B7 ?
SEC
LDA #00
SBC xfr    //included
TAY
LDA xstart    //created @ 0x061E
SBC x    //included
STA temp[2]
STY temp[3]    //maybe create @ 0x06CC ?
CLC
LDA vspd[2]    //created @ 0x0633
STA vspd[2]
JSR
LDA vspd[1]    //included
STA vspd[1]
LDA vspd[0]    //included
STA vspd[0]
RTS
LDA temp[2]
STA temp[0]
CLC
LDA hspd[2]    //created @ 0x0682 ???
STA hspd[2]
JSR
LDA hspd[1]    //included
STA hspd[1]
LDA hspd[0]    //included
STA hspd[0]
RTS``````
And that's an ugly concept, since we now turned vspd and hspd into 32bit values (only used 24bits here, though).

Basically, I'm looking for any ideas on how to make the code work properly (i.e., no wobbling or compression along the axis) whilst retaining only 16bit values for vspd and hspd? Or maybe it's just not even possible.

I have an enemy planned out, but currently it would have 4 instances which would need this code. In keeping with the source material, I think I'd only have 2 extra bytes per instance, but with what I've come up with so far here, my current code requires 4 extra bytes per instance PLUS an additional basic movement subroutine. That just doesn't feel acceptable to me, so I'm hoping one of you guys can come up with something better.

Edit: Redid the ASM to verify how many variables I'd need to create and streamlined the movement code to make it a little more viable. Looking at it again, it may actually be viable now. So really, I guess I'm looking for any ideas on how to optimize this code or the mechanics in such a way that it would be even more streamlined on an NES. I mean, temp[1] and temp[3] shouldn't even be temporary variables, so I went back and checked for any avaialbe address ranges I could hopefully use. The code is still slower than I would like, though.

Last edited:

RujiK

Member
I re-read your post twice, but to be honest I'm still not 100% sure what your issue is. It sounds like floating points are slowly adding up and giving unexpected results?

If you want to eliminate them, and use "true" 16 bit variables, why not encode your x and y values into a buffer? This will be slightly slower than straight variables but should eliminate any decimals.

something like:

Beginning of step event:
Code:
``````x_value = buffer_peek(my_buffer, 0, buffer_u16);
y_value = buffer_peek(my_buffer, 2, buffer_u16);``````

End of step event:
Code:
``````buffer_poke(my_buffer, 0, buffer_u16, x_value);
buffer_poke(my_buffer, 2, buffer_u16, y_value);``````

nacho_chicken

Member
I re-read your post twice, but to be honest I'm still not 100% sure what your issue is. It sounds like floating points are slowly adding up and giving unexpected results?

If you want to eliminate them, and use "true" 16 bit variables, why not encode your x and y values into a buffer? This will be slightly slower than straight variables but should eliminate any decimals.

something like:

Beginning of step event:
Code:
``````x_value = buffer_peek(my_buffer, 0, buffer_u16);
y_value = buffer_peek(my_buffer, 2, buffer_u16);``````

End of step event:
Code:
``````buffer_poke(my_buffer, 0, buffer_u16, x_value);
buffer_poke(my_buffer, 2, buffer_u16, y_value);``````
I'll have to run some test on OP's code before I go in depth on anything, but if all you want to do is remove the floating point, the easiest way to do it in GM is run it through a bitwise-or.
Code:
``xx |= xx;``
Don't even have to mess with buffers.

TheouAegis

Member
Unfortunately GM isn't that straightforward. Even running it through bitwise operators, it will still run its own rounding algorithm.

The floating points not much of an issue. Or rather, it was an issue even with the original source code, but I figured out a rough way of compensating for that glitch in the source code and the floating points are actually the solution. In GM, the fix was rather simple and straightforward, but when I try to break it down back into 8-bit and 16-bit terms, I realized how much more complex my solution was, as now instead of being able to be run from a single state separate from the movement code (the xfr+=hspd stuff), it now has to be run with its own additional movement code (basically xfr+=hspd mod 1, which isn't needed in GM but it is on an NES) on an 8-bit system.

At this point I've almost just accepted things are the way they are, but I'm still trying to figure out if there is a way the structure is so it would I'll be run in a state to separate from the default movement code without any additional movement code required. Essentially, I need to compensate for the wobble somehow. Maybe I need a timer... I dunno, my brain doesn't work well these days.

vdweller

Member
Well bro I ran your code and, sure as hell, it happens like you said.

However i noticed that the boundaries of that "rotation" remain strictly the same. In fact, if you make the dot leave a trace and leave it run for a ****tillion seconds, a perfect rectangle will form.

(screenshot is before it turns around to "fill" the other 2 corners)

I don't know how to explain this properly, but it has to do with motion "de-syncing": Not every y-jump happens every x-jumps. The fractional remainders added in different rates ultimately de-sync the ellipse.

Interestingly enough, the motion can be periodical. Example with the following parameters:
Create:
Code:
``````x += 128;
vspd = 16;
hspd=0;``````
Step:
Code:
``````vspd += (ystart - y) / 2;
hspd += (xstart - x) / 2;``````

This shape is as dense as it can be, which means the rotating eclipse eventually returns to its original orientation.

So it's a feature, not a bug, heh. From here your best bet is probably to find a relation of x and y to ensure a "stable" orbit, you could even try to "bind" y in relation to x, or maybe even use lookup tables.

Sorry if I wrote things you already knew.

Attachments

• 327.1 KB Views: 1

vdweller

Member
To explain even more:

The FIRST time your dot passes from x-coordinate 76 heading right, yfr may be 0.72, so it doesn't move down.

The SECOND time your dot passes from x-coordinate 76 heading right, yfr may be 1.64, so it does move down.

This inconsistency is what destabilizes the orbit's period.

Mathematically, an ellipsoid motion may seem like it's two independent moves along two axes, but there's more to it:

x=a*cos(t)
y=b*sin(t)

A perfect ellipse, because both x and y depend on the same parameter (t).

By employing empirical integer shenanigans, what you do is essentially mess with t. For example if you do

x=a*cos(t)
y=b*sin(0.78*t)

You get a "rotating-in-bounds" ellipse, precisely like what you are experiencing now.

Consider ditching the Konami code for elliptical motions and using lookup tables for sin and cos (I believe this is permitted for an NES game?), then use the same phase to get your ellipse.

Attachments

• 2.1 KB Views: 2
Last edited:

TheouAegis

Member
To explain even more:

The FIRST time your dot passes from x-coordinate 76 heading right, yfr may be 0.72, so it doesn't move down.

The SECOND time your dot passes from x-coordinate 76 heading right, yfr may be 1.64, so it does move down.

This inconsistency is what destabilizes the orbit's period.

Mathematically, an ellipsoid motion may seem like it's two independent moves along two axes, but there's more to it:

x=a*cos(t)
y=b*sin(t)

View attachment 25251

A perfect ellipse, because both x and y depend on the same parameter (t).

By employing empirical integer shenanigans, what you do is essentially mess with t. For example if you do

x=a*cos(t)
y=b*sin(0.78*t)

View attachment 25253

You get a "rotating-in-bounds" ellipse, precisely like what you are experiencing now.

Consider ditching the Konami code for elliptical motions and using lookup tables for sin and cos (I believe this is permitted for an NES game?), then use the same phase to get your ellipse.
I didn't even realize someone replied to this post! lol
I hadn't considered a LUT. I might give that a shot one of these days. And yes, sin/cos LUTs (and derivatives) are common in NES games.