Final Egg plasma effect mystery finally solved

If you’re into all things SA1, you probably know about the plasma texture effect in Final Egg. It appears on spinning cylinders and traps, and looks like this:

Like many other cool things, the effect was removed in the Gamecube version and later ports. Instead, there’s a simple 6-texture animation that looks a bit awkward:

Of course I wanted to restore it in Dreamcast Conversion. However, the textures for this effect aren’t in any PVMs. For a long time we had no idea how it works in the original game. After the first release of Dreamcast Conversion in December 2016, I talked to SonicFreak94 about the possibility of recreating this effect, and he suggested trying environment mapping. If you have played earlier releases of Dreamcast Conversion, you may have seen our first attempt at it. It was made by SonicFreak94 and it wasn’t compatible with Lantern Engine, so you had to pick between lighting and this effect. Still, I think it looks decent for what it was. Don’t you think?

That said, I was hoping to figure out a more faithful solution, and to make it compatible with Lantern Engine. Since I didn’t know how the effect actually worked, I decided to fake it by capturing a video of it and making a texture animation.

Besides spinning cylinders (OTatekan), this effect is also applied on a flat object (OTexture) that can be seen at the bottom of pits in Act 3. Since that object is just a flat surface, it can be used to look at the effect more clearly. By editing the SET file, I was able to put an OTexture on a black background, point the first-person camera at it and capture the effect in the crudest way possible:

For comparison, here’s the same object in SADX (the size is actually the same, it looks bigger because of a different position against the camera):

Although nowhere near efficient, this approach let me put together the following texture animation. It looked a lot more complex than we originally thought.

Although the texture could now be used in Dreamcast Conversion, seeing this left more questions than answers. How could this effect be achieved? Some kind of texture palette manipulation? Abuse of the Dreamcast’s GPU features? Was that why it was removed in SADX? Although I didn’t have any answers at that point, at least I had the texture to put in the game. I think it turned out pretty nice.

Some time later I came across redream, a Dreamcast emulator that was new at the time. Earlier versions of redream helped me understand a lot about SA1’s sound queue system thanks to various features of its debugger. It also had an extremely useful feature that let you trace the state of the GPU and dump all textures across a certain time period, which was where I finally saw the “real” texture for the plasma effect:

The texture’s pixel format is ARGB1555, which leaves out texture palette manipulation. Although still puzzled, I was able to produce a more faithful animation for Dreamcast Conversion. This animation was used in the mod all these years until yesterday. With it, I was able to make the effect look exactly the same as in the original game (thanks LastBreath for helping me arrange the textures and put them in a PVM).

Although it was good enough for the mod, I was still wondering how the original effect worked, and started asking around. The first useful hint was given by MetalliC, the developer of the Demul Dreamcast emulator. He said the effect looked like the classic plasma effect that was featured prominently in the demo scene a while back.

Now I knew the texture was generated using some clever trigonometry tricks, and that there were open-source examples I could try to recreate it. I was also curious about the actual implementation in the game: does it modify texture data directly? Is it pre-generated or real time? At least I knew what I was looking for, and with some things I learned since my last attempt, I decided to try to decompile it.

Starting with the texture list used by the objects in question, I was able to trace it back to the task that manages the effect, and decompiled almost the whole thing. Two functions that did the most important stuff were a bit out of my reach, but Exant did them for me, even matching them with the original SH4 disassembly.

I was expecting to see a lot of njSin’s or njCos’s but it turned out the effect uses precalculated values for the most part, and shuffles them a little. Before the effect starts, the following function runs once to fill out an array of precalculated sine values:

void AnimSet() // 0c91126c, decompiled properly by Exant
{
	int ang = 0;
	for (int i = 0; i < 256; i++) 
	{
		animAngles[i] = (njSin(ang) + 1) * 32.0f;
		ang += 256;
	}
}

The function that executes the effect uses six arrays of hardcoded data to fill in texture data directly:

void AnimExec(task* tp) // 0c9112e8, decompiled properly by Exant
{
    taskwk* twp = tp->twp;
    Uint16* animTexDataByte = animTexData;
    int animjump = (int)twp->scl.y;
    if (animjump > 6 || animjump < 0)
        animjump = 0;

    {
        Uint16* currTable = animTables[animjump];

        Uint8 c0 = twp->counter.b[0];
        Uint8 c1 = twp->counter.b[1];

        int y;
        for (y = 0; y < 64; y++)
        {
            int x;

            surface0 = twp->counter.b[2];
            surface1 = twp->counter.b[3];

            for (x = 0; x < 64; x++)
            {
                Uint8 angle = (Uint8)(animAngles[surface0] + animAngles[surface1] +
                    animAngles[c0] + animAngles[c1]);

                *(animTexDataByte++) = currTable[angle];

                surface1 += 3;
                surface0 += 1;
            }

            c0 += 2;
            c1 += 1;
        }

        twp->counter.b[0] += 1;
        twp->counter.b[1] -= 2;
        twp->counter.b[2] += 3;
        twp->counter.b[3] -= 4;
    }
}

Why six arrays? The task’s Y Scale value is used to pick which array to use the data from. At first I thought the Y Scale value changes at some point, but it actually always stays at 0, so the other five arrays are never used. Was this effect supposed to change depending on where the player was, or depending on what object it was? We don’t know, but at the very least there are five variations of the effect in addition to the used one. You can check out the variations in the US 1.005 version of the game by setting the float at 0C81F8D0 (2C81F8D0 in Demul) to 1.0, 2.0, 3.0, 4.0 or 5.0 while the stage is loaded. Interestingly 0 and 2 look the same even though the data for them isn’t identical, while the other variations stand out with different colors:

ValueEffect
0 (default)
1
2
3
4
5

The original game keeps track of the number of traps and cylinders loaded, and stops the effect if there are no objects using it. As such, there is only one texture being generated, which is reused for all objects. However, nothing is stopping us from generating all six of them in Dreamcast Conversion! In the update to the mod released today, you can select the variation of the effect to use in the mod’s configuration, including a “random” option that will assign a random variation to each new object. Check it out!

As for why this effect was replaced in SADX, I don’t have a definite answer. Since it uses a lot of precalculated data, there is no technical reason it couldn’t have been done in the Gamecube or PC versions exactly the same way as in the original. Perhaps it was a stylistic choice?

Curiously enough, the SADX replacement uses six textures, matching the total number of data arrays in the SA1 version of the effect. To implement all of the variations I also had to expand the texture list to 6 entries. Perhaps it’s simply a coincidence.

This concludes another SA1 mystery that’s bugged me for over 8 years. It’s amazing that after all these years of research we still find unused things in the game.