546 - bombShield(int a, int script)


Sets the bomb invulnerability flag (a=0 false, a=1 true), the caller's ANM script will change to script when a bomb is active.

611 - etEx(int etId, int async, int type, float a, float b)


Adds bullet transformation to bullet manager etId, Transformation of flag type determines the specific behaviour of the bullet. Documentation can be found here.

BulletEffectType-67108864 - etExDelay(string et, string isAsync, string time)


Hide the bullet for time frames. During this time, it will not move, will be invisible and won't have a hitbox. If used as a first transformation, can be used to delay when the bullet actually appears.

6 - isDelayedSpawn


While this flag is set, the bullet will be invisible and not have a hitbox. This flag is cleared once the delay timer ends.

4 - short_timer


Integer value between -1 and 15 inclusive (by default -1). If non-negative, shooters using short_timer shoot their bullets and timer increments by 1 per frame until 15 is reached. Once 15, timer reset to -1 or 0 depending on Z key state.

5 - long_timer


Integer value between -1 and 120 inclusive (by default -1). If non-negative, shooters using long_timer shoot their bullets and timer increments by 1 per frame until 120 is reached. Once 120, timer reset to -1 or 0 depending on Z key state.

503 - flagClear(int n)


Clears flag(s) according to n. Refer to the flag table here for exact values.

91 - floatTime(int slot, float var, int time, int mode, float start, float final)


In time frames using mode mode, variable var changes from start to final. slot is used to set the slot to be used by this ins, every enemy has 8 slots.

-9980 - F1


Local float variable, inherited by spawned enemies.

-9984 - I1


Local integer variable, inherited by spawned enemies.

-9989 - ANGLE_PLAYER


Angle from the enemy to the player.

-9962 - BOSS_Y


Final Y position of the boss.

-9990 - PLAYER_Y


Player's Y position.

-9963 - BOSS_X


Final X position of the boss.

-9991 - PLAYER_X


Player's X position.

81 - circlePos(float varX, float varY, float angle, float radius)


Performs following operation: varX = cos(angle) * radius and varY = cos(angle) * radius

-9981 - F0


Local float variable, inherited by spawned enemies.

300 - enmCreate(string sub, float x, float y, int hp, int score, int item)


Creates an enemy using subroutine sub at coordinates (x, y) (relative to position of the parent), health of created enemy is hp, score bonus is score and item drop is item.

601 - etOn(int etId)


Shoots bullet(s) using properties from bullet manager etId.

23 - wait(int time)


Stops sub execution for time frames.

1 - delete()


Returns to the top of current call stack.

2 - GAME_SPEED


The value controls by how much faster the game speed is run. This variable is often set to a number between 0 and 1. This value is often changed when a (final) boss dies or when a photo is taken. Its default value is 1.

-9985 - I0


Local integer variable, inherited by spawned enemies.

-9983 - I2


Local integer variable, inherited by spawned enemies.

-9982 - I3


Local integer variable, inherited by spawned enemies.

-9954 - ENEMY_HP


Enemy's current HP.

3 - goheiExists


A flag that determines if a gohei exists on-screen or not. Flag is set to true by active gohei. Flag is set to false if gohei goes off-screen or is despawning.

-9926 - GI0


Global integer value. Value is initialised to 0 on stage load but not on stage reset.

-9925 - GI1


Global integer value. Value is initialised to 0 on stage load but not on stage reset.

-9924 - GI2


Global integer value. Value is initialised to 0 on stage load but not on stage reset.

-9923 - GI3


Global integer value. Value is initialised to 0 on stage load but not on stage reset.

17 - distortion(int a)


Triggers distortion effects on the edge of the screen. The meaning of the argument is not entirely clear. SA uses a value of 1 to create distortion at the bottom of the screen, while UFO uses this same value to create distortion at the top.

539 - spell3(int id, int timeRate, int unused, string name)


Declares a spell-card with id id and name unused. The ID passed to the spell-creating function is id + difficulty - 2 (with easy=0, normal=1, ..., overdrive=5). timeRate determines how much time the spell circle takes to shrink (in frames), as well as how fast the spell bonus decreases.

514 - setInterrupt(int slot, int hp, int duration, string subroutine)


Sets an interrupt on slot slot. Once caller's HP reaches hp or duration frames have passed since this instruction was called, the caller will terminate all subroutines it's currently running and execute subroutine subroutine.

270 - enmCreate270(string sub, float x, float y, int hp, int score, int item)


Creates an enemy using subroutine sub at coordinates (x, y) (relative to position of the stage background camera), health of created enemy is hp, score bonus is score and item drop is item.

Merlin, Lyrica & Lunasa Glitch


Specifications


  • Versions: 1.00 - 1.00a - 1.00b
  • Difficulty: Easy - Normal - Hard - Lunatic - Extra - Phantasm
  • Mode: Main game - Practice mode
  • Shottype: ReimuA - ReimuB - MarisaA - MarisaB - SakuyaA - SakuyaB
    Table of contents
  1. What happens
  2. How it happens
    1. Merlin's set-up
      1. Merlin's subroutines - non-spell 2
      2. Merlin's subroutines - spell 1
      3. Merlin's subroutines - non-spell 3
      4. Merlin's subroutines - spell 2
      5. Merlin's full call stack - Final spell-card glitch
      6. Merlin's call stack - Post-boss glitch
    2. Lyrica's set-up
      1. Lyrica's subroutines - non-spell 1
      2. Lyrica's subroutines - non-spell 2
      3. Lyrica's subroutines - spell 1
      4. Lyrica's subroutines - non-spell 3
      5. Lyrica's subroutines - spell 2
      6. Lyrica's full call stack - Post-boss glitch
    3. Lunasa's set-up
      1. Lunasa's subroutines - non-spell 1
      2. Lunasa's subroutines - non-spell 2
      3. Lunasa's subroutines - spell 1
      4. Lunasa's subroutines - non-spell 3
      5. Lunasa's full call stack - Post-boss glitch
  3. Why it happens
    1. What is the stack?
    2. The stack limit
    3. Stack overflow
      1. Stack overflow - Merlin's final spell-card glitch
        1. Merlin's final spell-card glitch - Merlin does not despawn
      2. Stack overflow - Merlin's post-boss glitch
        1. Merlin's post-boss glitch - Circular motion
      3. Stack overflow - Lyrica's post-boss glitch
        1. Lyrica's post-boss glitch - Circular motion
      4. Stack overflow - Lunasa's post-boss glitch
        1. Lunasa's post-boss glitch - Circular motion
    4. Stack overflow on other bosses?
  4. All probabilities
    1. Merlin's probabilities
    2. Lyrica's probabilities
    3. Lunasa's probabilities
  5. Conclusion
  6. Links
    1. Replays
    2. Videos
    3. Other

What happens


Merlin behaves weirdly during and after the final spell-card.

Two images of Merlin glitching out
Figure 1: Two instances in which Merlin glitches out on the final spell-card.

There have been a number of reports in which Merlin bugged out. During this, there are various things Merlin does that do not make much sense.
Once, she kept moving around in circles, even after her final spell-card had happened.
Another time, she still attacked even though the fight has already ended.
Other times, she doesn't despawn after the final spell-card.

Replays are compatible with this bug.
This glitch is extraordinarily rare. All current replays/videos related to the Merlin glitch that were uploaded before this page was published can be found in the Links section of this page.

Two images of Lunasa and Lyrica glitching out
Figure 2: Lyrica and Lunasa both glitch out after the final spell-card

Though, as of 2022-08-30, it has been found out that a Lyrica & Lunasa glitch exists.

The Lyrica & Lunasa glitch has had no prior reports. However, Lyrica & Lunasa can only glitch out after the final spell-card, and not during. Similarly, replays are compatible with this bug.

How it happens


The details to do this glitch are quite specific. The set-up is similar across the Prismriver sisters, but each glitch is done differently. The code provided in this page was decompiled using truth v0.5.0.

Merlin's set-up


All the following criteria must be met for the final spell-card Merlin glitch, which is where Merlin uses her attack from her penultimate spell-card during her final spell-card..

  1. Play as Sakuya. This ensures you face Merlin during the first half of the fight.
  2. End Merlin's 2nd non-spell during a specific time frame.
  3. End the 1st spell during a specific time frame. The time frame is quite large.
  4. Target Merlin during the 1st spell.
  5. End Merlin's 3rd non-spell during a specific time frame.
  6. End Merlin's solo spell during a very specific time frame.

All the following criteria must be met for the post-boss Merlin glitch, which is where Merlin still hangs around after the fight has ended.

  1. Play as Sakuya. This ensures you face Merlin during the first half of the fight.
  2. Target Merlin during the 1st spell.

On top of this, exactly three out of four following criteria must be met.

  • End Merlin's 2nd non-spell during a specific time frame.
  • End the 1st spell during a specific time frame. The time frame is quite large.
  • End Merlin's 3rd non-spell during a specific time frame.
  • End Merlin's solo spell during a very specific time frame.

What the specific time frame is is explained below alongside the probability of success per attack.

Merlin's subroutines - non-spell 2


A subroutine is a set of instructions designed to perform a frequently used operation within a program. Merlin frequently uses subroutines to spawn particle effects and to shoot bullets. In the following sections I denote the subroutines that are involved with the precisely-timed kills. In this section, I denote the subroutines for the 2nd non-spell.

Note: The precisely-timed kills thing only works on the Prismriver sisters ! It does not work on other PCB bosses. The reason is explained here.

For non-spell 2, the subroutines are as follows:

void sub80() { // Merlin's 2nd non-spell
    [...]
    loop {
        sub81(1.7671459); // sub81 is the stuff Merlin shoots
+30:
        sub60(30, 0.8); // sub60 makes Merlin move randomly - irrelevant
+30:
        sub81(-1.7671459);
+30:
        sub60(30, 0.8);
+100:
        sub81(-1.7671459);
        sub81(1.7671459);
        sub81(-1.7671459);
+50:
        sub60(30, 1.2);
+50:
    }
}
void sub81(float FPAR_0) { // what Merlin shoots during non-spell 2
    sub2(4, 32); // particle effects right before she shoots bullets
    [...] // stuff she shoots - lasts for 64 frames
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

So this might be a bit confusing, but I will explain what is going on during the 2nd non-spell.
Firstly, the subroutine sub80 is called. This is Merlin's 2nd non-spell. Now, sub80 can also call for other functions, such as sub60 and sub81. In this case, we want to look at sub81, which is the one responsible for shooting the bullets. sub81 happens to call for sub2, which is responsible for these little particle effects she calls for before attacking. sub2 lasts for exactly 16 frames.
So assuming we start the fight from the 2nd non-spell sub80, Merlin's call stack can look as follows: sub80 → sub81 → sub2.
I put the word can in italics because the call stack can also be different. For example, it can look like this sub80 → sub81 or even this sub80! But note: it cannot be sub80 → sub60 because sub60 is executed instantly.
But wait, how exactly does this work?
In essence, every subroutine lasts for a certain amount of frames. Let's take a look at the code for sub80 again:

void sub80() { // Merlin's 2nd non-spell
    [...]
    loop {
        sub81(1.7671459); // lasts for 16 + 64f
+30: // lasts for 30f
        sub60(30, 0.8); // lasts for 0f
+30: // lasts for 30f
        sub81(-1.7671459);// lasts for 16 + 64f
+30: // lasts for 30f
        sub60(30, 0.8); // lasts for 0f
+100: // lasts for 100f
        sub81(-1.7671459); // lasts for 16 + 64f
        sub81(1.7671459); // lasts for 16 + 64f
        sub81(-1.7671459); // lasts for 16 + 64f
+50: // lasts for 50f
        sub60(30, 1.2); // lasts for 0f
+50: // lasts for 50f
    }
}

Basically, when the game says +50:, it means that Merlin is waiting for 50 frames long. During this time, if we were to end the 2nd non-spell, the call stack would look as follows: sub80.
Secondly, suppose I were to end the 2nd non-spell while Merlin is shooting her bullets. She is calling for sub81, but in this case she is not calling for sub2. Because of this, the call stack would look as follows: sub80 → sub81.
Thirdly, suppose I were to end the 2nd non-spell right before Merlin shoots her bullets, so while the particle effects are spawning. She calls for sub81, which calls for sub2. The call stack would look as follows: sub80 → sub81 → sub2.

Now, it's possible to calculate the chance to get the call stack sub80 → sub81 → sub2 assuming the probability of the time at which the pattern was killed is uniformly distributed (i.e. the chances of killing Merlin during the 386th frame is the same as killing her on the 1024th frame). This doesn't reflect real chances due to the fact players would want to speedkill Merlin and this may get consistent kills, but this is the least we could work with.

The entire subroutine sub80 lasts for 690f before it loops. Out of the 690f, 260f is spent on waiting. During the other 430f she is busy with attacking. Out of the 430f, there are 80f where she is busy with calling for sub2.
So, there is a 80/690 chance for Merlin to exit the 2nd non-spell with the call stack sub80 → sub81 → sub2.

Merlin's subroutines - spell 1


Similarly, we can calculate the probabilities for Merlin's other subroutines. The functions used for spell 1 are as follows:

void sub121() { // First spell 
    [...]
    loop {
+40: // lasts for 40f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -4.712389); // lasts for 108f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -6.2831855); // lasts for 108f
        {"HL"}:  wait(40); // lasts for 40f only on Hard/Lunatic mode.
    }
}
void sub124(float FPAR_0, float FPAR_1) { // stuff Merlin shoots during first spell
    [...] // code that shoots bullets - lasts for 108f
}

We want to cancel the attack when the call stack is sub121 → sub124.

On Easy/Normal, the entire subroutine sub121 lasts for 472f before it loops. Out of the 472f, 40f is spent on waiting. During the other 432f she is busy with attacking thus Merlin is busy with calling for sub124.
So, there is a 432/472 chance for Merlin to exit the 1st spell with the call stack sub121 → sub124 on Easy/Normal mode.

On Hard/Lunatic, the entire subroutine sub121 lasts for 512f before it loops. Merlin is programmed to do nothing for 40f on Hard/Lunatic, probably for balancing reasons. Out of the 512f, 80f is spent on waiting. During the other 432f she is busy with attacking thus Merlin is busy with calling for sub124.
So, there is a 432/512 chance for Merlin to exit the 1st spell with the call stack sub121 → sub124 on Hard/Lunatic mode.

Merlin's subroutines - non-spell 3


The functions used for Merlin's third non-spell are as follows:

void sub83() { // Merlin's 3rd non-spell
    [...]
    loop {
        sub84(1.7671459); // lasts for 16 + 64f 
+30: // lasts for 30f
        sub60(30, 1.2); // lasts for 0f
+30: // lasts for 30f
        sub84(-1.7671459); // lasts for 16 + 64f
+30: // lasts for 30f
        sub60(30, 1.2); // lasts for 0f
+100: // lasts for 100f
        sub84(-1.7671459); // lasts for 16 + 64f
        sub84(1.7671459); // lasts for 16 + 64f
+20: // lasts for 20f
        sub84(-1.7671459); // lasts for 16 + 64f
        sub84(1.7671459); // lasts for 16 + 64f
+50: // lasts for 50f
        sub60(30, 1.2); // lasts for 0f
    }
}
void sub84(float FPAR_0) { // Merlin's third non-spell attack
    sub2(4, 32); // particle effects - last for 16f
    [...] // stuff she shoots - lasts for 64 frames
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

We want to cancel the attack when the call stack is sub83 → sub84 → sub2.

The entire subroutine sub83 lasts for 740f before it loops. Out of the 740f, 432f is spent on attacking. Right before she attacks, she calls for sub2, which per loop lasts for 96f.
So, there is a 96/740 chance for Merlin to exit the third non-spell with the call stack sub83 → sub84 → sub2.

Merlin's subroutines - spell 2


The relevant functions used for the second spell used by Merlin are as follows:

void sub138() { // Merlin spell 2
    [...]
    loop {
        sub2(4, 32); // lasts for 16f
+30: // lasts for 30f
        sub139(64.0, 160.0, F0, -0.09817477); // lasts for 0f
+8: // lasts for 8f
        sub139(64.0, 188.0, F0, -0.09817477); // lasts for 0f
+8: // lasts for 8f 
        sub139(64.0, 216.0, F0, -0.09817477); // lasts for 0f
+8: // lasts for 8f
        sub139(64.0, 244.0, F0, -0.09817477); // lasts for 0f
+8: // lasts for 8f 
        sub139(64.0, 272.0, F0, -0.09817477); // lasts for 0f
+100: // lasts for 100f 
        sub60(60, 1.5); // lasts for 0f
+60: // lasts for 60f 
        sub2(4, 32); // lasts for 16f
+30: // lasts for 30f
        sub139(352.0, 320.0, F0, -2.3561945); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 292.0, F0, -2.3561945); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 264.0, F0, -2.3561945); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 236.0, F0, -2.3561945); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 208.0, F0, -2.3561945); // lasts for 0f
+100: // lasts for 100f
        sub60(60, 1.5); // lasts for 0f
+60: // lasts for 60f
        sub2(4, 32); // lasts for 16f
+30: // lasts for 30f
        sub139(32.0, 48.0, F0, 1.5707964); // lasts for 0f
+8: // lasts for 8f 
        sub139(32.0, 76.0, F0, 1.3744467); // lasts for 0f
+8: // lasts for 8f 
        sub139(32.0, 104.0, F0, 0.5890486); // lasts for 0f
+8: // lasts for 8f 
        sub139(32.0, 132.0, F0, 0.3926991); // lasts for 0f
+8: // lasts for 8f 
        sub139(32.0, 160.0, F0, 0.0); // lasts for 0f
+20: // lasts for 20f
        sub2(4, 32); // lasts for 16f
+30: // lasts for 30f
        sub139(352.0, 48.0, F0, 1.5707964); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 76.0, F0, 1.7671459); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 104.0, F0, 2.552544); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 132.0, F0, 2.7488935); // lasts for 0f
+8: // lasts for 8f 
        sub139(352.0, 160.0, F0, 3.1415927); // lasts for 0f
+100: // lasts for 100f
        sub60(100, 1.5); // lasts for 0f
+100: // lasts for 100f
        sub2(4, 32);  // lasts for 16f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

We want to cancel the attack when the call stack is sub138 → sub2.

The entire subroutine sub138 lasts for 868f before it loops. Out of the 868f, 80f is spent on spawning the animation particles of sub2.
So, there is a 80/868 chance for Merlin to exit the third non-spell with the call stack sub138 → sub2.

Merlin's full call stack - Final spell-card glitch


During a full fight in which the final spell-card Merlin glitch is triggered, Merlin's full call stack looks as follows:

sub71 → sub72 → sub73 → sub76 → sub80 → sub81 → sub2 → sub121 → sub124 → sub83 → sub84 → sub2 → sub138 → sub2 → sub141/sub142 → sub143 → sub2

Below is described what each subroutine does in order:

  1. sub71 - Merlin is spawned
  2. sub72 - Merlin faces Sakuya in the first part of the boss fight
  3. sub73 - Merlin waits until the boss fight starts
  4. sub76 - Merlin's 1st non-spell
  5. sub80 - Merlin's 2nd non-spell
  6. sub81 - Merlin's 2nd non-spell attack
  7. sub2 - Particle effects spawned during the 2nd non-spell
  8. sub121 - Prismrivers' 1st spell-card
  9. sub124 - Merlin's 1st spell-card attack
  10. sub83 - Merlin's 3rd non-spell
  11. sub84 - Merlin's 3rd non-spell attack
  12. sub2 - Particle effects spawned during the 3rd non-spell
  13. sub138 - Merlin's 2nd spell-card
  14. sub2 - Particle effects spawned during the 2nd spell-card
  15. sub141/sub142 - Prismrivers' 3rd spell-card - Merlin's side
  16. sub143 - Prismrivers' 4th spell-card - Merlin's side
  17. sub2 - Particle effects spawned at the start of the 4th spell-card by Merlin. This always happens.

The final spell-card Merlin glitch takes place when the last subroutine sub2 in the full call stack is returned.

This has a very small probability of taking place. In essence, the following requirements must be met:

  1. The player has to play as Sakuya.
  2. The player has to target Merlin during the 1st spell-card.
  3. On Easy/Normal, the probability of triggering this glitch is 1/787. On Hard/Lunatic, the probability of triggering this glitch is 1/854.

The probabilities were calculated as follows:
For Easy/Normal mode, the probability of getting the call stack sub71 → sub72 → [...] → sub143 → sub2 is as follows:

P(E/N sub71 → sub72 → [...] → sub143 → sub2)
= P(sub80 → sub81 → sub2) * P(E/N sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
= 80/690 * 432/472 * 96/740 * 80/868
= 13824 / 10895353
≈ 0.127%
≈ 1/787

For Lunatic/Hard mode, the probability of getting the call stack sub71 → sub72 → [...] → sub143 → sub2 is as follows:

P(L/H sub71 → sub72 → [...] → sub143 → sub2)
= P(sub80 → sub81 → sub2) * P(L/H sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
= 80/690 * 432/512 * 96/740 * 80/868
= 216 / 184667
≈ 0.117%
≈ 1/854

This means on average for a random player to replicate the final spell-card Merlin glitch, on Easy/Normal it would take them 787 attempts, and on Hard/Lunatic it would take them 854 attempts.

Merlin's call stack - Post-boss glitch


During a full fight in which the post-boss Merlin glitch is triggered, Merlin's full call stack looks as follows:

sub71 → sub72 → sub73 → sub76 → sub80 → sub81 → sub2 → sub121 → sub124 → sub83 → sub84 → sub2 → sub138 → sub2 → sub141/sub142 → sub143 → sub52

For the post-boss Merlin glitch to happen, exactly three out of four conditions must be met:

  • sub80 → sub81 → sub2 (done on 2nd non-spell)
  • sub121 → sub124 (done on 1st spell)
  • sub83 → sub84 → sub2 (done on 3rd non-spell)
  • sub138 → sub2 (done on 2nd spell)

If less than three of the conditions are met, then the glitch does not happen. If four conditions are met, then Merlin does her final spell-card glitch.

Below is described what each subroutine does in order:

  1. sub71 - Merlin is spawned
  2. sub72 - Merlin faces Sakuya in the first part of the boss fight
  3. sub73 - Merlin waits until the boss fight starts
  4. sub76 - Merlin's 1st non-spell
  5. sub80 - Merlin's 2nd non-spell
  6. sub81 - Merlin's 2nd non-spell attack
  7. sub2 - Particle effects spawned during the 2nd non-spell
  8. sub121 - Prismrivers' 1st spell-card
  9. sub124 - Merlin's 1st spell-card attack
  10. sub83 - Merlin's 3rd non-spell
  11. sub84 - Merlin's 3rd non-spell attack
  12. sub2 - Particle effects spawned during the 3rd non-spell
  13. sub138 - Merlin's 2nd spell-card
  14. sub2 - Particle effects spawned during the 2nd spell-card
  15. sub141/sub142 - Prismrivers' 3rd spell-card - Merlin's side
  16. sub143 - Prismrivers' 4th spell-card - Merlin's side
  17. sub52 - Boss dies

The post-boss Merlin glitch takes place when the last subroutine sub52 in the full call stack is called

This has a very small probability of taking place. In essence, the following requirements must be met:

  1. The player has to play as Sakuya.
  2. The player has to target Merlin during the 1st spell-card.
  3. On Easy/Normal, the probability of triggering this glitch is 1/44. On Hard/Lunatic, the probability of triggering this glitch is 1/48.

The probabilities were calculated as follows:
For Easy/Normal mode, the probability of getting the call stack sub71 → sub72 → [...] → sub143 → sub52 with 3 out of 4 conditions being true is as follows:

P((E/N sub71 → sub72 → [...] → sub143 → sub52) with 3/4 subs true)
= P(sub80 → sub81 → sub2) * P(E/N sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 ⇏ sub2)
+ P(sub80 → sub81 → sub2) * P(E/N sub121 → sub124) * P(sub83 → sub84 ⇏ sub2) * P(sub138 → sub2)
+ P(sub80 → sub81 → sub2) * P(E/N sub121 ⇏ sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
+ P(sub80 → sub81 ⇏ sub2) * P(E/N sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
= 80/690 * 432/472 * 96/740 * 788/868 + 80/690 * 432/472 * 384/740 * 80/868 + 80/690 * 40/472 * 96/740 * 80/868 + 320/690 * 432/472 * 96/740 * 80/868
= 1240192 / 54476765
≈ 2.277%
≈ 1/44

For Lunatic/Hard mode, the probability of getting the call stack sub71 → sub72 → [...] → sub143 → sub52 with 3 out of 4 conditions being true is as follows:

P((L/H sub71 → sub72 → [...] → sub143 → sub52) with 3/4 subs true)
= P(sub80 → sub81 → sub2) * P(H/L sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 ⇏ sub2)
+ P(sub80 → sub81 → sub2) * P(H/L sub121 → sub124) * P(sub83 → sub84 ⇏ sub2) * P(sub138 → sub2)
+ P(sub80 → sub81 → sub2) * P(H/L sub121 ⇏ sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
+ P(sub80 → sub81 ⇏ sub2) * P(H/L sub121 → sub124) * P(sub83 → sub84 → sub2) * P(sub138 → sub2)
= 80/690 * 432/512 * 96/740 * 788/868 + 80/690 * 432/512 * 384/740 * 80/868 + 80/690 * 40/472 * 96/740 * 80/868 + 320/690 * 432/512 * 96/740 * 80/868
= 1143802 / 54476765
≈ 2.100%
≈ 1/48

This means on average for a random player to replicate the post-boss Merlin glitch, on Easy/Normal it would take them 44 attempts, and on Hard/Lunatic it would take them 48 attempts.

Lyrica's set-up


All the following criteria must be met for the Lyrica glitch.

  1. Play as Marisa. This ensures you face Lyrica during the first half of the fight.
  2. End Lyrica's 1st non-spell during a specific time frame.
  3. End Lyrica's 2nd non-spell during a specific time frame.
  4. End the 1st spell during a specific time frame. The time frame is quite large.
  5. Target Lyrica during the 1st spell.
  6. End Lyrica's 3rd non-spell during a specific time frame.
  7. End Lyrica's solo spell during a specific time frame. The time frame is quite large.

What the specific time frame is is explained below alongside the probability of success per attack.

Lyrica's subroutines - non-spell 1


The functions used for Lyrica's first non-spell are as follows:

void sub95() { // Lyrica's 1st non-spell
    [...]
    loop {
        sub2(4, 32); // particle effects - last for 16f
+30: // lasts for 30f
        [...] // stuff she shoots - lasts for 120f
        [...] // stuff she shoots - lasts for 120f
+100: // lasts for 100f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

We want to cancel the attack when the call stack is sub95 → sub2.

The entire subroutine sub95 lasts for 386f before it loops. Out of the 386f, 16f is spent on spawning the animation particles of sub2.
So, there is a 16/386 chance for Lyrica to exit the first non-spell with the call stack sub95 → sub2.

Lyrica's subroutines - non-spell 2


The functions used for Lyrica's second non-spell are as follows:

void sub98() { // Lyrica's 2nd non-spell
    [...]
    loop {
        sub2(4, 32); // lasts for 16f
        sub99(2); // lasts for 0f
+60: // lasts for 60f
        sub100(6); // lasts for 0f
        sub60(60, 1.3); // lasts for 0f
+60: // lasts for 60f
        sub99(2); // lasts for 0f
+30: // lasts for 30f
        sub100(6); // lasts for 0f
+60: // lasts for 60f
        sub60(60, 1.3); // lasts for 0f
+60: // lasts for 60f
        sub99(2); // lasts for 0f
+16: // lasts for 16f
        sub99(2); // lasts for 0f
+16: // lasts for 16f
        sub100(6); // lasts for 0f
+16: // lasts for 16f
        sub99(2); // lasts for 0f
+16: // lasts for 16f
        sub99(2); // lasts for 0f
+16: // lasts for 16f
        sub100(6); // lasts for 0f
+60: // lasts for 60f
        sub60(60, 1.3); // lasts for 0f
+60: // lasts for 60f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

We want to cancel the attack when the call stack is sub98 → sub2.

The entire subroutine sub98 lasts for 486f before it loops. Out of the 486f, 16f is spent on spawning the animation particles of sub2.
So, there is a 16/486 chance for Lyrica to exit the second non-spell with the call stack sub98 → sub2.

Lyrica's subroutines - spell 1


The functions used for spell 1 are as follows:

void sub121() { // First spell 
    [...]
    loop {
+40: // lasts for 40f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -4.712389); // lasts for 108f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -6.2831855); // lasts for 108f
        {"HL"}:  wait(40); // lasts for 40f only on Hard/Lunatic mode.
    }
}
void sub124(float FPAR_0, float FPAR_1) { // stuff Lyrica shoots during first spell
    [...] // code that shoots bullets - lasts for 108f
}

We want to cancel the attack when the call stack is sub121 → sub124.

On Easy/Normal, the entire subroutine sub121 lasts for 472f before it loops. Out of the 472f, 40f is spent on waiting. During the other 432f she is busy with attacking thus Lyrica is busy with calling for sub124.
So, there is a 432/472 chance for Lyrica to exit the 1st spell with the call stack sub121 → sub124 on Easy/Normal mode.

On Hard/Lunatic, the entire subroutine sub121 lasts for 512f before it loops. Lyrica is programmed to do nothing for 40f on Hard/Lunatic, probably for balancing reasons. Out of the 512f, 80f is spent on waiting. During the other 432f she is busy with attacking thus Lyrica is busy with calling for sub124.
So, there is a 432/512 chance for Lyrica to exit the 1st spell with the call stack sub121 → sub124 on Hard/Lunatic mode.

Lyrica's subroutines - non-spell 3


The functions used for Lyrica's third non-spell are as follows:

void sub103() { // Lyrica's 3rd non-spell
    [...]
    loop {
        sub2(4, 32); // lasts for 16f
        sub104(2); // lasts for 0f
+120: // lasts for 120f
        sub105(6); // lasts for 0f
        sub60(60, 0.6); // lasts for 0f
+60: // lasts for 60f
        sub104(2); // lasts for 0f
+60: // lasts for 60f
        sub105(6); // lasts for 0f
+60: // lasts for 60f
        sub60(60, 0.6); // lasts for 0f
+60: // lasts for 60f
        sub104(2); // lasts for 0f
+60: // lasts for 60f
        sub104(2); // lasts for 0f
+60: // lasts for 60f
        sub104(2); // lasts for 0f
+60: // lasts for 60f
        sub105(6); // lasts for 0f
+60: // lasts for 60f
        sub60(60, 0.6); // lasts for 0f
+60: // lasts for 60f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 16f
   [...] // spawns particle effects
}

We want to cancel the attack when the call stack is sub103 → sub2.

The entire subroutine sub103 lasts for 676f before it loops. Out of the 676f, 16f is spent on spawning the animation particles of sub2.
So, there is a 16/676 chance for Lyrica to exit the third non-spell with the call stack sub103 → sub2.

Lyrica's subroutines - spell 2


void sub145() { // Lyrica spell 2
    [...]
    loop {
        {"EN"}:  sub146(2, I0); // lasts for 90f
        {"HL"}:  sub149(13, I0); // lasts for 50f
        {"EN"}:  sub147(6, I0); // lasts for 90f
        {"HL"}:  sub148(2, I0); // lasts for 75f
        sub60(60, 1.0); // lasts for 0f
+60: // lasts for 60f
        {"EN"}:  sub146(2, I0); // lasts for 90f
        {"HL"}:  sub149(13, I0); // lasts for 50f
        {"EN"}:  sub147(6, I0); // lasts for 90f
        {"HL"}:  sub148(6, I0); // lasts for 75f 
        sub60(60, 1.3); // lasts for 0f
        [...]
+60: // lasts for 60f
    }
}
void sub146(int IPAR_0, int IPAR_1) { // stuff Lyrica shoots during second spell on Easy/Normal
    [...] // code that shoots bullets - lasts for 90f
}
void sub147(int IPAR_0, int IPAR_1) { // stuff Lyrica shoots during second spell on Easy/Normal
    [...] // code that shoots bullets - lasts for 90f
}
void sub148(int IPAR_0, int IPAR_1) { // stuff Lyrica shoots during second spell on Hard/Lunatic
    [...] // code that shoots bullets - lasts for 75f
}
void sub149(int IPAR_0, int IPAR_1) { // stuff Lyrica shoots during second spell on Hard/Lunatic
    [...] // code that shoots bullets - lasts for 50f
}

We want to cancel the attack when the call stack is sub145 → sub146/sub147/sub148/sub149.

On Easy/Normal, the entire subroutine sub145 lasts for 480f before it loops. Out of the 480f, 360f is spent on attacking where Lyrica is busy with calling for sub146 or sub147.
So, there is a 360/480 chance for Lyrica to exit the 2nd spell with the call stack sub145 → sub146/sub147 on Easy/Normal mode.

On Hard/Lunatic, the entire subroutine sub145 lasts for 370f before it loops. Out of the 370f, 250f is spent on attacking where Lyrica is busy with calling for sub148 or sub149.
So, there is a 250/370 chance for Lyrica to exit the 2nd spell with the call stack sub145 → sub148/sub149 on Hard/Lunatic mode.

Lyrica's full call stack - Post-boss glitch


During a full fight in which the Lyrica glitch is triggered, Lyrica's full call stack looks as follows:

sub88 → sub89 → sub90 → sub95 → sub2 → sub98 → sub2 → sub121 → sub124 → sub103 → sub2 → sub145 → sub146/sub147/sub148/sub149 → sub150 → sub152 → sub52

Below is described what each subroutine does in order:

  1. sub88 - Lyrica is spawned
  2. sub89 - Lyrica faces Marisa in the first part of the boss fight
  3. sub90 - Lyrica waits until the boss fight starts
  4. sub95 - Lyrica's 1st non-spell
  5. sub2 - Particle effects spawned during the 1st non-spell
  6. sub98 - Lyrica's 2nd non-spell
  7. sub2 - Particle effects spawned during the 2nd non-spell
  8. sub121 - Prismrivers' 1st spell-card
  9. sub124 - Lyrica's 1st spell-card attack
  10. sub103 - Lyrica's 3rd non-spell
  11. sub2 - Particle effects spawned during the 3rd non-spell
  12. sub145 - Lyrica's 2nd spell-card
  13. sub146/sub147/sub148/sub149 - Lyrica's 2nd spell-card attack
  14. sub150 - Prismrivers' 3rd spell-card - Lyrica's side
  15. sub152 - Prismrivers' 4th spell-card - Lyrica's side
  16. sub52 - Boss dies

The Lyrica glitch takes place when the last subroutine sub52 is called.

This has a very small probability of taking place. In essence, the following requirements must be met:

  1. The player has to play as Marisa.
  2. The player has to target Lyrica during the 1st spell-card.
  3. On Easy/Normal, the probability of triggering this glitch is 1/45103. On Hard/Lunatic, the probability of triggering this glitch is 1/54307.

The probabilities were calculated as follows:
For Easy/Normal mode, the probability of getting the call stack sub88 → sub89 → [...] → sub152 → sub52 is as follows:

P(E/N sub88 → sub89 → [...] → sub152 → sub52)
= P(sub95 → sub2) * P(sub98 → sub2) * P(E/N sub121 → sub124) * P(sub103 → sub2) * P(sub145 → sub146/sub147)
= 16/386 * 16/486 * 432/472 * 16/676 * 360/480
= 128 / 5773209
≈ 0.00222%
≈ 1/45103

For Lunatic/Hard mode, the probability of getting the call stack sub88 → sub89 → [...] → sub152 → sub52 is as follows:

P(L/H sub88 → sub89 → [...] → sub152 → sub52)
= P(sub95 → sub2) * P(sub98 → sub2) * P(H/L sub121 → sub124) * P(sub103 → sub2) * P(sub145 → sub148/sub149)
= 16/386 * 16/486 * 432/512 * 16/676 * 250/370
= 200 / 10861461
≈ 0.00184%
≈ 1/54307

This means on average for a random player to replicate the Lyrica glitch, on Easy/Normal it would take them 45,103 attempts, and on Hard/Lunatic it would take them 54,307 attempts.

Lunasa's set-up


All the following criteria must be met for the Lunasa glitch.

  1. Play as Reimu. This ensures you face Lunasa during the first half of the fight.
  2. End Lunasa's 1st non-spell during a specific time frame.
  3. End Lunasa's 2nd non-spell during a specific time frame.
  4. End the 1st spell during a specific time frame. The time frame is quite large.
  5. Target Lunasa during the 1st spell.
  6. End Lunasa's 3rd non-spell during a specific time frame.

What the specific time frame is is explained below alongside the probability of success per attack.

Lunasa's subroutines - non-spell 1


The functions used for Lunasa's first non-spell are as follows:

void sub58() { // Lunasa's 1st non-spell
    [...]
    loop {
        sub59(128.0, 1, 6, 4, 5); // lasts for 31 + 8f
+60: // lasts for 60f
        sub60(100, 0.8); // lasts for 0f
+100: // lasts for 100f
        sub59(-128.0, 0, 2, 4, 6); // lasts for 31 + 8f
+60: // lasts for 60f
        sub60(100, 0.8); // lasts for 0f
+100: // lasts for 100f
        sub59(128.0, 1, 6, 4, 6); // lasts for 31 + 8f
+60: // lasts for 60f
        sub60(100, 0.8); // lasts for 0f
+100: // lasts for 100f
        sub59(-128.0, 0, 2, 4, 7); // lasts for 31 + 8f
+60: // lasts for 60f
        [...]
+100: // lasts for 100f
        sub59(64.0, 1, 6, 4, 3); // lasts for 31 + 8f
+30: // lasts for 30f
        sub59(-64.0, 1, 6, 4, 3); // lasts for 31 + 8f
+60: // lasts for 60f
        sub59(112.0, 0, 2, 4, 4); // lasts for 31 + 8f
        sub59(-112.0, 0, 2, 4, 4); // lasts for 31 + 8f
+60: // lasts for 60f
        sub59(160.0, 3, 13, 4, 5); // lasts for 31 + 8f
        sub59(-160.0, 3, 13, 4, 5); // lasts for 31 + 8f
+300: // lasts for 300f
        sub60(100, 0.8); // lasts for 0f
+100: // lasts for 100f
    }
}
void sub59(float FPAR_0, int IPAR_0, int IPAR_1, int IPAR_2, int IPAR_3) {
    [...]
+1: // lasts for 1f
    sub3(2, 16, FPAR_0, 0.0); // lasts for 8f
    [...]
+30: // lasts for 30f
    [...]
}
void sub3(int IPAR_0, int IPAR_1, float FPAR_0, float FPAR_1) { // spawns particle effects - lasts for 8f
    [...] // spawns particle effects
}

In this case, the shown code of sub58 has been modified such that it does not take up the entire screen.

We want to cancel the attack when the call stack is sub58 → sub59 → sub3.

The entire subroutine sub58 lasts for 1580f before it loops. Out of the 1580f, 390f is spent on attacking using sub59. Out of the 390f, 80f is spent on spawning animation particles from sub3.
So, there is a 80/1580 chance for Lunasa to exit the first non-spell with the call stack sub58 → sub59 → sub3.

Lunasa's subroutines - non-spell 2


The relevant functions used for Lunasa's second non-spell are as follows:

void sub64() { // Lunasa's 2nd non-spell
    [...]
    loop {
        sub65(128, 16, 0.049087387, 0.0, 6, 3.0); // lasts for 0f
+60: // lasts for 60f
        sub65(128, 16, -0.049087387, 0.0, 2, 3.0); // lasts for 0f
+60: // lasts for 60f
        sub60(100, 0.8); // lasts for 0f
+100: // lasts for 100f
        sub2(2, 16); // lasts for 8f
+30: // lasts for 30f
        sub65(96, 16, 0.06544985, -1.5707964, 4, 3.0); // lasts for 0f
+30: // lasts for 30f
        sub65(96, 16, -0.06544985, 1.5707964, 3, 3.0); // lasts for 0f
+30: // lasts for 30f
        sub65(96, 16, 0.06544985, -1.5707964, 4, 3.0); // lasts for 0f
+30: // lasts for 30f
        sub65(96, 16, -0.06544985, 1.5707964, 3, 3.0); // lasts for 0f
+100: // lasts for 100f
        sub60(100, 0.8); // lasts for 0f
+50: // lasts for 50f
        sub2(2, 16); // lasts for 8f
+30: // lasts for 30f
        sub65(80, 8, 0.07853982, 3.1415927, 6, 4.6); // lasts for 0f
+30: // lasts for 30f
        sub65(80, 8, -0.07853982, -3.1415927, 6, 4.6); // lasts for 0f
+30: // lasts for 30f
        sub65(80, 8, 0.07853982, 1.5707964, 6, 4.6); // lasts for 0f
+30: // lasts for 30f
        sub65(80, 8, -0.07853982, -1.5707964, 6, 4.6); // lasts for 0f
+60: // lasts for 60f
        [...]
        sub65(60, 6, 0.10471976, 0.0, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub65(60, 6, -0.10471976, 0.0, 2, 2.5); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub65(60, 6, 0.10471976, 3.1415927, 2, 3.0); // lasts for 0f
+10: // lasts for 10f
        {"N"}:  sub65(60, 6, -0.10471976, 3.1415927, 2, 3.5); // lasts for 0f
        {"N"}:  sub65(60, 6, -0.10471976, 3.1415927, 2, 4.5); // lasts for 0f
        {"L"}:  sub65(60, 6, -0.10471976, 3.1415927, 2, 5.5); // lasts for 0f
        [...]
+120: // lasts for 120f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 8f
   [...] // spawns particle effects
}

Note: A lot of redundant lines have been removed from the sub64 code block on this page. This is done such that the code block does not take up the entire screen.

We want to cancel the attack when the call stack is sub64 → sub2.

The entire subroutine sub64 lasts for 836f before it loops. Out of the 836f, 16f is spent on spawning the animation particles of sub2.
So, there is a 16/836 chance for Lunasa to exit the second non-spell with the call stack sub64 → sub2.

Lunasa's subroutines - spell 1


The functions used for spell 1 are as follows:

void sub121() { // First spell 
    [...]
    loop {
+40: // lasts for 40f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -4.712389); // lasts for 108f
        sub124(0.0, 4.712389); // lasts for 108f
        sub124(3.1415927, -6.2831855); // lasts for 108f
        {"HL"}:  wait(40); // lasts for 40f only on Hard/Lunatic mode.
    }
}
void sub124(float FPAR_0, float FPAR_1) { // stuff Lunasa shoots during first spell
    [...] // code that shoots bullets - lasts for 108f
}

We want to cancel the attack when the call stack is sub121 → sub124.

On Easy/Normal, the entire subroutine sub121 lasts for 472f before it loops. Out of the 472f, 40f is spent on waiting. During the other 432f she is busy with attacking thus Lunasa is busy with calling for sub124.
So, there is a 432/472 chance for Lunasa to exit the 1st spell with the call stack sub121 → sub124 on Easy/Normal mode.

On Hard/Lunatic, the entire subroutine sub121 lasts for 512f before it loops. Lunasa is programmed to do nothing for 40f on Hard/Lunatic, probably for balancing reasons. Out of the 512f, 80f is spent on waiting. During the other 432f she is busy with attacking thus Lunasa is busy with calling for sub124.
So, there is a 432/512 chance for Lunasa to exit the 1st spell with the call stack sub121 → sub124 on Hard/Lunatic mode.

Lunasa's subroutines - non-spell 3


The functions used for Lunasa's third non-spell are as follows:

void sub68() { // Lunasa's 3rd non-spell
    [...]
    loop {
        sub2(2, 16); // lasts for 8f
        sub66(64, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(64, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub60(60, 0.8); // lasts for 0f
+60: // lasts for 60f
        sub2(2, 16); // lasts for 8f
        sub66(64, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, -0.06544985, 1.5707964, 2, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, -0.06544985, 1.5707964, 2, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(64, 8, -0.06544985, 1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, -0.06544985, 1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.06544985, 1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.06544985, -1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub60(60, 0.8); // lasts for 0f
+60: // lasts for 16f
        sub2(2, 16); // lasts for 8f
        sub66(64, 8, 0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, -0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, 0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(64, 8, 0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, -0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.07853982, 0.0, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.07853982, 3.1415927, 6, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub60(60, 0.8); // lasts for 0f
+60: // lasts for 60f
        sub2(2, 16); // lasts for 8f
        sub66(64, 8, 0.049087387, -0.3926991, 2, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, -0.049087387, 0.3926991, 2, 2.0); // lasts for 0f
+30: // lasts for 30f
        sub66(64, 8, 0.049087387, -0.7853982, 2, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, -0.049087387, 0.7853982, 2, 2.0); // lasts for 0f
+20: // lasts for 20f
        sub66(64, 8, 0.049087387, -1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(64, 8, -0.049087387, 1.5707964, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, 0.049087387, -3.1415927, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        sub66(56, 8, -0.049087387, 3.1415927, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.049087387, -3.1415927, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.049087387, 3.1415927, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, 0.049087387, -3.1415927, 2, 2.0); // lasts for 0f
+10: // lasts for 10f
        {"NHL"}:  sub66(56, 8, -0.049087387, 3.1415927, 2, 2.0); // lasts for 0f
        {"HL"}:  sub66(80, 8, 0.049087387, -0.7853982, 2, 3.0); // lasts for 0f
        {"L"}:  sub66(80, 8, -0.049087387, 0.7853982, 2, 4.0); // lasts for 0f
+60: // lasts for 60f
    }
}
void sub2(int IPAR_0, int IPAR_1) { // spawns particle effects - lasts for 8f
   [...] // spawns particle effects
}

Note: A lot of redundant lines have been removed from the sub68 code block on this page. This is done such that the code block does not take up the entire screen.

We want to cancel the attack when the call stack is sub68 → sub2.

The entire subroutine sub98 lasts for 920f before it loops. Out of the 920f, 32f is spent on spawning the animation particles of sub2.
So, there is a 32/952 chance for Lunasa to exit the third non-spell with the call stack sub68 → sub2.

Lunasa's full call stack - Post-boss glitch


During a full fight in which the Lunasa glitch is triggered, Lunasa's full call stack looks as follows:

sub53 → sub54 → sub55 → sub58 → sub59 → sub3 → sub64 → sub2 → sub121 → sub124 → sub68 → sub2 → sub127/sub128 → sub132/sub133 → sub135 → sub52

Below is described what each subroutine does in order:

  1. sub53 - Lunasa is spawned
  2. sub54 - Lunasa faces Reimu in the first part of the boss fight
  3. sub55 - Lunasa waits until the boss fight starts
  4. sub58 - Lunasa's 1st non-spell
  5. sub59 - Lunasa's 1st non-spell attack
  6. sub3 - Particle effects spawned during the 1st non-spell attack
  7. sub64 - Lunasa's 2nd non-spell
  8. sub2 - Particle effects spawned during the 2nd non-spell
  9. sub121 - Prismrivers' 1st spell-card
  10. sub124 - Lunasa's 1st spell-card attack
  11. sub68 - Lunasa's 3rd non-spell
  12. sub2 - Particle effects spawned during the 3rd non-spell
  13. sub127/sub128 - Lunasa's 2nd spell-card
  14. sub132/sub133 - Prismrivers' 3rd spell-card - Lunasa's side
  15. sub135 - Prismrivers' 4th spell-card - Lunasa's side + Centre bullets
  16. sub52 - Boss dies

The Lunasa glitch takes place when the last subroutine sub52 is called.

This has a very small probability of taking place. In essence, the following requirements must be met:

  1. The player has to play as Reimu.
  2. The player has to target Lunasa during the 1st spell-card.
  3. On Easy/Normal, the probability of triggering this glitch is 1/33543. On Hard/Lunatic, the probability of triggering this glitch is 1/36385.

The probabilities were calculated as follows:
For Easy/Normal mode, the probability of getting the call stack sub53 → sub54 → [...] → sub152 → sub52 is as follows:

P(E/N sub53 → sub54 → [...] → sub152 → sub52)
= P(sub58 → sub59 → sub3) * P(sub64 → sub2) * P(E/N sub121 → sub124) * P(sub68 → sub2)
= 80/1580 * 16/836 * 432/472 * 32/952
= 3456 / 115923731
≈ 0.00298%
≈ 1/33543

For Lunatic/Hard mode, the probability of getting the call stack sub53 → sub54 → [...] → sub152 → sub52 is as follows:

P(L/H sub53 → sub54 → [...] → sub152 → sub52)
= P(sub58 → sub59 → sub3) * P(sub64 → sub2) * P(H/L sub121 → sub124) * P(sub68 → sub2)
= 80/1580 * 16/836 * 432/512 * 32/952
= 54 / 1964809
≈ 0.00275%
≈ 1/36385

This means on average for a random player to replicate the Lunasa glitch, on Easy/Normal it would take them 33,543 attempts, and on Hard/Lunatic it would take them 36,385 attempts.

Why it happens


During the final spell-card Merlin glitch, Merlin's call stack looks as follows:

sub71 → sub72 → sub73 → sub76 → sub80 → sub81 → sub2 → sub121 → sub124 → sub83 → sub84 → sub2 → sub138 → sub2 → sub141/sub142 → sub143 → sub2

However, the stack has a limit that is reached in the Merlin glitch.

What is the stack?


The stack can be taken quite literally. Suppose you have a stack of papers (an organised pile, one on top of another). You can put another paper on top of the original stack (push) or take one of the top (pop). However, you never push/pop from the bottom or middle, as the stack would "collapse". A stack is useful if you are working on a task A and get interrupted by task B. You put A on the stack and start to work on B. When you're done with B, you check the stack to see what the previous task was. If C interrupts B, you put B on the stack, and start work on C.

In the context of Merlin, take for example the call stack sub80 → sub81 → sub2. We first start off with the call stack sub80. It then calls for sub81, making the new call stack sub80 → sub81. This once again calls for sub2, which means the new call stack is sub80 → sub81 → sub2. Once Merlin is done with spawning the particles in subroutine sub2, then sub2 is removed (popped) from the stack, and the call stack is now sub80 → sub81. Merlin proceeds to execute the rest of sub81. Once Merlin is done with sub81, this subroutine sub81 is popped from the stack and the call stack is now sub80.

The following image describes what was written above:

A diagram of the stack
Figure 3: A visualisation of the aforementioned example stack.

The stack limit


However, there is a limit as to how large a stack can be. If the call stack is too large, then this can cause serious issues.
The context stack is an array of 16 slots. However, ZUN made it such that only the first 15 slots of the stack work. The 16th slot acts as a mere buffer.

A diagram of the stack with stack overflow
Figure 4: An example visualisation of the stack when there is stack overflow

Stack overflow


The next two sections talk in extensive detail about why some sisters do weird things. The subsequent sections talk in slightly less detail.

Stack overflow - Merlin's final spell-card glitch


Take the call stack of the final spell-card Merlin glitch again, alongside what each subroutine does:

sub71 → sub72 → sub73 → sub76 → sub80 → sub81 → sub2 → sub121 → sub124 → sub83 → sub84 → sub2 → sub138 → sub2 → sub141/sub142 → sub143 → sub2
  1. sub71 - Merlin is spawned
  2. sub72 - Merlin faces Sakuya in the first part of the boss fight
  3. sub73 - Merlin waits until the boss fight starts
  4. sub76 - Merlin's 1st non-spell
  5. sub80 - Merlin's 2nd non-spell
  6. sub81 - Merlin's 2nd non-spell attack
  7. sub2 - Particle effects spawned during the 2nd non-spell
  8. sub121 - Prismrivers' 1st spell-card
  9. sub124 - Merlin's 1st spell-card attack
  10. sub83 - Merlin's 3rd non-spell
  11. sub84 - Merlin's 3rd non-spell attack
  12. sub2 - Particle effects spawned during the 3rd non-spell
  13. sub138 - Merlin's 2nd spell-card
  14. sub2 - Particle effects spawned during the 2nd spell-card
  15. sub141/sub142 - Prismrivers' 3rd spell-card - Merlin's side
  16. sub143 - Prismrivers' 4th spell-card - Merlin's side
  17. sub2 - Particle effects spawned at the start of the 4th spell-card by Merlin. This always happens.

The first 15 slots (sub71, [...], sub141/sub142) are pushed to the stack. The 16th slot (sub143) is a mere buffer - it is not pushed to the stack.
Since sub143 is not pushed to the stack, it means that any subroutines following sub143 return to the 15th slot once those subroutines return. In this case, the glitch happens once the last sub2 returns. This causes Merlin to return to sub141/sub142, which is the Prismrivers' 3rd spell-card.

A diagram of the stack of Merlin with stack overflow.
Figure 5: A visualisation of stack overflow on Merlin's call stack

However, occasionally Merlin would survive and keep attacking post-boss. This can be seen in the video of simugung.

On top of the final spell-card Merlin glitch, there is a chance where Merlin does not first move to the middle of the screen, but instead where Merlin keeps attacking as if nothing has happened. For this to happen, its probability is 12/1172, which requires for sub52 and sub144 to be called for on the same frame. Its concept is explained in more detail in the next section.

Merlin's final spell-card glitch - Merlin does not despawn

During the final spell-card Merlin glitch, Merlin is on the subroutine sub141/sub142 during the final spell-card of the boss fight. Merlin is only repeating everything inside the loop part, so the following section shows the relevant code of both subs inside the loop:

void sub141() { // Merlin's 3rd spell-card - Easy/Normal
    [...]
    loop {
        [...]
+130: // lasts for 130f
        call_repeat(60, sub144);
+260: // lasts for 260f
        call_repeat(0, -1);
+60: // lasts for 60f
        call_repeat(60, sub144);
+60: // lasts for 60f
        [...] // attack - lasts for 286f
+30: // lasts for 30f
        [...]
+30: // lasts for 30f
        call_repeat(0, -1);
        [...] // attack - lasts for 256f
+60: // lasts for 60f
    }
}
void sub142() { // Merlin's 3rd spell-card - Hard/Lunatic
    loop {
+30: // 300
        [...]
+30: // 330
        [...]
        call_repeat(0, -1);
        [...] // attack - lasts for 256f
+30: // 361
        [...]
+30: // 391
        [...]
        call_repeat(0, -1);
        [...] // attack - lasts for 256f
    }
}
void sub144() {
        [...] // stuff Merlin shoots - lasts for 0f
}

The instruction call_repeat(a, sub) calls for sub sub every a frames. If sub is equal to -1, then the previous call_repeat instructions are stopped.

In this case, Merlin calls for sub144 every 60f. Merlin makes exactly 12 calls to sub144 every loop. This happens until the instruction call_repeat(0, -1);, in which case Merlin stops calling for sub144.

The subroutine sub144 lasts for 0f but it gets pushed and popped from the stack on the same frame. This means that sub144 cannot exist in the stack, but since it has a return statement it can still cause the stack overflow if sub144 happens to be pushed to the 17th slot of the stack. When this happens, Merlin stays around after the boss-fight.

  1. Frame 1: Call stack is [...] → sub141 - HP is above 0 (note: sub141 is the 15th context of the stack)
  2. Frame 2: Call stack is [...] → sub141 → sub52 - HP is 0, so sub52 is pushed on the stack
  3. Frame 2 - Frame 62: Call stack is [...] → sub141 → sub52 → sub144 - It happens that sub144 is pushed during this moment.
  4. Also Frame 2 - Frame 62: Call stack becomes [...] → sub141

There is a 60f window (set by sub52) in which Merlin can call for sub144.

The entire subroutine sub141 lasts for 1172f before it loops. Out of the 1172f, there are 636f where call_repeat is active.
So during the final spell-card Merlin glitch, there is a 636/1172 chance for Merlin to not despawn on Easy/Normal mode. Merlin can never not despawn on Hard/Lunatic mode as there is no valid subroutine in the call_repeat instruction in sub142.

Stack overflow - Merlin's post-boss glitch


Take the call stack of the post-boss Merlin glitch.

sub71 → [...] → sub143 → sub52

In this case sub52 is the 16th context of the stack.

The code of sub143 is as follows:

void sub143() {
    [...]
    call_repeat(10, sub137);
    [...]
    loop {
        [...]
    }
}
void sub137() {
    [...] // lasts for 0f
}

Every 10 frames, sub137 is called. For Merlin to still hang around post-boss fight, sub137 must be called after sub52 is called. This has a 100% chance of happening.

Merlin's post-boss glitch - Circular motion

Sometimes, Merlin still moves around in circles post-boss fight. This can be seen in the replay of the player K・G, which is provided in the Links section.

For this to happen, sub137 must be called on the same frame sub52 is called. This has a 1/10 chance to happen on top of the post-boss Merlin glitch. When this is done, the call stack is as follows:

  1. Frame 1: Call stack is [...] → sub143
  2. Frame 2: Call stack is [...] → sub143 → sub52 → sub137
  3. Also Frame 2: Call stack becomes [...] → sub143

The call stack is [...] → sub143 → sub52 → sub137, and not [...] → sub143 → sub137 → sub52. This has to do with how the game handles the order of events. First, sub52 is pushed, then sub137 is pushed, all on the same frame. If the order were reversed, then it is not possible for Merlin to move around in a circle.

Stack overflow - Lyrica's post-boss glitch


Take the call stack of the post-boss Lyrica glitch.

sub88 → [...] → sub152 → sub52

In this case sub52 is the 16th context of the stack.

The code of sub152 is as follows:

void sub152() {
    [...]
    call_repeat(10, sub137);
    [...]
    loop {
        [...]
    }
}
void sub137() {
    [...] // lasts for 0f
}

Every 10 frames, sub137 is called. For Lyrica to still hang around post-boss fight, sub137 must be called after sub52 is called. This has a 100% chance of happening.

Lyrica's post-boss glitch - Circular motion

Sometimes, Lyrica still moves around in circles post-boss fight.

For this to happen, sub137 must be called on the same frame sub52 is called. This has a 1/10 chance to happen on top of the post-boss Lyrica glitch.

Stack overflow - Lunasa's post-boss glitch


Take the call stack of the post-boss Lunasa glitch.

sub53 → [...] → sub135 → sub52

In this case sub52 is the 16th context of the stack.

The code of sub135 is as follows:

void sub135() {
    [...]
    call_repeat((12 : 8 : 11 : 10), sub136);
    [...]
    loop {
        [...]
    }
}
void sub136() {
    [...] // lasts for 0f
}

On Easy/Normal/Hard/Lunatic mode, sub136 is called every 12f/8f/11f/10f respectively. For Lyrica to still hang around post-boss fight, sub136 must be called after sub52 is called. This has a 100% chance of happening.

Lunasa's post-boss glitch - Circular motion

Sometimes, Lunasa still moves around in circles post-boss fight.

For this to happen, sub136 must be called on the same frame sub52 is called. The probabilities for Merlin to circle around post boss-fight on top of the post-boss Lunasa glitch are as follows:

  • On Easy mode: 1/12
  • On Normal mode: 1/8
  • On Hard mode: 1/11
  • On Lunatic mode: 1/10

Stack overflow on other bosses?


The Prismriver sisters' boss-fight is implemented differently than how other bosses are implemented. In the case of the Prismriver sisters, they are controlled by an invisible boss manager. This is different from other bosses in PCB. In this case, the stack of the invisible boss manager gets reset whenever a life/timer callback is triggered. This is not the case with the Prismriver sisters themselves.

All probabilities


This section assumes you are facing Merlin/Lyrica/Lunasa as Sakuya/Marisa/Reimu respectively, and also that the same sister was targeted during the first spell-card.

Merlin's probabilities


Easy/Normal mode:

  1. Merlin does not glitch out E/N - 41/42
  2. Merlin does glitch out E/N - 1/42
    1. Final spell-card Merlin glitch - 1/19 (1/787)
      1. Merlin does not despawn - 636/1172 (1/1450)
      2. Merlin does despawn - 536/1172 (1/1720)
    2. Post-boss Merlin glitch - 1/1.056 (1/44)
      1. Merlin does not circle around - 9/10 (1/49)
      2. Merlin does circle around - 1/10 (1/440)

Hard/Lunatic mode:

  1. Merlin does not glitch out H/L - 44/45
  2. Merlin does glitch out H/L - 1/45
    1. Final spell-card Merlin glitch - 1/19 (1/854)
    2. Post-boss Merlin glitch - 1/1.056 (1/48)
      1. Merlin does not circle around - 9/10 (1/53)
      2. Merlin does circle around - 1/10 (1/476)

Lyrica's probabilities


Easy/Normal mode:

  1. No post-boss Lyrica glitch - 45102/45103
  2. Post-boss Lyrica glitch E/N - 1/45103
    1. Lyrica does not circle around - 9/10 (1/50114)
    2. Lyrica does circle around - 1/10 (1/451032)

Hard/Lunatic mode:

  1. No post-boss Lyrica glitch H/L - 54306/54307
  2. Post-boss Lyrica glitch H/L - 1/54307
    1. Lyrica does not circle around - 9/10 (1/60314)
    2. Lyrica does circle around - 1/10 (1/543073)

Lunasa's probabilities


Easy mode:

  1. No post-boss Lunasa glitch - 33542/33543
  2. Post-boss Lunasa glitch E/N - 1/33543
    1. Lunasa does not circle around Easy - 11/12 (1/36592)
    2. Lunasa does circle around Easy - 1/12 (1/402513)

Normal mode:

  1. No post-boss Lunasa glitch - 33542/33543
  2. Post-boss Lunasa glitch E/N - 1/33543
    1. Lunasa does not circle around Normal - 7/8 (1/38335)
    2. Lunasa does circle around Normal - 1/8 (1/268342)

Hard mode:

  1. No post-boss Lunasa glitch - 36384/36385
  2. Post-boss Lunasa glitch H/L - 1/36385
    1. Lunasa does not circle around Hard - 10/11 (1/40024)
    2. Lunasa does circle around Hard - 1/11 (1/400239)

Lunatic mode:

  1. No post-boss Lunasa glitch - 36384/36385
  2. Post-boss Lunasa glitch H/L - 1/36385
    1. Lunasa does not circle around Lunatic - 9/10 (1/40428)
    2. Lunasa does circle around Lunatic - 1/10 (1/363854)

Conclusion


The Merlin/Lyrica/Lunasa glitch is triggered through precisely-timed kills on various patterns as Sakuya/Marisa/Reimu respectively. There is roughly a 1 in 800 chance to get the final spell-card Merlin glitch. For Merlin to glitch out post-boss, there is roughly a lenient 1 in 32 chance. For Lunasa to glitch out, its probability is roughly a 1 in 35,000 chance. For Lyrica to glitch out, its probability is roughly a 1 in 50,000 chance. This has to do with how the game handles the call stack. The issue is caused because of stack overflow. This causes the sisters to prematurely return to their previous attack.


Replays


  • Replay AAAAAAAA by "ゆっきー". Easy, SakuyaA, 1.00b. 「September 13, 2020」. Download link (Note: Merlin final spell-card glitch - chance is 1/1720)
  • Replay + by "Plus". Normal, SakuyaB, 1.00b. 「July 20, 2020」. Download link (Note: Merlin post-boss glitch - chance is 1/49)
  • Replay KiS HaSB by "K・G". Hard, SakuyaB, 1.00b. 「September 12, 2020」. Download link (Note: Merlin post-boss glitch - chance is 1/476)
  • Replay UOKIN by "67811MKKA7". Lunatic, SakuyaB, 1.00b. 「April 29, 2021」. Download link (Note: Merlin final spell-card glitch - chance is 1/854)
  • Replay Nylilsa by "Nylilsa". Lunatic, MarisaB, 1.00b. 「August 31, 2022」. Download link (Note: Lyrica post-boss glitch - chance is 1/60314)
  • Replay Nylilsa by "Nylilsa". Lunatic, ReimuB, 1.00b. 「August 31, 2022」. Download link (Note: Lunasa post-boss glitch - chance is 1/40428)

Videos


Other


  • touhougarakuta. 「July 19, 2022」. "「『東方ダンマクカグラ』2022年10月28日でのサービス終了を発表」など、今週の東方ニュースまとめ" https://touhougarakuta.com/news/220719a/
All Perfect Cherry Blossom pages:
Merlin, Lyrica & Lunasa Glitch · Dialogue Pause Desync · Laser Midpoint Bug · The 2038 problem
All Overflow-related pages: