[Release] BoulderCaves+, a Krissz Engine compatible remake

Everything about the modern clones and remakes.

Moderator: Admin

Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

Ok, I tried to patch that crash, pushed some additional code to Git. Will work on a better fix tomorrow, but hopefully this will make it work already ;)

- Agetian
DarkStoorM
Member
Posts: 15
Joined: Tue Dec 14, 2021 10:52 pm

Post by DarkStoorM »

I noticed a couple things:

- scaled font is blurred, is the font scaled differently? I think the Nearest Neighbor is not applied to the font sprite.

- on C64 and Modern variants, the game will not play the "last requested sound", but all at once - I believe that's unintented - for example, when two boulders drop at the same frame, two sounds are played. it gets even worse when more boulders start falling or stop at the same frame :D

Although, I can see the flag self.diamond_sound_played = True for Boulder/Diamond when Krissz engine is selected. I think this should apply to all game variations.
Just in case, a quote from Peter Broadribb's BD page:

Precedence of sounds
Since the C64 has only three "voices", it can play a maximum of three different sounds concurrently. Here are the rules for deciding which sounds get played:
Voice 3: Crack sound gets highest priority, followed by amoeba sound, followed by magic wall sound.
Voice 2: Used for the sound of Rockford moving.
Voice 1: Used for all other short sounds. Last in, first served. In other words, the last object in the scan that requested a sound (if any) is the one that gets played. Once requested, the sound gets played to completion; any other requests that come in while that sound is playing are ignored.

^-- notice Voice 1, I believe this is how it's done in GDash. I also believe Krissz has separated every sound in a way that it has its own "voice", but still playing the sound of the last scanned object of given type, not sure. I went with such solution - storing certain sound requests, then after the scanning routine is complete all sounds are played, but only once for each kind

- editor should point in the current game directory when saving, if possible

- I'm a bit worried about the performance, it should not use this much processing power. I'm not familiar with Python, so I won't be able to find what is happening or profile the code :D I think this might be the reason why not every frame has the same exact timing. You can hear that in the movement sound, it's inconsistent, but the same happens in GDash and it's uncomfortable to play faster caves.

Not really sure what is causing it to use this much CPU, but perhaps the cave is rendered more frequently than 60 frames per second? If not, maybe the cave scanner is the culprit here.
Is the cave colored only once or every frame?

I'm trying to use cProfile to find out what is happening, but no luck so far. cave scanning routine seems to be fine - not sure how to read the function calls below... if that's total per frame or what :D can't really tell how frequent are these calls from the console output speed

game.py:565

Code: Select all

def update_game(self) -> None:
        if self.popup_frame < self.graphics_frame:
            cProfile.runctx("self.gamestate.update(self.graphics_frame)", globals(), locals(), None, "ncalls")
            #self.gamestate.update(self.graphics_frame)
Image

I will try to poke around a little more :D
User avatar
Chris Neilson
Member
Posts: 33
Joined: Wed Aug 29, 2007 2:53 am

Post by Chris Neilson »

Thank.

Its working
Image
Boulder Dash Construction Kit
Crazy Light Construction Kit
GDash <> XDC-engine
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

Thank you so much for the feedback, DarkStoorM! I'm happy to see that our concerns and our investigations are mostly going in the same direction. Indeed, the original sound playback algorithm in Boulder Caves was quite cacophonic, I noticed it when I played Blifil on it and it sounded ear-splitting :) I'll apply the current method applied in Krissz Engine mode to all modes, and later I'll try to rewrite it according to the guidelines you posted for the sound priority, that sounds clean and straightforward.

When it comes to performance, I'm a bit puzzled too. I also tried cProfile and spent quite a lot of time measuring things in it. What that output above shows is that the cave scanner gets called a lot (880 times is probably a full scan of the 40*22 cave), but doesn't take much time doing so (0.000 sec total for the update call), so it's probably not the thing affecting the performance the most, and it's probably the graphics subsystem which is, for some reason, not actually shown in this profile (and the same in my tests too, nothing tkinter-related pops up there at all?). What I know for certain by now is that the cave scanner is no longer the culprit, at least in most scenarios. It actually used to limit the game performance more than other things before, but now that it's switched to per-ID checks and also some stuff in it was rewritten, in most typical cases and outside of some artificial "overload" tests it performs fine compared to the graphics subsystem.

Now, the graphics subsystem is a bit of a mystery performance-wise. I'm actually not sure what can be done under the current framework (Tkinter/Pillow) to improve performance, but a suggestion that maybe the refresh is happening too often is an interesting one - I'm going to run some tests to determine if that's happening. The "repaint" routine is what's responsible for the refresh. It's called from tick_loop, which supposedly should only be called at 60 fps intervals, so not sure if it does actually get more calls than needed to meet the desired frame rate somehow... maybe the math is messed up somewhere which deals with when to call graphics updates?... Outside of that, however, there seems to be little that is within direct control that can significantly shift the performance towards acceptable levels on hardware where the game currently underperforms. I have a strange suspicion that if we switched to a different framework (e.g. SDL 2), especially one that allowed hardware acceleration, things would have been much different here. I don't think Tkinter/Pillow is normally meant for high frame rate gaming, but it is what it is, until there's a complete overhaul of this graphics subsystem, I think it's still worth figuring out what can be improved with this particular design.

Nice catch regarding the font scaling - indeed, something that's also worth looking into! :) Interestingly, the font loading routine in tiles.py (load_font) seems to apply SCALING_NEAREST to it, I wonder what's causing it to be blurry, hmm...
EDIT: Oh, it actually overwrites that with SCALING_HAMMING, maybe the PNG file has some attribute set?.. Anyway, I'll stick to SCALING_NEAREST for now.

- Agetian
Last edited by Agetian on Tue Dec 21, 2021 8:09 am, edited 1 time in total.
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

UPDATE (12/21/21): Version 1.1.0 has been released.
This is a minor bugfix release addressing several stability and functional issues raised in the Boulder-Dash.nl thread.

* Construction Kit now points to the "caves" directory by default when loading and saving caves
* Construction Kit now starts the playtest in extended open border mode if this mode is enabled in the launcher
* Sounds are less cacophonic in C64 and Modern modes (same as Krissz Engine mode at the moment)
* Fixed a crash in playtest mode related to the extended open border functionality
* The High Score box will no longer nag you until you type something into it, it's possible to cancel the entry and avoid saving the high score
* Fonts are no longer blurry when scaled

- Agetian
DarkStoorM
Member
Posts: 15
Joined: Tue Dec 14, 2021 10:52 pm

Post by DarkStoorM »

Something is wrong with Synthesized sounds, the game crashes upon finishing the cave.

When I remove the repeat assertion in line 186 (synthsamples.py), it works, but the last 10 seconds will sound weird :D like the game tries to play the next 10 samples without interrupting the previous one

Image
zsom
Member
Posts: 84
Joined: Sat Apr 10, 2010 5:46 am

Post by zsom »

Agetian.
Thank you for the effort you made.

And there are a few Rockfords, Boulderdashplus are also working.

greetings
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

DarkStoorM wrote:Something is wrong with Synthesized sounds, the game crashes upon finishing the cave.

When I remove the repeat assertion in line 186 (synthsamples.py), it works, but the last 10 seconds will sound weird :D like the game tries to play the next 10 samples without interrupting the previous one
Hmm, this seems to be an issue with the upstream Miniaudio library or the way it interacts with Synthplayer (another external library BoulderCaves(+) currently uses for the audio playback), and it could be one of the Windows-specific issues mentioned by Irmen de Jong in the original Bounder Caves documentation (it says there that on Windows there could be some audio issues). Not sure if I can easily resolve that somehow, will need to take a look though :)
zsom wrote:Agetian.
Thank you for the effort you made.
And there are a few Rockfords, Boulderdashplus are also working.
Thank you for the kind words, zsom! Much appreciated! :) Very happy to hear that you like the project so far!

- Agetian
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

I checked the coloring of the cave and it looks it's only applied once - when the cave is loaded and also if you press F8 to randomize the colors, then it's reapplied, but also only once.

- Agetian
DarkStoorM
Member
Posts: 15
Joined: Tue Dec 14, 2021 10:52 pm

Post by DarkStoorM »

Are you able to somehow profile the whole rendering?

I just had a random thought at work, that the game maybe tries to render the entire cave, along with the tiles that are not visible (unnecessary draws).

I actually made this mistake in my project where I loop through the Cave-X and Cave-Y rather than the Canvas-X and Canvas-Y for repainting, which just made the game really laggy in caves above 200x200 tiles (it tried to render objects outside of the visible area) - but let's be honest, no one would play such caves :D

I wonder if Python has some sort of full profiler like you can see in Unity. Unity's profiler analyzes the entire application and shows at the real-time what is happening each frame, with extreme details, diving deep into the stack frames.

This is actually really important, because this remake has big potential. Once you manage to get rid of the high CPU usage and the inconsistency with Rockford's movement, you are good to go.

To give you some more details: when you move Rockford around and take a moment to listen to his movements, you will hear that the timing between the played sounds is different. This is very important, because once you get used to Rockford's movement timing, you will sort of start to use his sounds as a metronome, which then translates to perfect control.

Inconsistently played sounds completely disrupt your flow and increase the movement errors :D

To give you an idea, take a look at the comparison between BC+ (upper graph) and Krissz (lower graph).
Occasionally, there is a weird spike in timing, where some frames appear to be delayed, I can't really explain this. The same actually happens in GDash, where in Krissz engine the timing is perfect (there are some rare spikes, but unnoticeable), works the same in mine.

I marked the big spikes with exclamation marks in the BC+

Image

I guess browsers handle this differently since the cave scanning in our projects run on the JavaScript's "SetInterval" function - executing exactly after n-milliseconds, which can only slow down when the CPU is really busy.

I don't really know, maybe there is some weirdness with delta time in standalone applications :D maybe this could be the clue
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

Very interesting, DarkStoorM, thanks for the analysis! I'll ponder over this and I'll see if I can profile this in a better way to see what's going on exactly. It's kind of strange there seems to be a "pattern" to these spikes - with it jumping between 0.95s, give or take, and then going to a value of 0.125s (more or less) for a single step, then going back to the normal 0.95-ish updates. It's also interesting that this also plagues GDash, I didn't know that - do you know if it happens in all back-ends? I know that GDash supports a few, including software rendering of some sort, Qt, and SDL (2?) back-ends.

When it comes to rendering only a portion of the screen, I actually thought so a while ago too, and I even optimized the update routine somewhat, making it account for a portion of the field - unfortunately, I couldn't make it refresh only the visible part, I had to give it a bit of an extra at the sides due to scrolling, otherwise there will be missing parts when the screen scrolls too quickly. I'll think about how to do this better later, too. This improved the situation, but not as drastically as I hoped it would, and it didn't get rid of the "spikes".

Also, what's a bit worrying is that spikes happen even in 30 fps mode, which should actually be pretty "mellow" for the CPU. This kinda hints at the fact that something's just not doing things right somewhere, but I wish I knew where exactly and how to fix it :D

- Agetian
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

Ok, testing our performance and timer issues a bit more and so far, it looks like we're dealing with two possibly related, yet separate issues - the issue of "performance" itself is one thing, and is possibly best addressed by switching over to a hardware accelerated graphics stack (outside of the possible efforts to improve various portions of the redraw, such as minimizing the number of updated elements per frame where possible).

However, the "timer" issue (those spikes that cause a certain asynchronicity in the steps and whatnot) appears to be separate from the issue of performance. That is, even in case there's no performance limitation (e.g. playing a very small cave in 30 fps mode), the lag spikes still happen, which sort of makes me think more in the direction of the implementation of the timer itself and possibly the way the game updates are synchronized to this timer.

The first thing I tried was switching from Python's "perf_counter" timer to the Clock timer implementation in Pygame, which uses SDL 2 and which is a legitimate gaming library. However, it seems that just switching implementations doesn't cut it, and there's still the issue of uneven footstep sounds and stuff. This sort of makes me think that maybe the updates aren't properly synchronized to this timer and don't happen exactly at 60/30 fps intervals, but sometimes fractionally, but I haven't been able to determine the cause of that yet, at least nothing I tried produced the desired effect :/

- Agetian
DarkStoorM
Member
Posts: 15
Joined: Tue Dec 14, 2021 10:52 pm

Post by DarkStoorM »

I played around with fps and delta time in the code, also no luck :D even tried setting the time step to something ridiculous like 1000 updates per second just for a test, there was almost no difference. I thought it would give some higher accuracy, but still nothing :D

I don't really know where to look anymore. I'm sure you will find it eventually :D

By the way, I randomly found out that the scaling doesn't quite work.

Check the size in pixels for each scaling factor (below each sprite) in BC+
4x is really weird, not sure where size of 40 comes from, but that's really odd. It should always be a multiple of its own size in order to maintain the pixel-perfect sprites (16 in this case)

Image

Now this is wrong I believe. It should be:

1x - 16x16
2x - 32x32
3x - 48x48
4x - 64x64
5x - 80x80 (this is the optimal max for most big screens to fit a canvas of 20x12 tiles, this is my 5x canvas right now https://i.imgur.com/t3oBZJq.png)
6x (but you are using fullscreen, so I guess it's just dynamic resolution(?)) - 96x96

6x is nice, but we are entering the aspect ratio territory. The closest "perfect" fullscreen is 20x11 (1920x1056 pixels), but that leaves you 24 pixels for the UI :D... unless you plan on playing on the TV
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

Yeah, I don't have any luck with the "fps mystery" either so far, but I'm still looking and investigating :)

And omg yes, those fractional scaling values in the original Boulder Caves that are currently in use in Boulder Caves+ are a bit of a mess - not only visually, but also they cause side effects and don't work with certain stuff such as extended open border and for some reason the "authentic C64" mode as well. I also agree that they need to be removed and replaced only with integer scaling where each element would be scaled to an exact integer multiple of its own size - I'll do that either later today or this weekend tops, it's long overdue by now, thanks for noticing it as well and reminding me to get rid of it :D

EDIT: Oh, and judging by your findings, something also appears to be broken with interpreting scaling as well - I wonder why x2 and x3 come out the same as well. Definitely going to look into it this week.

- Agetian
Agetian
Member
Posts: 78
Joined: Sun Sep 19, 2021 10:14 am

Post by Agetian »

UPDATE (12/23/21): Version 1.1.1 has been released.
This is a bug fix release that addresses some of the recently reported issues.

* Fractional scaling is no longer used, as it was causing graphical distortion and functional side effects in certain modes.
* The game launcher will now save the settings prior to launching the editor, which is relevant for extended open border mode support while playtesting caves.

- Agetian
Post Reply