TAS mode for Javitto (development thread)

I haven’t looked enough to understand quite what’s going on here, but here’s a shot in the dark…

Have you tried using floor instead of cameraX/tileW? It looks like your column is happening at the x == 0 threshold, so a common issue (at least in c++ with floats/ints) is if you let divides truncate down to integers, you get a duplicate where x == 0. i.e., a conversion from a float to an int just chops off the decimals, so it rounds down for the positive and up for the negative.

1 Like

That is exactly the problem yeah!
So that line is trying to find where in the map the X coordinates land based on the tile’s width. In this case 16. The problem indeed is that anywhere between -1 and 0 should be skipped but it is always read as 0 due to precision loss in integers.

I’ve tried using camera as a float but then I have all this struggle getting the values aligned/converted/moved through the rest of the process :thinking:

I am thoroughly convinced there is a better way to do the entire thing. But I’m yet inexperienced in the maths necessary to figure it out.

As mentioned too I’ve tried to dissect and do most the same as from the current c++tasmode but I clearly don’t have a solid understanding of what’s happening there :sweat_smile:

I’ll try to explain what is happening better here:

void fillLine(ushort[] line, int y) {
        // Clip top and bottom of map.
        if(y-cameraY < 0 || y-cameraY >= mapHeight*tileH)return;

Here I am checking if the offset of the cameraY position causes the position to be over (<0) or under (greater than the height of the map, which is in tiles so we multiply by the height of the tiles for the true height in pixels). Pretty straight forward, but ugly…

        
        // Set the Y for the map and tileset lookup
        var mapY = ((y-cameraY) / 16) * mapWidth;
        var tileY = ((y-cameraY) % 16) * tileW;

Here I am collecting the Y position in the Map array (which is an array of tile ID’s) and then also collecting the Y position within the tile itself (in this case 16 is the height of the tile). Nothing too interesting here, but again, the math looks ugly and there may be better ways to determine where on the map/tile the current passed in line should fall.

        // Divide the current X position by the width of the Tiles.
        var mapX = cameraX / tileW;
        
        // Get the position on the first X Tile.
        var tileX = cameraX % tileW;

Similar, I thought, to how I get the Y position, here I attempt to get where the X positions for map and tile. Firstly by dividing the camera’s position to the width of a tile and then the X position of where to start in the tile itself.

        // Loop the map width to collect the tiles
        for (int i = 0; i < 220;) {
            // Clip the right hand side of the map. Whee~
            if(mapX >= mapWidth)return;

We loop over the width of the screen, since we are populating a line :person_shrugging: . Starting we try to see if the mapX is outside of the map’s width. Don’t need to put anything there if we aren’t on the map.

            int iter = Math.min(tileW - tileX, 220 - i);

Getting the length of the tile to render. This helps find on the left/right side of the map in the case where only a partial tile should be rendered.

            // Trying to clip the left side of the map...
            if(mapX < 0){
                mapX++;
                tileX = 0;
                i+=iter;
                continue;
            }

So, the broken section, as we determined, that the mapX can’t find the values between -1 and 0 (as such -0.5 etc…)

            __inline_cpp__("
            // Get tile ID from the map. Then use that to find the tile itself from the tileset
            auto tileId = ((uint8_t*)tileMap)[mapX + mapY];
            auto tile = ((uint8_t*)tileSet) + tileId * 256 + tileY;
            ");

This is where we actually use the values previously collected to get first the Tile ID from the map and then using that tile ID we get the actual Tile (which is an array of color indexes).

            // Loop over the Tile color IDs and put them in the line array.
            for(int t = 0; t < iter; t++){
                __inline_cpp__("
                color = tile[tileX + t];
                ");
                line[i+t] = palette[color];
            }

With the tile, we loop (with that iter variable so we know how many to put in the line) over the tile to populate the line with the correct color from the palette.

            i+=iter;
            tileX = 0;
            mapX++;
        }
    }

and finally we increment to do the same thing again on the next tile in the row.

This may be entirely obvious for most, but breaking it up I hope helps explain a bit on my mental process of what’s happening.

Screenshot from 2022-08-01 19-59-20
This is when things are about to get good :smiley:

Your cameraX and cameraY should never be negative, but if you want the map to scroll past it’s borders then you would simply need to clip where cameraX < 0 you’d fill the first cameraX * -1 pixels with a designated background color, same with cameraY (although with cameraY if it’s less than 0 you’d simply fill the entire line with the designated background color.

The alternative approach is to only use a tilemap slightly bigger than the screen (which is how TAS does it by default) and lock cameraX to between 0 and tileW - 1. This would eliminate the need for mapX and mapY calculations. From there when the camera above/below that range you simply shift the whole timemap accordingly and fill in the now invalid rows/columns with new tiles. This allows larger maps to be stored in FLASH (if possible with Javitto) or even streamed from SD in chunks (like how I did the ChunkMap system, but that’s more advanced).

This is the part I would LOVE to do. But I am really struggling to figure out how to do mathematically :frowning:

Actually, Javitto should be able to stream from SD as well now with the File access :thinking:

Hehe, thanks, but I guess it didn’t help me much :sweat_smile:

But the issue I was trying to point out is not on the range of -1 to 0, btw. It’s anything below 0. The dirty fix would be to put if (cameraX < 0) mapX--; below your mapX assignment. But then I’m not sure what’s going on with your modulo after that, because it also has a negative sign issue. But then there are maybe corrections? I’m just all turned around.

Let me start over. I would recommend stepping through with a debugger or perhaps rewriting for clarity.

FWIW, I’ll put how I would adapt your code for the problem. Mind, I don’t know java and this could also be totally broken :stuck_out_tongue: But it seems to make sense to me.

int mapX;
int tileStart;

if (cameraX < 0) {
	mapX = 0;
	tileStart = 0;
} else {
	mapX = cameraX / tileW;  
	tileStart = cameraX % 16;
}

// for negative camera values, skip ahead by the delta between the map start (i.e., 0) and cameraX
int screenX = Math.max(0, -cameraX);

while (screenX < 220) {
	if (mapX >= mapWidth) return;

	__inline_cpp__("
	// Get tile ID from the map. Then use that to find the tile itself from the tileset
	auto tileId = ((uint8_t*)tileMap)[mapX + mapY];
	auto tile = ((uint8_t*)tileSet) + tileId * 256 + tileY;
	");            

    int tileEnd = Math.min(16, 220 - screenX);
	// Loop over the Tile color IDs and put them in the line array.
	for(int t = tileStart; t < tileEnd; ++t){
		__inline_cpp__("
		color = tile[t];
		");
		line[screenX + t - tileStart] = palette[color];
	}

	tileStart = 0;
	screenX += 16;
	mapX++;
}
3 Likes

Actually you already have everything you need plus some extra (hint: mapX starts at 0 and increments with each tile and mapY = y / tileH since tile 0,0 is always visible).

From there it’s just a matter of having a function to modify values in the tilemap and then that’s TAS bgFiller. Its up to the individual games to update the tilemap either directly or through convenience functions.

Don’t worry about that for now obviously, but it’s good keep in mind once you’ve finished the core and had a chance to familiarize with it’s usage.

EDIT: To drop a further hint on the relationship between TAS and the user’s code

cameraX and cameraY are set by the user and just indicates the x/y pixel offset into the upper-left tile. From there it’s up to the user’s code to fill the tilemap. The line fillers simply draw that single screen’s worth of tilemap to the screen, then draw sprites on top of the tiles.

I’m confused about this. I don’t want 0,0 to always be visible. If I have a larger map I’d like to be able to move to the right more no? Or am I misunderstanding.

Its just the screen tilemap (ie the tiles visible on screen). The larger map is responsible for telling it what tiles are to draw. Essentially just copying the tiles from the larger map to the screen.

So say I can roughly fit a map of 14x11 onto the screen. If I have a map double that size, 28x22 tiles (of 16x16 size)

I need to be able to move the map around to view the whole of it. But do I force the map to stop instead of going “off the view” or do I make the end user manage that?

As seen now, if the map is too far off the screen I have difficulty managing what should be drawn in those spaces which should be empty tiles. So instead of that I should just force the map to stay on the screen?

I am really frustrated with how I seem to be overcomplicating this :neutral_face:

In the project I did with TAS (Nanophage zero) the map is not constrained that way. But I’m not understanding how that works with what I’m being told the BGTileFiller is doing…


So, using a hopefully helpful visual aid here.

The red box is the “screen”
The green box is the “map”
The white box is the current “line” being filled.
(it isn’t supposed to be accurate dimentionally, just enough to demonstrate where my brain goes kaput)

Right now I’m using CameraX and CameraY to offset that top green corner. So, in this case, cameraX would be something akin to -48 and cameraY would be -16.

So the screen tilemap just says what tiles to render on screen. The map can be clipped or not and would simply fill the screen map with the appropriate tiles. To simplify how PtaD works (which allows maps to be clipped or not) any tiles beyond the maps border are filled with a specific tile.

The lineFiller shouldn’t concern itself with borders because its only told to draw the internal tilemap to the screen. The map would then tell it what tiles to draw, and if its past the edge of the map it would still tell it what tiles to draw in those spaces.

Ah I currently don’t have the concept of an internal map. Only the one giant map. Maybe that’s my problem.

I like the simplification of this! I’ll try and tinker with it to see what happens :eyes: I think mixing this and trying to understand the inner map as Tux has called it could help a lot.

Though I don’t yet know how to get a section of the map as mentioned. Very odd working with a pointer mixed in javitto and c++ haha

@lucentbeam I don’t know why it was so difficult for my stubborn brain juice to comprehend it, but the screenX piece was definitely something I needed! That works!

I’ll keep tinkering and try to make it a bit “cleaner” code-wise. But now I am definitely almost to a good spot where I can call this “done” to move on without guilt haha. Thank you!

8 Likes

Getting a setTile method working :slight_smile: thanks to @Hanski for pointing out my super dumb with the const earlier :laughing:

5 Likes

That should be a playable character.

2 Likes

Somehow I forgot and overlooked the possibility for using multiple Data layers. In the original TASMode it would be something like this:

// Pseudocode
MapEnum {
    VALUE1,
    VALUE2,
    VALUE3,
}

MapEnum(VALUE1|VALUE2|VALUE3)

With the new converter I’m turning those values into their own individual static byte variables:

static byte EMPTY = 0;
static byte VALUE1 = 1;
static byte VALUE2 = 2;
static byte VALUE3 = 3;

public static byte getFarmMapData(int x, int y){
        byte ptr;
        __inline_cpp__("
        static signed char parameters[] = {
        VALUE1,
        VALUE2,
        VALUE3,
... etc ...
        };
            
        ptr = (x >= 14 || y >= 12) ? EMPTY : parameters[y * 14 + x];
        ");
        return ptr;
    }

So I’m not sure how yet to manage the case where the map has both VALUE1 and VALUE2 or any combo of more than a single data point.

I don’t remember why I moved away from an enum :sweat_smile:

2 Likes

For now I am leaving multiple data values on a tile as “UNSUPPORTED” with an ID of -1

Things are looking better and better though! After changing the tile sizes and adding support for variable sizes (need a global place to set them) it is looking good for usability.

Here I am playing with “animating” some tiles by using a manual counter in my code and updating the tiles haha…


5 Likes

I still plan to continue working on this. Had to take a break from a number of projects, and archived others for the foreseeable future. Hoping I can get back into the groove eventually (probably closer to Springtime on this one though).

I’ve gotten to a point where I was working on the game itself and making some decent progress. The TASmode stuff itself works alright. I don’t really know how the performance would be for most folks and there is room for much improvement I’m sure, however I was struggling to figure out where/how to fix that up.

I wanted to post that for anyone interested in using this, I wasn’t sure how to share the TASMode for Javitto for those interested in trying it. I wanted eventually to do a PR to the FemtoIDE repository on github to make it more “official” but I’m concerned it isn’t going to get to a place where it reaches that level :slight_smile:

2 Likes

If not already done, you could put your Femto IDE forked repository available in Github. So everyone interested can test it and maybe even improve it.

2 Likes