This December is going to be Dreamcast Conversion’s ninth anniversary. Can you imagine?
For earlier versions of the mod, I would play SA1 on the emulator and the PC version of SADX side by side, looking for as many differences as I could find, and trying to replicate them. By “replicate” I mean trying to recreate them manually without knowing how they worked in the original game, and making SADX do something that looks the same (or, in some rare cases, “close enough”). This approach worked well for visual accuracy (such as the title screen), and eventually, when I found out or realized how things worked, I would implement more faithful versions. This is what happened with the SA1 title screen, which was originally just a bunch of animated textures, and later a real model taken from SA1 and animated using the code from the X360 prototype. Or, more recently, the plasma cylinders in Final Egg.
There are still some things in SA1 that I couldn’t understand or reproduce by just looking at them, and today brings us to the conclusion of another 8-year-old mystery. Let me introduce one of the oldest issues on Dreamcast Conversion’s issue tracker: the so-called stand light in Final Egg.

From a quick look it seems pretty innocuous. What could possibly be so difficult about it? It’s just a pole with a bright triangular light beam.
As you walk around, you may notice a couple of interesting things. For each individual object the beam has different length and width. It also has an effect that looks somewhat similar to a billboard, but not quite. The light looks mostly the same from different angles, except that it “flips” when it’s looking directly at the camera. Here is a video of the effect in action (30MB file warning: might take a while to load):
Now let’s see what the object looks like in SADX:
![]() | ![]() |
In the ports, all instances of this object share the same beam, and there is no adjustment to its width or length. However, this beam consists of two flat parts, which makes it look odd. It doesn’t rotate in any way, so it appears like two flat surfaces perpendicular to each other.
Figuring out how to get beam length and width wasn’t difficult: they’re stored in the SET object’s scale values. I even made the game hide either the “horizontal” or the “vertical” part of the SADX beam, and make the remaining beam wider or longer depending on what object it was. However, all my attempts to get the light to rotate like in the original game were unsuccessful. It looked “good enough” for the most part, but it just wasn’t accurate. Back when Ghidra wasn’t released yet, trying to figure out what the Dreamcast version does in code was out of the question (at least for me), so I just did the best I could and left the issue hanging, hoping to figure it out someday.
After Ghidra’s SH4 decompiler became available, the perspective of decompiling the Dreamcast version of the effect became more realistic. Of course I still suck at Ghidra, and its pseudocode is still a mystery to me. However, with people like woofmute and Exant being in the scene, it seemed like it would be worth a try.
The first attempt to understand what this object did dates back all the way to 2020, when Exant started looking into it. We had no idea how deep the rabbit hole goes. Originally, because the weird two-sided beam is part of the model in both the DC and DX versions, I assumed the DC version simply removed one side of it for rendering. However, it turns out the DC version doesn’t use the beam model at all, and instead draws it using one of the “wide line” functions. The “wide lines” are still used in SADX, though rarely, to draw such things as the “crush light” (the beam effect when the E-series robots explode), as well as some effects in ZERO and E101R boss fights (some of which have billboarding issues on PC, but that’s a story for another post). However, the function that draws the Final Egg beams takes an angle argument, and none of the functions in the PC versions can do that. Exant tried to recreate that function, but at that point in time it didn’t work, and we gave up.
The second attempt, which happened just a few days ago, was much more successful. Using code from a damaged X360 prototype that had unused functions compiled into it as a reference and adding missing bits from decompiled code from the Dreamcast version, Exant reconstructed the version of “draw wide line 3D” that we needed in addition to a matching decompilation of the object’s functions (and adjustment for different resolutions rather than just 640×480). After a lot of tinkering, it was finally there:
As you can see, the original code still needed some adjustments for the beam to look good in SADX, so I did some of the usual transparency shenanigans with blending modes and queuing it for drawing with a custom callback. As I was getting ready to release a new update for Dreamcast Conversion, I noticed something strange… Maybe the math was wrong somewhere?
It took us a while to figure out what was going on because Exant couldn’t reproduce it at first, and some time later I couldn’t reproduce it consistently either. Finally we realized that it behaved differently in DirectX 8 and DirectX 9 modes, as well as between nVIDIA and Intel GPUs. It appears that some GPU drivers implement T&L vertex clipping (indicated by the D3DPMISCCAPS_CLIPTLVERTS
capability) differently, so some people might get that glitch and some wouldn’t. Exant proposed two workarounds that made the glitch less visible and I was personally happy with them. The near-final result looked good enough to me.
However, there was also the idea to use the function D3DXVec3Unproject
to undo the screen space calculations and prevent the glitchy projection from happening in the first place. This worked even better, and the effect is now glitch-free on any GPU without workarounds while staying faithful in its looks to the Dreamcast original.
I had no idea how involved recreating this effect was, and I’d like to thank Exant for resolving another long-standing mystery of SA1 vs SADX differences. The updated version of the object is now in Dreamcast Conversion.