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.

ReimuA Desync


Specifications


  • Versions: 1.00a
  • Difficulty: Easy - Normal - Hard - Lunatic - Extra
  • Mode: Main game - Practice mode
  • Shottype: ReimuA - ReimuB - ReimuC - MarisaA - MarisaB - MarisaC

What happens


Rarely, a replay desyncs on Stage 4. This only happens as ReimuA, and also only on the difficulty Easy mode. For the desync to happen, the replay can be started from any stage. This also means that Practice mode replays may desync. Though, the desync seemingly happens at random.

How it happens


For the desync to happen, the player must do the following:

  1. Play as ReimuA
  2. Play on Easy mode
  3. Play on Stage 4, and survive until Satori's 3rd spell-card.

The desync sometimes happens on Satori's 3rd spell-card or later.

Why it happens


Satori's boss-fight is unique in the sense that every shottype faces three different spell-cards. The code provided in this section is from the stage4c00a.ecl file, which is the code that is called when playing as ReimuA. The code was decompiled using thecl.

The code


The relevant code of ReimuA's 3rd spell-card Recollection "Flying Insects' Nest" is as follows:

void BossCard3_00A() {
    [...]
    @BossCard3_00Aat3() async;
    [...]
    @BossCard3_00Aat() async;
    [...]
    @BossCard3_00Aat2() async;
    [...]
}

In this case, Satori calls for three different subroutines: BossCard3_00Aat3, BossCard3_00Aat and BossCard3_00Aat2. In particular, we want to look at the subroutine BossCard3_00Aat2.

The full code of subroutine BossCard3_00Aat2 is pasted below:

void BossCard3_00Aat2() {
    var A, B, C, D, E;
    etNew(0);
    etOffsetAbs(0, 0.0f, 128.0f);
    etEx(0, 0, 0, 4, 30, -999999, -0.3f, -999999.0f);
    %A = _f(-192) + (%RANDF2 * _f(8));
    %B = _f(2);
    goto BossCard3_00Aat2_1496 @ 0;
BossCard3_00Aat2_308:
    playSound(16);
    %D = %RANDF2 * _f(32);
    etOffset(0, %A + (%RANDF2 * _f(0)), %D);
    unless ((($E % 2) == 0) || ($DIFF >= 1)) goto BossCard3_00Aat2_948 @ 0;
    unless (%PLAYER_Y2 < (%D + _f(128))) goto BossCard3_00Aat2_900 @ 0;
    laserOnA2(3, 4, -1.5707964f, 12.0f, 0, 128.0f, 0.0f, 16.0f);
    goto BossCard3_00Aat2_948 @ 0;
BossCard3_00Aat2_900:
    laserOnA2(3, 4, 1.5707964f, 12.0f, 0, 128.0f, 0.0f, 16.0f);
BossCard3_00Aat2_948:
    %C = %B / 2.8f;
    %A = %A + (%C * %C);
    unless (%A > _f(192)) goto BossCard3_00Aat2_1232 @ 0;
    return;
BossCard3_00Aat2_1232:
    unless (%B < _f(128)) goto BossCard3_00Aat2_1388 @ 0;
    %B = %B + _f(1);
BossCard3_00Aat2_1388:
    $E = $E + 1;
    diffWait(6, 6, 6, 6);
BossCard3_00Aat2_1496:
    if (1) goto BossCard3_00Aat2_308 @ 0;
    return;
}

The first line var A, B, C, D, E; declares the variables A, B, C, D, and E. The variables aren't too useful in this stage - the variables need to be initialised first before usage, which is done by assigning the variables a value. All variables are assigned a value before they are read, except for variable E. Its value is read in the line unless ((($E % 2) == 0) || [...], but E is not assigned a value prior to this. This means that E is an uninitialised variable. The uninitialised variable E will have some value, but its value is not predictable.

Easy-exclusivity


The line that is affected is as follows:

unless ((($E % 2) == 0) || ($DIFF >= 1)) goto BossCard3_00Aat2_948 @ 0;

The variable DIFF is an integer that returns the current difficulty (Easy = 0, Normal = 1, Hard = 2, Lunatic = 3, Extra = 4).

The line ($DIFF >= 1) returns true if played on Normal/Hard/Lunatic, and it returns false if played on Easy.

The line ($E % 2) == 0) returns true if E is an even number, and it returns false if E is an odd number. However, since E is uninitialised, E has some value that is unpredictable. It is likely that E may not even be an integer value, which results in the condition having a higher chance to return false than to return true.

The logical OR operator || returns true if one of the operands is true and returns false otherwise. This means that the statement ($E % 2) == 0) || ($DIFF >= 1) always returns true if played on a difficulty other than Easy mode.

If the statement is true, then the game goes to the next line: unless (%PLAYER_Y2 < (%D + _f(128))) goto BossCard3_00Aat2_900 @ 0;
If the statement is false, then the game jumps to the label BossCard3_00Aat2_948, skipping code responsible for spawning lasers that otherwise would have run if the statement were true.

Conclusion


Replays of ReimuA on Easy mode may desync on Satori's 3rd spell-card. This has to do with an uninitialised variable that only affects Easy mode. The uninitialised variable is assigned a random value, which causes code-execution to be inconsistent. This causes some lasers to desync, causing the replay to desync.


Replays


Any ReimuA replay of Stage 4 on Easy mode suffices, so none are provided.

Videos


All Subterranean Animism pages:
Negative Spell Bonus · ReimuA 2 Power Damage · Mid-boss skips · ReimuC Desync · Score Desync · ReimuA Desync · Stage 6 Desync · Stage transition Desync · Extra Relocking · Extra Leaderboard Crash · 999.99s Spell Timer · Stage Transition Death Crash · Slowdown Item Duplication
All Desync-related pages:
All Uninitialized-related pages:
All Shottype-related pages: