Thank you! I'm using your new flash streaming mixer and mconvert to make my new MIDI smaller. Awesome work, every little bit of space saved helps!
I also posted about another trick I discovered where you can swap the patchStruct pointer on the fly to change the instruments of a song on the fly, say if your character leaves a room, or goes underwater.
It is a shame we don't have dual VRAM buffers side-by-side in order to use some of those old school tricks. Right now for the enemies, I'm just leaning towards each of them knowing their bounds, and so it can move independently of needing to know the level data. Similar to how the flying enemies worked in Bugz. For now I'm even borrowing some of the sprites. I figure at least the butterfly, and possibly the bee might find a permanent home in the same universe as the unicorn. I'm not sure if I'll have enough cycles to run a full physics engine against each one, but I hope that I'm at least be able to run the "entity_update_flying" physics, where it's just acceleration and friction, but no collision detection.
I had been thinking about it a lot more, and came to the realization that if my Camera object simply keeps track of the level extents (x_min, x_max, y_min, and y_max) currently mapped into VRAM (both the visible and hidden tiles), I could be a lot more precise with things such as the glitch protection in the DissolvingTile_update() function, and things that happen if you jump above the screen or fall into a pit below the screen. The VRAM extent tracking turned out to be more complicated than I had expected, because depending on how close you are to the edge of a level, some rows and/or columns in VRAM might not contain valid level data at all. Once I got the VRAM extent tracking working, I tried completely rewriting the DissolvingTile_update function to use it, and then I discovered another glitch that was present in the original as well that couldn't be fixed unless I enabled my "perfect, but slow" compile-time option. Since I didn't want any glitches, this forced me to really think about how to optimize it some more. The extent tracking allowed me to apply the glitch protection fixes only exactly when I needed them, and I was able to clearly see why they were necessary.
In order to make it look like you're standing in the middle of the top of a 3D block, the tiles are divided into solid and sky tiles, a solid block that has sky above it will always have a sky block with the ground on it to give it a 3D look. When I store things in RAM, like the list of active dissolving tiles, I want to store as little as possible, so I only store the bottom "solid" portion, and the top gets animated along with the bottom. However if you pan the screen up or down and stop right on the edge where you've scrolled just the top or bottom half out of VRAM, and then you scroll back the other way, the half that got reloaded from the level data will be solid again and won't match the other half. The only solution is to check for this, and make the other half solid while it is still in the hidden portion of VRAM, so you never see the mismatch, and the only way to do that was to not wait for the expiration time to match, but to check everything on the list, regardless of expiration time.
In my first iteration this took many thousands of clock cycles (~18,000, if I remember correctly), but the CPU cycles started to become reasonable with the simpler extent-based VRAM tracking system. And then I realized the key to making it faster than my previous glitchy solution... If I'm running through the entire list of active dissolving tiles each frame, I only need to run the glitch protection checks on frames where a new row or column of level data was removed/loaded in VRAM. If not, I could just jump right to the expiration check, and if a tile's timer had expired, it could transition to the next tile in the sequence of dissolving tiles. So I modified the Camera object to have flags indicating whether or not a new row or column was loaded on that frame, and now the DissolvingTile_update function became stupid-fast.
Subsequently, I had another idea to keep peak CPU-usage per frame down, and that was to delay the somewhat expensive lollipop animations by a single frame if we just scrolled past a tile boundary and loaded a new row or column of level data. That prevents a worst-case loading new level data, doing all the glitch protection, and animating all the onscreen lollipops from happening all within a single frame.
In order to test that I would wrap my call to WaitVsync(1) in WDR instructions (handling sprite processing, and restoring the background myself, so that gets included in the clock time) and log the CPU cycles per frame to a file using uzem, and then I would take the sum, and find the maximum, change the code over with a compile flag, and do it again so I could make data-driven decisions about which code was the best.
And then I had a crazy idea, what if I just took a completely naive approach for animating the lollipops that didn't involve the Tracker object at all, and instead used the simple VRAM extent tracking system to loop over the list of all lollipops and if it's within VRAM bounds, animate it. It turned out to be more expensive that looping over just the bits from the Tracker object, but the Tracker object was super expensive when loading new level data. I did the sum and maximum comparison, and the naive approach handily beat the Tracker object. I even disabled animations completely, and still ended up with the same max CPU usage per frame as I did with the naive approach, with a fairly long torture test of running around the level, farting all over the place while collecting the lollipops. So I actually ended up removing the Tracker object completely, even though it made me a bit sad to do so.
I spent some time chasing what I thought might have been a kernel bug, but it turned out to be a bug in my program. When the unicorn fell down a pit, suddenly whichever other sprite that was using ram tile 0 would have parts of the unicorn blitted on top. I couldn't figure it out for a couple days and just worked around it by allocating a user ram tile that I never used, but that bothered me. In order to make the testing of lollipops and spikes a bit faster, the lollipops are the very first tiles, so I can just do a single less than comparison with the tile number for a lollipop, and a single greater than comparison with the tile number for a hazard. That meant that my tile 0 was a red lollipop. Calling ClearVram() would end up filling the screen with red lollipops, and loading anything else after that would take longer than one frame, so you'd hit VSync, and you'd see half a screen full of lollipops, so I removed the call to ClearVram(). However, if you load a level and the unicorn is near the edge, not all of VRAM gets initialized, because you're up against the edge of a level. It turns out that the uninitialized VRAM memory was filled with RAM tile 0! So as the unicorn fell down a pit, it was being composed with that ram tile, which happened to be what backed a real sprite on screen, so that real sprite ended up showing part of the unicorn on top of it until it fell completely past. So again, I was able to make good use of the VRAM extent tracker, and detect if there would be any uninitialized parts of VRAM in Camera_fillVram, and then fill those parts myself before loading the level data. That gained me that RAM tile and my sanity back!
After adding sprites that can kill the player, I realized that I needed to allow the lollipops to disappear if you overlap them while dead, because if you jumped into a sprite, died, and then fell on a dissolving tile that had a lollipop stem on the same tile as it's top half, it would glitch, because it was counting on the lollipop not being there in order to animate correctly. That made me a bit sad, so if that happens I prevent the new Gulp sound from playing. Yes it disappeared, but you didn't actually collect it; you just knocked it down and it's hiding under your dead unicorn's body. And yes, now that I said that, it is canon.
Attached is the latest copy of my demo. If you kill a sprite and then go far enough away it will despawn and then be respawned again. They are stationary until I work out the best way to get them moving. I think this version is actually smaller than the first, even though it has more features.
One thing that has been a huge help has been using a hacked version of cuzebox that always records your inputs (compatible with uzem, so I can play it back in uzem and use gdbserver if I need to), and that also prints whisper port stuff to the console. If you want to try it, you can compile the debug-enhancements branch of my cuzebox fork: https://github.com/artcfox/cuzebox/tree ... hancements
I typically compile it once with replay enabled, and rename that executable cuzebox-replay, and then compile it with input recording, and keep that named cuzebox. That way any time that I'm testing something out and notice a bug or glitch, I have it recorded so I can reproduce it after I add some UZEMCHR = and UZEMHEX = statements to the code.