A complete technical rundown of how LSD works
1 year ago

There are 2 main types of LSD and they are both caused by the exact same oversight in the game's logic flow and on a fundamental level triggered by the same actions - saving in the moments between one event and another. I will refer to these as "Salts LSD" and "Level Load LSD". There also what can be considered a third variation (or rather, sub-variation of Salts LSD) which is a lot more limited (it can only send you to the brain tumbler) which I will discuss later. For now, I'll focus on Salts LSD and Level Load LSD.

The 2 main differences between the methods from an execution standpoint:

  • The timing window for Salts LSD is very different, with Level Load LSD being significantly easier due to the flow of the code involved.
  • Salts LSD will only work in cases where the game sends you back to the collective unconscious after using smelling salts. It does not work if the game sends you to the brain tumbler, and will not work if the game sends you back next to the character you're in the mind of. Generally, this means Salts LSD can only be performed once you've reached Meat Circus.

To understand how the bug works, you need to understand level scripts and global keys:

Every level in the game has a special level script entity. This entity is used for handling many different things depending on the level. Common functions for example involve setting up the skybox, registering certain triggers, initialising other entities in the level or storing information for different purposes. Often levels also have special functionality in their custom script such as the ASGR level script disabling the elevator if it's not meant to be active. All level scripts share a base class, but they exist entirely within their own levels. That's where the global class and global keys come in.

On a higher level is a class named Global.GlobalClass. This entity persists at all times from the moment the game starts to the moment the game is closed and handles a lot of different stuff. The important thing here is global keys. These are key/value pairs that any entity can freely save and load at will. These values are written to the player's save data and so are often used to save values that would be relevant in that case (such as progression or collectibles) but are sometimes used just to store and transfer data between entities.

With that out of the way...

PART 1: How level transitions work For the purposes of this explanation, I will start with the Level Load LSD as it is the most simple form of the bug. We need to understand how a normal level transition goes. For example, let's examine what might happen when going from the camp reception (CARE) to the main lodge (CAMA).

The moment Raz enters the level transition trigger, the game saves a global key called "teleport". This key is saved with the destination point of the trigger that was entered. In our CARE->CAMA example, the trigger's destination point is set to fromCARE1 and thus that's the value stored in teleport.

The level transition will then run the transition fadeout, before finally loading the appropriate level. Again, in this case, CAMA.

On loading into a new level at any point, the level's level script will run the function onBeginLevel. This function will, at some point, inevitably call into the base level script's onBeginLevel function. After some prepwork here, the base level script then runs the logic that decides where to position Raz. As it happens, the teleport location is the very first thing it checks, followed by Oatmeal, then a mental level kickout point, then respawn points and so on. In this case, teleport is set and so the game will then find the object with the name that matches the value of the teleport key and places Raz at that position. Finally, it clears out the teleport key. In our example, the game finds fromCARE1 and puts Raz at that object's position. Simple!

PART 2: How level transitions go wrong

That's all well and good, but how do we end up breaking this process and causing a Level Load LSD? Well, there is a significant period of time between the game saving that teleport key and the actual level load. During that time, it's possible to save the game. And that means that teleport key gets written to your save file.

And the result of that is that we now have a save file that tells the game we are in one level, with a teleport key intended for another level. In our example, our save file says we're in CARE but also has a teleport key of fromCARE1.

When we load a save that is in this state, the game will load the level it is told to but then the base level script's code for position Raz will run, and it will see that the teleport variable is set. And then the "magic" happens.

The code is incredibly naive. It sees the teleport variable and makes no attempt to verify that it is valid. It blindly assumes that the entity exists and will happily try to set Raz's position to match it. But we're not in the right level for that key. The entity the game is trying to place us at does not exist. And as a result the base level script hits several script errors, halting execution of the function.

As a result, Raz's position never actually gets set. This causes him to spawn wherever he's positioned by default on the level which is almost always 0,0,0 (the campgrounds seem to be an exception to this, but Raz still usually ends up out of bounds)

A side effect is that any code in the level script's onBeginLevel function that was set to occur after the call to the base level script doesn't get run. This very often results in a large range of side effects due to how important this code is. Uninitialised entities, no skybox, the Asylum lift not being disabled, all sorts of things.

Now, in the case of the campgrounds, as in our working example, this generally doesn't appear do a lot of damage because all campgrounds level scripts also have their own special campgrounds base which does things like setting the skybox before calling the base level script that runs the Raz spawn position code. In our CARE example though, if we make it back in bounds, we may still notice that various particle effects are missing and the stump speech won't work.

So, now we understand how a standard Level Load LSD works. How does a Salts LSD work and how is it related?

PART 3: Salts LSD

Salts LSDs, in principle actually work the exact same way as Level Load LSDs. After you reach Meat Circus, using smelling salts from then on will take you directly to the Collective Unconscious instead of to the real world. To do this, it goes down a special code path. Let's do an LSD from Meat Circus.

To figure out which door to position Raz at when he enters the collective unconscious in this manner, the game once again sets the teleport variable. In this case it automatically generates it by appending the level's 2 -letter code (in our example, MC) to the word "respawn". So, when going to the CU from Meat Circus, it places Raz at the locator respawnMC.

Again, in principle the Salts LSD works exactly the same - you save the game at a point after the teleport key is set but before the game has a chance to actually load the new level. The problem is, in the case of Salts LSD, the game does not give you ample time and indication to perform the save. The amount of time you have is essentially the amount of time between the Lua VM running the code to save the teleport key and the Lua VM running the code that calls into the engine to load the next level. It is absolutely no wonder that doing Salts LSD is so difficult in this case - that's some really precise timing with basically no solid way to judge when the perfect moment is.

PART 4: Outliers

There are some cases worth talking about the fall outside the bounds of regular LSD shenanigans. Namely - what happens when you try to Salts LSD a level before arriving at Meat Circus and how you're able to spawn outside the Asylum gate in ASGR when doing LSD. These two things aren't related.

PART 4a - Salts LSD before Meat Circus

Normally when you haven't yet reached Meat Circus, using smelling salts will put you in one of two places:

  • Next to the person who's mind you're in, if you entered their mind directly
  • Next to the brain tumbler otherwise

As a result, the code path that normally sets the teleport variable and all that never runs and so it's not possible to perform an LSD as we know it. There are still some side effects though. The game stores two other globals - one is known as LastBrainingLevel and tracks which level you last entered via using the Psycho-Portal. Another is bKickedOut, which is used to notify scripts that Raz is being "kicked out" of a mind.

Much like how the game runs Raz's positioning code on any level load, the game runs some code related to this variable too - if bKickedOut is set, then is set, then it will run the code relevant to Raz being ejected from a mind. In particular, if LastBrainingLevel is set, it'll run the cutscene for leaving the mind and then clear LastBrainingLevel. If you save and load after bKickedOut is set but before you exit the level, the game will run this code when you load back into the level, playing the cutscene and then clearing LastBrainingLevel. Since LastBrainingLevel is now clear, using the smelling salts again will now send you to the brain tumbler as the game believes you entered the level through the collective unconscious.

PART 4b - Spawning outside the asylum gate

This is completely unrelated to the above, and fairly simple. Essentially, you can do a Level Load LSD at the Asylum and still spawn outside the Asylum gates if certain conditions are met:

  • You must have beaten The Milkman Conspiracy
  • The last level you were in must have been Milkman. This means that if you happen to leave the area after beating it the first time, then to regain the effect you must re-enter Milkman via the Psycho-Portal, then use smelling salts to exit out again.

When these conditions are met, something interesting happens.

The bug still happens as usual, but ASGR has a special hack in it in code that still runs even after onBeginLevel fails. This hacky code checks if Milkman was the last level you were in, and then checks if Milkman has been beaten. If it has, it will set Raz's position to be outside the gate. This was actually only intended to ever run once, but a coding oversight results in it always running when these conditions are met. This stroke of luck is what allows you to LSD in ASGR and still spawn on solid ground, so long as you don't leave the level.

Edited by the author 1 year ago
Ink_, Grimdonuts, and AstraLumina like this