At AscentCore, we invest in world-class talent to ensure we can build and deliver innovative digital products for our clients. Part of investing in our team includes encouraging and cultivating their interests outside of their day-to-day activities at AscentCore.
AscentCore CTO Cornel Stefanche recently sat down with Vlad Popescu, senior software engineer, to talk about his background in programming and his experiments with procedural generation – the creation of data through computers – and how its use could be expanded beyond gaming to business cases.
Cornel Stefanache: Vlad, can you start us off by sharing more about your background and what you’re working on?
Vlad Popescu: I’ve been doing stuff with computers since I was a child and I’ve always had fun experimenting with them. I’ve always had a passion for front-end development and I like animating and stylizing things, especially in coding.
I’ve been developing professionally for close to eight years now and I’ve been working on several game engines with a bunch of tech stacks for making UI applications.
Cornel Stefanache: We’re really excited to have you on the AscentCore team! Now, let’s dive into the topic at hand because I understand you have a lot of experience with procedural generation. Can you share a few examples about how you’ve applied procedural generations in your experiments?
Vlad Popescu: One of the things I love about procedural generation is that you can use the same concepts in UI, not just gaming, where it’s traditionally used to build things like 3D worlds. I’ve made some UI systems that were mostly based on prototype patterns where you would essentially have code that would define how something should be built. The system was fragmented in such a way that you could generate a UI from very basic fragments that would build something more complex – Essentially, you’d have something that defines the design, and then you have a library that your system picks the components out of and starts creating the interface – like a map.
Cornel Stefanache: So whenever you want to apply procedure generation, do you create a certain set of rules or do you start from random noise, such as latent space, to make meaning out of that noise?
Vlad Popescu: It’s actually a combination of the two. You may have something that needs to be predictable with an abstract set of data that needs to be used to build your final product. In that case, you don’t use any noise. Noise functions are usually the answer for when you want to have variants that don’t feel robotic. The simplest example is starting from something like Perlin noise and generating a landscape.
Cornel Stefanache: Let’s talk a little bit about the new Wave Function Collapse, of which I’m a really big fan. It’s such an interesting aspect of generating randomized content. There is a high risk for both randomized procedural generation and Wave Function Collapse, where their generated map can reach a dead end.
One option I thought of could be pathfinding – so if you think you’ve reached a dead end, you can randomize terrain until you have the possibility to traverse point A to point B. Do you have any algorithm that can prevent this risk?
Vlad Popescu: The problem with using something like Wave Function Collapse is that when you restart, you need to cache whatever you generated because while it has rules, it’s still random. So if there’s a possibility to choose between map segment X or Y, it will choose one of them, but you can’t predict that for the next iteration, it’s not deterministic
Cornel Stefanache: Do you think you can apply procedural generation to behavior, such as MPC? For example a basic video game enemy that takes flight. Could you replace fully random behavior with procedurally generated behavior?
Vlad Popescu: You could have a state machine for the enemy that checks weights for some actions that it can do, and then these actions can have weights that lead to other actions, so that you get behavior that feels natural. This is also similar to Wave Function Collapse, which can be implemented based on a graph where you define the way your vertices (i.e. actions in this case) relate to each other via weights.
If you have a convincing enough rule set, you could just have a one dimensional array of whatever the enemy can do, and these actions can just “ping” each other- that is, you define your behavior as an array of possible actions, along with the chance that these actions can happen at any point in time – it’s the equivalent of constantly asking a person what they would do next in a situation, where every action has a different chance to be taken.Admittedly, I’ve only talked about random behavior but you could apply decisional logic to enhance that behavior and increase its perceived entropy, for example. An anecdote to illustrate this scenario is music apps. Sometimes when we use the shuffle feature, we think it doesn’t seem random if we hear three songs that are consecutive in our playlist. But the problem is that the randomizer just so happened to choose the same exact order out of 5,000 for three tracks, and those three tracks just came up at some point – so you can have something that’s seemingly “more random” than something else, even if its output was manipulated by decisional logic.
Cornel Stefanache: Let’s talk more about this pseudo-randomness, particularly around generating numbers. I faced an issue thinking that I was generating seeded random numbers, but then I realized that only 90% of my generator was using the seeded random number generator. So I had accidental randomness in my seeded random number generator which was hard to debug. Have you experienced anything like this?
Vlad Popescu: I have actually faced the exact same issue. I developed a system where a procedurally generated game received a multiplayer feature later down in development, and suddenly there was this problem where both players needed to have the same randomly generated map, which meant seeded random numbers had to be used. When the two players would find each other, the game would negotiate the seed. The problem was that in some cases, because of some user behavior, the seeds would get desynchronized. So the moment you desynchronize to seeded random number generators, they become unpredictable, and start generating different outputs. The random factor desynchronizing the seeds here was the user, so that was an application bug, as the two players were intended to receive the same exact copy of the randomly generated, infinite map. However, since some unpredictable behavior was not accounted for, the system was “sidetracked” and the players kept ending up with divergent maps.
To solve this, I came up with a solution to create multiple number generators where I isolated everything that would be vulnerable to unpredictable user behavior. So as long as they’re isolated enough, every stream of these random numbers can be synchronized. Like that, they both get the same play field, they both get the same power ups, the same enemies, or whatever your system might include. By parallelizing the streams of random numbers, the two players would no longer desynchronize, as their actions no longer influenced the procedural generation.
Cornel Stefanache: Let’s go back to the topic of music for a moment. Do you think that you can apply procedurally generating algorithms to a more subjective field like music?
Vlad Popescu: You can have procedural generation in music, at varying degrees of granularity. In the 12 temperament system that we use, which corresponds with the 12 keys on the piano, you can take a subset of those 12 keys that will usually, in some patterns, sound good. You can apply Wave Function Collapse to generate the melody itself. If you use a synthesizer engine, something that can play the music dynamically generated on the fly, there’s nothing stopping you from making dynamic phrases. If you use the correct subsets of notes, you could generate stereotypical music on the fly and then combine it with pre-made percussion. You could then add the rest of the layers such as the bassline by deriving it from your main melody.
If you generate your melody by picking up pseudo-random notes from a musical scale, it can feel random, or iit can feel that it doesn’t “lead” anywhere. An alternative is to use bars and stems: You record everything into sets of 1, 2, 4 bars – which are little, seconds-long snippets of music – and you split them along the different tracks for your instruments, which are called stems. You can then sequence those dynamically in your application. You usually want to go with something that follows the pattern of introduction, bridge, chorus, maybe another bridge and outro. Your application then can decide which instruments it can bring into the mix, by adjusting the volume of your stems, and, like a digital “DJ”, it builds the melody by linking the separate music snippets together. This would work well for video game music for example, if you’re playing a game where there is a mechanic of going up, and you go up a certain amount of floors, another instrument starts playing; but then as you also accumulate some other metric in the game, let’s say the amount of points, maybe the game starts to loop the next parts of the melody which may be more intense, something like a bridge or a chorus. If you design this well enough, you’ll end up with something that sounds like the music is actually playing along with you in the game.
Cornel Stefanache: I think we could go on all day about this! Thank you for sharing your expertise – I’m looking forward to discovering how we can incorporate procedural generation into future projects.
If you’d like to learn more about the AscentCore team and what we’re working on, visit our website at https://ascentcore.com/.