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.

Dragon Eater Timeout


Specifications


  • Versions: 1.00a
  • Difficulty: Easy - Normal - Hard - Lunatic - Extra
  • Mode: Main game - Practice mode - Spell practice
  • Shottype: Reimu - Marisa - Sakuya - Sanae
  • Cards: None

What happens


Momoyo's 8th spell-card is a spell-card that is aimed at the player and spawns a cluster of bullets that fire at a random direction from where the player was previously at. There is a bug in which Momoyo continues to spawn a cluster of bullets that are never-ending. This phenomenon appears at around the ~20.00 second mark. Momoyo continues to attack the player as she normally does, meaning that the amount of clusters of bullets increases. She continues firing new waves until the spell ends.

Momoyo overwhelming the player with bullets
Figure 1: Marisa is being overwhelmed by Momoyo's attack Dragon Eater.

How it happens


There are two requirements to be met for this bug to be triggered:

  1. The player must time down Momoyo's 8th spell-card for at least 60 seconds.
  2. The player must NOT be at an incredibly precise angle relative to Momoyo. The reason for this is explained in the next paragraph.

Why it happens


The information provided in this section is obtained by decompiling the st07bs.ecl file through using thecl with a premade eclmap. In the following section, all instruction names will be explained. Furthermore, if you want to read more about what the instructions do, you can search it up in the following page.

Momoyo's subroutines


Momoyo's 8th spell-card uses several subroutines to do her attacks. A subroutine is a set of instructions designed to perform a frequently used operation within a program. The following relevant subroutines to Momoyo's 8th spell-card are as follows:

void BossCard8() {
    [...]
    floatTime(0, %F1, 3600, 0, 3.0f, 30.0f);
    loop {
        $I1 = _S(%F1);
        @BossCard8_at();
    }
}
void BossCard8_at() {
    var ANGLE;
    [...]
    %ANGLE = %ANGLE_PLAYER - 3.1415927f;
    circlePos(%F0, %F1, %ANGLE + 1.0471976f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE + 0.5235988f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE - 0.5235988f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE - 1.0471976f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    return;
}
void BossCard8_et() {
    var DECREMENT;
    [...]
    $DECREMENT = $I1;
BossCard8_et_1040:
    etAngle(1, %A, 0.0f);
    %A = %A + 3.883222f;
    validRad(%A);
    etDist(1, %RANDF2 * _f(32));
    etOn(1);
    wait(3);
BossCard8_et_1284:
    if ($DECREMENT--) goto BossCard8_et_1040 @ 0;
    delete();
}

All the aforementioned code is explained in the following section. Also, the important instructions will be highlighted !

Code Explanation


  • BossCard8 is what Momoyo is currently.
  • BossCard8_at is what Momoyo attacks with. In this case, it only spawns an enemy called BossCard8_et.
  • BossCard8_et is the invisible enemy that is spawned by BossCard8_at. BossCard8_et is the one that spawns bullets. BossCard8_et despawns after a certain time.

In short, Momoyo uses the code at BossCard8 to spawn invisible enemies called BossCard8_et that shoot the bullets BossCard8_at.

BossCard8


For convenience, the code is posted below again:

void BossCard8() {
    [...]
    floatTime(0, %F1, 3600, 0, 3.0f, 30.0f);
    loop {
        $I1 = _S(%F1);
        @BossCard8_at();
    }
}

The instruction floatTime does the following:
In time frames using mode mode, variable var changes from start to final.
Instruction floatTime(0, %F1, 3600, 0, 3.0f, 30.0f) does the following: The float variable F1 is defined and updated every frame for 3600 frames long. Its initial value is 3.0 and its final value is 30.0. This takes a total of 3600 frames and it is done linearly (so on frame t the variable F1 is equal to 27/3600*t+3).

The line $I1 = _S(%F1) declares the integer variable I1, which is equal to the float value of F1 but converted to an integer and rounded down. This action is looped.

The line @BossCard8_at() calls for the subroutine BossCard8_at. This action is looped.

BossCard8_at


For convenience, the code is posted below again:

void BossCard8_at() {
    var ANGLE;
    [...]
    %ANGLE = %ANGLE_PLAYER - 3.1415927f;
    circlePos(%F0, %F1, %ANGLE + 1.0471976f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE + 0.5235988f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE - 0.5235988f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    circlePos(%F0, %F1, %ANGLE - 1.0471976f, 1400.0f);
    enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0);
    return;
}

The float variable ANGLE is given the value ANGLE_PLAYER - 3.1415927f, where ANGLE_PLAYER is the angle between the parent object (in this case Momoyo) and the player.
ANGLE_PLAYER is calculated as follows: ANGLE_PLAYER = arctan2(BOSS_Y - PLAYER_Y, BOSS_X - PLAYER_X)

The instruction circlePos(float varX, float varY, float ang, float radius) performs the following two actions: varX = cos(ang) * radius and varY = sin(ang) * radius
The instruction circlePos(%F0, %F1, %ANGLE + 1.0471976f, 1400.0f) performs the following two actions: F0 = cos(ANGLE + 1.0471976f) * 1400.0 and F0 = sin(ANGLE + 1.0471976f) * 1400.0

The instruction enmCreate is quite complicated, so it will be summarised in this page. To put it simply, enmCreate("BossCard8_et", 0.0f, 0.0f, 100, 0, 0); creates an enemy BossCard8_et at Momoyo's position.

The return keyword tells Momoyo to go back to the subroutine BossCard8 where she will keep looping her attacking.

BossCard8_et


For convenience, the code is posted below again:

void BossCard8_et() {
    var DECREMENT;
    [...]
    $DECREMENT = $I1;
BossCard8_et_1040:
    [...]
    etOn(1);
    wait(3);
BossCard8_et_1284:
    if ($DECREMENT--) goto BossCard8_et_1040 @ 0;
    delete();
}

The integer variable DECREMENT is declared and is assigned the value I1, which was declared in the subroutine BossCard8.
Instruction etOn(int id) makes the bullets spawn. In this case, etOn(1) spawns the arrowhead bullets.
Instruction wait(int time); stops the subroutine execution for time frames, so wait(3) stops the execution time for 3 frames.
Instruction delete(); deletes the caller once called. In this case, the subroutine BossCard8_et destroys itself once called.
Although it is written quite obtusely, the end part of subroutine BossCard8_et keeps shooting bullets through instruction etOn(1) until the integer variable DECREMENT is exactly equal to 0. The variable DECREMENT decreases by 1 every iteration.

Cause of Bullet spam


Instruction floatTime(0, %F1, 3600, 0, 3.0f, 30.0f) writes to the variable F1 for exactly 3600 frames. However, once those 3600 frames have passed, the variable F1 is no longer updated by the instruction floatTime.
Instead, the variable F1 is set by the instruction circlePos, where F1 = sin(A - 1.0471976f) * 1400.0 = sin(ANGLE_PLAYER - 4.1887903f) * 1400.0. This means that the amount of bullets spawning depends on the ANGLE_PLAYER value. Following this, the value I1 is equal to F1 but I1 is an integer. The variable DECREMENT is equal to the value of I1.

The invisible enemy bullet spawner BossCard8_et only despawns if DECREMENT is equal to 0. This means that the value DECREMENT must be low enough where the variable DECREMENT is in the range [0, 30] such that it doesn't go on infinitely (30 is chosen here because that is its intended maximum value that ZUN set according to floatTime). For DECREMENT to be low enough, the value I1 must be low enough. For I1 to be low enough, the value of F1 must be low enough. For F1 to be low enough, sin(ANGLE_PLAYER -1.0471977f) * 1400.0 must be low enough.

To give an estimate how precisely-small ANGLE_PLAYER for there to be no bullet spam, the angles will be calculated for when there are a total of 0 and 30 lingering bullets.
For there to be exactly 0 lingering bullets, the variable ANGLE_PLAYER must be around 1.0471977.
For there to be exactly 30 lingering bullets, the variable ANGLE_PLAYER must be around 1.0686279.

If the variable ANGLE_PLAYER were any higher, then the integer variable DECREMENT is a much higher value. This means that the invisible enemy bullet spawner would have to shoot a few thousand bullets before it despawns. Furthermore, if the variable ANGLE_PLAYER were any lower, then the integer variable DECREMENT is a negative value. It means that the value would have to underflow, meaning it has to go through a few billion iterations before the invisible enemy bullet spawner despawns.

To visualise how precise the angles are, I have modified the stage such that Momoyo shoots lasers that are at the aforementioned angles. Anything outside the lasers gives the player waves that are going to last a while:

Two lines spawn out of Momoyo pointing to the bottom right.
Figure 2: Visualisation of the precise angle is shown in pink.

In order to humanely timeout this spell, the player must be between the two lasers when Momoyo is spawning the invisible bullet spawning enemies. This is humanely impossible to do considering Momoyo constantly moves around alongside the difficulty of the spell.

Conclusion


The bullets on Dragon Eater are shot a small number of times before it stops shooting. However, after 60 seconds there is an overwhelming number of bullets each wave. This is because part of the code is accessed differently. Instead, the amount of bullets that are shot depends on your player angle.


Replays


This section has no content yet. Would you like to add to this section? Contact me if you are interested!

Videos


All Unconnected Marketeers pages:
Slowdown Item Duplication · Chimata Final Timeout Crash · D press Desync · Takane Card Cost · No Bombs in Practice Mode · Centipede + Wolf cards combination · Lily White Crash · Dragon Eater Timeout · Momoyo's Final Bomb Damage · (Sub-)shottypes Not Functioning · Shield Method of the Youkai Laser Misalignment · Shop Manipulation · Incorrect Playtime · Koishi Card Desync · Equipment Cards Desync · Momoyo Card Desync · Byakuren Card Desync · Eirin/Eiki Card Purchase Crash · Eirin Card Deactivation
All Ecl-related pages: