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.
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
- What happens
- How it happens
- Why it happens
- All probabilities
- Conclusion
- Links
What happens
Merlin behaves weirdly during and after 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.

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..
- Play as Sakuya. This ensures you face Merlin during the first half of the fight.
- 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.
- Target Merlin during the 1st spell.
- End Merlin's 3rd non-spell during a specific time frame.
- 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.
- Play as Sakuya. This ensures you face Merlin during the first half of the fight.
- 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:
Below is described what each subroutine does in order:
sub71- Merlin is spawnedsub72- Merlin faces Sakuya in the first part of the boss fightsub73- Merlin waits until the boss fight startssub76- Merlin's 1st non-spellsub80- Merlin's 2nd non-spellsub81- Merlin's 2nd non-spell attacksub2- Particle effects spawned during the 2nd non-spellsub121- Prismrivers' 1st spell-cardsub124- Merlin's 1st spell-card attacksub83- Merlin's 3rd non-spellsub84- Merlin's 3rd non-spell attacksub2- Particle effects spawned during the 3rd non-spellsub138- Merlin's 2nd spell-cardsub2- Particle effects spawned during the 2nd spell-cardsub141/sub142- Prismrivers' 3rd spell-card - Merlin's sidesub143- Prismrivers' 4th spell-card - Merlin's sidesub2- 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:
- The player has to play as Sakuya.
- The player has to target Merlin during the 1st spell-card.
- On Easy/Normal, the probability of triggering this glitch is
1/787. On Hard/Lunatic, the probability of triggering this glitch is1/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:
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:
sub71- Merlin is spawnedsub72- Merlin faces Sakuya in the first part of the boss fightsub73- Merlin waits until the boss fight startssub76- Merlin's 1st non-spellsub80- Merlin's 2nd non-spellsub81- Merlin's 2nd non-spell attacksub2- Particle effects spawned during the 2nd non-spellsub121- Prismrivers' 1st spell-cardsub124- Merlin's 1st spell-card attacksub83- Merlin's 3rd non-spellsub84- Merlin's 3rd non-spell attacksub2- Particle effects spawned during the 3rd non-spellsub138- Merlin's 2nd spell-cardsub2- Particle effects spawned during the 2nd spell-cardsub141/sub142- Prismrivers' 3rd spell-card - Merlin's sidesub143- Prismrivers' 4th spell-card - Merlin's sidesub52- 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:
- The player has to play as Sakuya.
- The player has to target Merlin during the 1st spell-card.
- On Easy/Normal, the probability of triggering this glitch is
1/44. On Hard/Lunatic, the probability of triggering this glitch is1/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.
- Play as Marisa. This ensures you face Lyrica during the first half of the fight.
- End Lyrica's 1st non-spell during a specific time frame.
- End Lyrica's 2nd non-spell during a specific time frame.
- End the 1st spell during a specific time frame. The time frame is quite large.
- Target Lyrica during the 1st spell.
- End Lyrica's 3rd non-spell during a specific time frame.
- 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:
Below is described what each subroutine does in order:
sub88- Lyrica is spawnedsub89- Lyrica faces Marisa in the first part of the boss fightsub90- Lyrica waits until the boss fight startssub95- Lyrica's 1st non-spellsub2- Particle effects spawned during the 1st non-spellsub98- Lyrica's 2nd non-spellsub2- Particle effects spawned during the 2nd non-spellsub121- Prismrivers' 1st spell-cardsub124- Lyrica's 1st spell-card attacksub103- Lyrica's 3rd non-spellsub2- Particle effects spawned during the 3rd non-spellsub145- Lyrica's 2nd spell-cardsub146/sub147/sub148/sub149- Lyrica's 2nd spell-card attacksub150- Prismrivers' 3rd spell-card - Lyrica's sidesub152- Prismrivers' 4th spell-card - Lyrica's sidesub52- 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:
- The player has to play as Marisa.
- The player has to target Lyrica during the 1st spell-card.
- On Easy/Normal, the probability of triggering this glitch is
1/45103. On Hard/Lunatic, the probability of triggering this glitch is1/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.
- Play as Reimu. This ensures you face Lunasa during the first half of the fight.
- End Lunasa's 1st non-spell during a specific time frame.
- End Lunasa's 2nd non-spell during a specific time frame.
- End the 1st spell during a specific time frame. The time frame is quite large.
- Target Lunasa during the 1st spell.
- 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:
Below is described what each subroutine does in order:
sub53- Lunasa is spawnedsub54- Lunasa faces Reimu in the first part of the boss fightsub55- Lunasa waits until the boss fight startssub58- Lunasa's 1st non-spellsub59- Lunasa's 1st non-spell attacksub3- Particle effects spawned during the 1st non-spell attacksub64- Lunasa's 2nd non-spellsub2- Particle effects spawned during the 2nd non-spellsub121- Prismrivers' 1st spell-cardsub124- Lunasa's 1st spell-card attacksub68- Lunasa's 3rd non-spellsub2- Particle effects spawned during the 3rd non-spellsub127/sub128- Lunasa's 2nd spell-cardsub132/sub133- Prismrivers' 3rd spell-card - Lunasa's sidesub135- Prismrivers' 4th spell-card - Lunasa's side + Centre bulletssub52- 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:
- The player has to play as Reimu.
- The player has to target Lunasa during the 1st spell-card.
- On Easy/Normal, the probability of triggering this glitch is
1/33543. On Hard/Lunatic, the probability of triggering this glitch is1/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:
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:

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.

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- Merlin is spawnedsub72- Merlin faces Sakuya in the first part of the boss fightsub73- Merlin waits until the boss fight startssub76- Merlin's 1st non-spellsub80- Merlin's 2nd non-spellsub81- Merlin's 2nd non-spell attacksub2- Particle effects spawned during the 2nd non-spellsub121- Prismrivers' 1st spell-cardsub124- Merlin's 1st spell-card attacksub83- Merlin's 3rd non-spellsub84- Merlin's 3rd non-spell attacksub2- Particle effects spawned during the 3rd non-spellsub138- Merlin's 2nd spell-cardsub2- Particle effects spawned during the 2nd spell-cardsub141/sub142- Prismrivers' 3rd spell-card - Merlin's sidesub143- Prismrivers' 4th spell-card - Merlin's sidesub2- 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.

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.
- Frame 1: Call stack is
[...] → sub141- HP is above 0 (note:sub141is the 15th context of the stack) - Frame 2: Call stack is
[...] → sub141 → sub52- HP is 0, sosub52is pushed on the stack - Frame 2 - Frame 62: Call stack is
[...] → sub141 → sub52 → sub144- It happens thatsub144is pushed during this moment. - 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.
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:
- Frame 1: Call stack is
[...] → sub143 - Frame 2: Call stack is
[...] → sub143 → sub52 → sub137 - 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.
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.
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:
- Merlin does not glitch out E/N - 41/42
- Merlin does glitch out E/N - 1/42
- Final spell-card Merlin glitch - 1/19 (1/787)
- Merlin does not despawn - 636/1172 (1/1450)
- Merlin does despawn - 536/1172 (1/1720)
- Post-boss Merlin glitch - 1/1.056 (1/44)
- Merlin does not circle around - 9/10 (1/49)
- Merlin does circle around - 1/10 (1/440)
- Final spell-card Merlin glitch - 1/19 (1/787)
Hard/Lunatic mode:
- Merlin does not glitch out H/L - 44/45
- Merlin does glitch out H/L - 1/45
- Final spell-card Merlin glitch - 1/19 (1/854)
- Post-boss Merlin glitch - 1/1.056 (1/48)
- Merlin does not circle around - 9/10 (1/53)
- Merlin does circle around - 1/10 (1/476)
Lyrica's probabilities
Easy/Normal mode:
- No post-boss Lyrica glitch - 45102/45103
- Post-boss Lyrica glitch E/N - 1/45103
- Lyrica does not circle around - 9/10 (1/50114)
- Lyrica does circle around - 1/10 (1/451032)
Hard/Lunatic mode:
- No post-boss Lyrica glitch H/L - 54306/54307
- Post-boss Lyrica glitch H/L - 1/54307
- Lyrica does not circle around - 9/10 (1/60314)
- Lyrica does circle around - 1/10 (1/543073)
Lunasa's probabilities
Easy mode:
- No post-boss Lunasa glitch - 33542/33543
- Post-boss Lunasa glitch E/N - 1/33543
- Lunasa does not circle around Easy - 11/12 (1/36592)
- Lunasa does circle around Easy - 1/12 (1/402513)
Normal mode:
- No post-boss Lunasa glitch - 33542/33543
- Post-boss Lunasa glitch E/N - 1/33543
- Lunasa does not circle around Normal - 7/8 (1/38335)
- Lunasa does circle around Normal - 1/8 (1/268342)
Hard mode:
- No post-boss Lunasa glitch - 36384/36385
- Post-boss Lunasa glitch H/L - 1/36385
- Lunasa does not circle around Hard - 10/11 (1/40024)
- Lunasa does circle around Hard - 1/11 (1/400239)
Lunatic mode:
- No post-boss Lunasa glitch - 36384/36385
- Post-boss Lunasa glitch H/L - 1/36385
- Lunasa does not circle around Lunatic - 9/10 (1/40428)
- Lunasa does circle around Lunatic - 1/10 (1/363854)
Conclusion
Links
Replays
- Replay
AAAAAAAAby "ゆっきー". 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 HaSBby "K・G". Hard, SakuyaB, 1.00b. 「September 12, 2020」. Download link (Note: Merlin post-boss glitch - chance is 1/476) - Replay
UOKINby "67811MKKA7". Lunatic, SakuyaB, 1.00b. 「April 29, 2021」. Download link (Note: Merlin final spell-card glitch - chance is 1/854) - Replay
Nylilsaby "Nylilsa". Lunatic, MarisaB, 1.00b. 「August 31, 2022」. Download link (Note: Lyrica post-boss glitch - chance is 1/60314) - Replay
Nylilsaby "Nylilsa". Lunatic, ReimuB, 1.00b. 「August 31, 2022」. Download link (Note: Lunasa post-boss glitch - chance is 1/40428)
Videos
- simugung. 「April 12, 2007」. "Perfect Cherry Blossom, Overheat of Merlin" https://www.youtube.com/watch?v=CU2Z6VobIvc
- Leviathean. 「May 21, 2007」. "Perfect Cherry Blossom - Berserk Merlin" https://www.youtube.com/watch?v=zKfcE_dJh8Y
- ゆっきー. 「December 29, 2020」. "メルラン暴走バグ" https://www.youtube.com/watch?v=5CCtaC7BnOw
- Nylilsa. 「July 10, 2022」. "東方妖々夢~メルラン・プリズムリバー暴走バグ" https://www.youtube.com/watch?v=F4b9lnb9218
- inakilbss. 「July 13, 2022」. "Perfect Cherry Blossom — Merlin Glitch + Prismrivers LNN (SakuyaA)" https://www.youtube.com/watch?v=aMRkmKmocsQ
- Nylilsa. 「August 31, 2022」. "東方妖々夢~リリカ・プリズムリバー暴走バグ" https://www.youtube.com/watch?v=tufvQ1fR3Y0
- Nylilsa. 「August 31, 2022」. "東方妖々夢~ルナサ・プリズムリバー暴走バグ" https://www.youtube.com/watch?v=q1-qCmcfB5A
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: |